diff --git a/.gitmodules b/.gitmodules index ee1cc80880..f1c4f5d172 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "osu-framework"] - path = osu-framework - url = https://github.com/ppy/osu-framework [submodule "osu-resources"] path = osu-resources - url = https://github.com/ppy/osu-resources + url = https://github.com/ppy/osu-resources \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml index be69e92748..1c988fe6fd 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml index da968b54b7..d7bb0f90f1 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml index f2b8155e37..997ac6b078 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml index 941455f8b4..b7a070174c 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml @@ -1,21 +1,17 @@ - diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml b/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml deleted file mode 100644 index 20a15f985f..0000000000 --- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__net471_.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__netcoreapp2_0_.xml b/.idea/.idea.osu/.idea/runConfigurations/VisualTests__netcoreapp2_1_.xml similarity index 82% rename from .idea/.idea.osu/.idea/runConfigurations/VisualTests__netcoreapp2_0_.xml rename to .idea/.idea.osu/.idea/runConfigurations/VisualTests__netcoreapp2_1_.xml index 08b4e38667..2d3a848922 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests__netcoreapp2_0_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/VisualTests__netcoreapp2_1_.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml deleted file mode 100644 index 7196e486d2..0000000000 --- a/.idea/.idea.osu/.idea/runConfigurations/osu___net471_.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu___netcoreapp2_0_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___netcoreapp2_1_.xml similarity index 82% rename from .idea/.idea.osu/.idea/runConfigurations/osu___netcoreapp2_0_.xml rename to .idea/.idea.osu/.idea/runConfigurations/osu___netcoreapp2_1_.xml index 2f5c137631..36efe211c6 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/osu___netcoreapp2_0_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/osu___netcoreapp2_1_.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index df5b11f63a..ed67fa92cc 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,110 +2,54 @@ "version": "0.2.0", "configurations": [ { - "name": "VisualTests (Debug, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Debug, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "VisualTests (Release, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Release, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "osu! (Debug, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Desktop/bin/Debug/net471/osu!.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Debug, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "osu! (Release, net471)", - "windows": { - "type": "clr" - }, - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/osu.Desktop/bin/Release/net471/osu!.exe", - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Release, msbuild)", - "runtimeExecutable": null, - "env": {}, - "console": "internalConsole" - }, - { - "name": "VisualTests (Debug, netcoreapp2.0)", + "name": "VisualTests (Debug)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Debug, dotnet)", + "preLaunchTask": "Build tests (Debug)", "env": {}, "console": "internalConsole" }, { - "name": "VisualTests (Release, netcoreapp2.0)", + "name": "VisualTests (Release)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll" + "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1/osu.Game.Tests.dll" ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Release, dotnet)", + "preLaunchTask": "Build tests (Release)", "env": {}, "console": "internalConsole" }, { - "name": "osu! (Debug, netcoreapp2.0)", + "name": "osu! (Debug)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll", ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Debug, dotnet)", + "preLaunchTask": "Build osu! (Debug)", "env": {}, "console": "internalConsole" }, { - "name": "osu! (Release, netcoreapp2.0)", + "name": "osu! (Release)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.0/osu!.dll", + "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll", ], "cwd": "${workspaceRoot}", - "preLaunchTask": "Build (Release, dotnet)", + "preLaunchTask": "Build osu! (Release)", "env": {}, "console": "internalConsole" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7144a584f3..188f20b69f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,41 +4,14 @@ "version": "2.0.0", "tasks": [ { - "label": "Build (Debug, msbuild)", - "type": "shell", - "command": "msbuild", - "args": [ - "/p:TargetFramework=net471", - "/p:GenerateFullPaths=true", - "/m", - "/verbosity:m" - ], - "group": "build", - "problemMatcher": "$msCompile" - }, - { - "label": "Build (Release, msbuild)", - "type": "shell", - "command": "msbuild", - "args": [ - "/p:Configuration=Release", - "/p:TargetFramework=net471", - "/p:GenerateFullPaths=true", - "/m", - "/verbosity:m" - ], - "group": "build", - "problemMatcher": "$msCompile" - }, - { - "label": "Build (Debug, dotnet)", + "label": "Build osu! (Debug)", "type": "shell", "command": "dotnet", "args": [ "build", "--no-restore", "osu.Desktop", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -47,14 +20,14 @@ "problemMatcher": "$msCompile" }, { - "label": "Build (Release, dotnet)", + "label": "Build osu! (Release)", "type": "shell", "command": "dotnet", "args": [ "build", "--no-restore", "osu.Desktop", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:Configuration=Release", "/p:GenerateFullPaths=true", "/m", @@ -64,16 +37,40 @@ "problemMatcher": "$msCompile" }, { - "label": "Restore (net471)", + "label": "Build tests (Debug)", "type": "shell", - "command": "nuget", + "command": "dotnet", "args": [ - "restore" + "build", + "--no-restore", + "osu.Game.Tests", + "/p:TargetFramework=netcoreapp2.1", + "/p:GenerateFullPaths=true", + "/m", + "/verbosity:m" ], - "problemMatcher": [] + "group": "build", + "problemMatcher": "$msCompile" }, { - "label": "Restore (netcoreapp2.0)", + "label": "Build tests (Release)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Tests", + "/p:TargetFramework=netcoreapp2.1", + "/p:Configuration=Release", + "/p:GenerateFullPaths=true", + "/m", + "/verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Restore (netcoreapp2.1)", "type": "shell", "command": "dotnet", "args": [ diff --git a/appveyor.yml b/appveyor.yml index 69bc762f4c..ac6d6ebff8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -2,11 +2,6 @@ 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 diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index cadebd9d11..0247714cdf 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -1,29 +1,24 @@ -branches: - only: - - release -skip_tags: true -skip_branch_with_pr: true clone_depth: 1 -version: '{branch}-{build}' +version: '{build}' +skip_non_tags: true image: Visual Studio 2017 -configuration: Debug -cache: - - packages -> **\packages.config install: - - cmd: git submodule update --init --recursive --depth=5 + - git clone https://github.com/ppy/osu-deploy before_build: + - ps: if($env:appveyor_repo_tag -eq 'True') { Update-AppveyorBuild -Version $env:appveyor_repo_tag_name } + - cmd: git submodule update --init --recursive --depth=5 - cmd: nuget restore -verbosity quiet -build: - project: osu.Desktop.Deploy/osu.Desktop.Deploy.csproj - verbosity: minimal -after_build: +build_script: - 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% + - cd osu-deploy + - nuget restore -verbosity quiet + - msbuild osu.Desktop.Deploy.csproj + - cmd: ..\appveyor-tools\secure-file -decrypt ..\fdc6f19b04.enc -secret %decode_secret% -out bin\Debug\net471\osu.Desktop.Deploy.exe.config + - cd bin\Debug\net471\ + - osu.Desktop.Deploy.exe %code_signing_password% %APPVEYOR_REPO_TAG_NAME% environment: TargetFramework: net471 decode_secret: @@ -31,4 +26,7 @@ environment: code_signing_password: secure: 34tLNqvjmmZEi97MLKfrnQ== artifacts: - - path: 'Releases\*' \ No newline at end of file + - path: 'Releases\*' +deploy: + - provider: Environment + name: github \ No newline at end of file diff --git a/osu-framework b/osu-framework deleted file mode 160000 index eb076a3301..0000000000 --- a/osu-framework +++ /dev/null @@ -1 +0,0 @@ -Subproject commit eb076a3301231eb73917073499051e49a9b12978 diff --git a/osu.Desktop.Deploy/App.config b/osu.Desktop.Deploy/App.config deleted file mode 100644 index 9ec53d5a31..0000000000 --- a/osu.Desktop.Deploy/App.config +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/osu.Desktop.Deploy/GitHubObject.cs b/osu.Desktop.Deploy/GitHubObject.cs deleted file mode 100644 index 02ff16fa69..0000000000 --- a/osu.Desktop.Deploy/GitHubObject.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; - -namespace osu.Desktop.Deploy -{ - public class GitHubObject - { - [JsonProperty(@"id")] - public int Id; - - [JsonProperty(@"name")] - public string Name; - } -} diff --git a/osu.Desktop.Deploy/GitHubRelease.cs b/osu.Desktop.Deploy/GitHubRelease.cs deleted file mode 100644 index faf312d4f7..0000000000 --- a/osu.Desktop.Deploy/GitHubRelease.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using Newtonsoft.Json; - -namespace osu.Desktop.Deploy -{ - public class GitHubRelease - { - [JsonProperty(@"id")] - public int Id; - - [JsonProperty(@"tag_name")] - public string TagName => $"v{Name}"; - - [JsonProperty(@"name")] - public string Name; - - [JsonProperty(@"draft")] - public bool Draft; - - [JsonProperty(@"prerelease")] - public bool PreRelease; - - [JsonProperty(@"upload_url")] - public string UploadUrl; - } -} diff --git a/osu.Desktop.Deploy/Program.cs b/osu.Desktop.Deploy/Program.cs deleted file mode 100644 index a1c2a8aef2..0000000000 --- a/osu.Desktop.Deploy/Program.cs +++ /dev/null @@ -1,471 +0,0 @@ -// 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.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; -using WebRequest = osu.Framework.IO.Network.WebRequest; - -namespace osu.Desktop.Deploy -{ - internal static class Program - { - 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.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"]; - public static string ReleasesFolder = ConfigurationManager.AppSettings["ReleasesFolder"]; - public static string GitHubAccessToken = ConfigurationManager.AppSettings["GitHubAccessToken"]; - public static string GitHubUsername = ConfigurationManager.AppSettings["GitHubUsername"]; - public static string GitHubRepoName = ConfigurationManager.AppSettings["GitHubRepoName"]; - public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"]; - public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"]; - public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"]; - public static string TargetNames = ConfigurationManager.AppSettings["TargetName"]; - public static string PackageName = ConfigurationManager.AppSettings["PackageName"]; - public static string IconName = ConfigurationManager.AppSettings["IconName"]; - public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"]; - - public static string GitHubApiEndpoint => $"https://api.github.com/repos/{GitHubUsername}/{GitHubRepoName}/releases"; - public static string GitHubReleasePage => $"https://github.com/{GitHubUsername}/{GitHubRepoName}/releases"; - - /// - /// How many previous build deltas we want to keep when publishing. - /// - private const int keep_delta_count = 4; - - private static string codeSigningCmd => string.IsNullOrEmpty(codeSigningPassword) ? "" : $"-n \"/a /f {codeSigningCertPath} /p {codeSigningPassword} /t http://timestamp.comodoca.com/authenticode\""; - - private static string homeDir => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - private static string codeSigningCertPath => Path.Combine(homeDir, CodeSigningCertificate); - private static string solutionPath => Environment.CurrentDirectory; - private static string stagingPath => Path.Combine(solutionPath, StagingFolder); - private static string iconPath => Path.Combine(solutionPath, ProjectName, IconName); - - private static string nupkgFilename(string ver) => $"{PackageName}.{ver}.nupkg"; - private static string nupkgDistroFilename(string ver) => $"{PackageName}-{ver}-full.nupkg"; - - private static readonly Stopwatch sw = new Stopwatch(); - - private static string codeSigningPassword; - - private static bool interactive; - - public static void Main(string[] args) - { - interactive = args.Length == 0; - - displayHeader(); - - findSolutionPath(); - - if (!Directory.Exists(ReleasesFolder)) - { - write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow); - Directory.CreateDirectory(ReleasesFolder); - } - - checkGitHubReleases(); - - refreshDirectory(StagingFolder); - - //increment build number until we have a unique one. - string verBase = DateTime.Now.ToString("yyyy.Mdd."); - int increment = 0; - while (Directory.GetFiles(ReleasesFolder, $"*{verBase}{increment}*").Any()) - increment++; - - string version = $"{verBase}{increment}"; - - Console.ForegroundColor = ConsoleColor.White; - Console.Write($"Ready to deploy {version}!"); - pauseIfInteractive(); - - sw.Start(); - - if (!string.IsNullOrEmpty(CodeSigningCertificate)) - { - Console.Write("Enter code signing password: "); - codeSigningPassword = args.Length > 0 ? args[0] : readLineMasked(); - } - - write("Updating AssemblyInfo..."); - updateCsprojVersion(version); - updateAppveyorVersion(version); - - write("Running build process..."); - foreach (string targetName in TargetNames.Split(',')) - runCommand(msbuild_path, $"/v:quiet /m /t:{targetName.Replace('.', '_')} /p:OutputPath={stagingPath};Targets=\"Clean;Build\";Configuration=Release {SolutionName}.sln"); - - write("Creating NuGet deployment package..."); - runCommand(nugetPath, $"pack {NuSpecName} -Version {version} -Properties Configuration=Deploy -OutputDirectory {stagingPath} -BasePath {stagingPath}"); - - //prune once before checking for files so we can avoid erroring on files which aren't even needed for this build. - pruneReleases(); - - checkReleaseFiles(); - - write("Running squirrel build..."); - runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --framework-version=net471 --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi"); - - //prune again to clean up before upload. - pruneReleases(); - - //rename setup to install. - File.Copy(Path.Combine(ReleasesFolder, "Setup.exe"), Path.Combine(ReleasesFolder, "install.exe"), true); - File.Delete(Path.Combine(ReleasesFolder, "Setup.exe")); - - uploadBuild(version); - - //reset assemblyinfo. - updateCsprojVersion("0.0.0"); - - write("Done!", ConsoleColor.White); - pauseIfInteractive(); - } - - private static void displayHeader() - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(); - Console.WriteLine(" Please note that OSU! and PPY are registered trademarks and as such covered by trademark law."); - Console.WriteLine(" Do not distribute builds of this project publicly that make use of these."); - Console.ResetColor(); - Console.WriteLine(); - } - - /// - /// Ensure we have all the files in the release directory which are expected to be there. - /// This should have been accounted for in earlier steps, and just serves as a verification step. - /// - private static void checkReleaseFiles() - { - if (!canGitHub) return; - - var releaseLines = getReleaseLines(); - - //ensure we have all files necessary - foreach (var l in releaseLines) - if (!File.Exists(Path.Combine(ReleasesFolder, l.Filename))) - error($"Local file missing {l.Filename}"); - } - - private static IEnumerable getReleaseLines() => File.ReadAllLines(Path.Combine(ReleasesFolder, "RELEASES")).Select(l => new ReleaseLine(l)); - - private static void pruneReleases() - { - if (!canGitHub) return; - - write("Pruning RELEASES..."); - - var releaseLines = getReleaseLines().ToList(); - - var fulls = releaseLines.Where(l => l.Filename.Contains("-full")).Reverse().Skip(1); - - //remove any FULL releases (except most recent) - foreach (var l in fulls) - { - write($"- Removing old release {l.Filename}", ConsoleColor.Yellow); - File.Delete(Path.Combine(ReleasesFolder, l.Filename)); - releaseLines.Remove(l); - } - - //remove excess deltas - var deltas = releaseLines.Where(l => l.Filename.Contains("-delta")).ToArray(); - if (deltas.Length > keep_delta_count) - { - foreach (var l in deltas.Take(deltas.Length - keep_delta_count)) - { - write($"- Removing old delta {l.Filename}", ConsoleColor.Yellow); - File.Delete(Path.Combine(ReleasesFolder, l.Filename)); - releaseLines.Remove(l); - } - } - - var lines = new List(); - releaseLines.ForEach(l => lines.Add(l.ToString())); - File.WriteAllLines(Path.Combine(ReleasesFolder, "RELEASES"), lines); - } - - private static void uploadBuild(string version) - { - if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate)) - return; - - write("Publishing to GitHub..."); - - write($"- Creating release {version}...", ConsoleColor.Yellow); - var req = new JsonWebRequest($"{GitHubApiEndpoint}") - { - Method = HttpMethod.POST, - }; - req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease - { - Name = version, - Draft = true, - PreRelease = true - })); - req.AuthenticatedBlockingPerform(); - - var assetUploadUrl = req.ResponseObject.UploadUrl.Replace("{?name,label}", "?name={0}"); - foreach (var a in Directory.GetFiles(ReleasesFolder).Reverse()) //reverse to upload RELEASES first. - { - write($"- Adding asset {a}...", ConsoleColor.Yellow); - var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a)) - { - Method = HttpMethod.POST, - Timeout = 240000, - ContentType = "application/octet-stream", - }; - - upload.AddRaw(File.ReadAllBytes(a)); - upload.AuthenticatedBlockingPerform(); - } - - openGitHubReleasePage(); - } - - private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage); - - private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken); - - private static void checkGitHubReleases() - { - if (!canGitHub) return; - - write("Checking GitHub releases..."); - var req = new JsonWebRequest>($"{GitHubApiEndpoint}"); - req.AuthenticatedBlockingPerform(); - - var lastRelease = req.ResponseObject.FirstOrDefault(); - - if (lastRelease == null) - return; - - if (lastRelease.Draft) - { - openGitHubReleasePage(); - error("There's a pending draft release! You probably don't want to push a build with this present."); - } - - //there's a previous release for this project. - var assetReq = new JsonWebRequest>($"{GitHubApiEndpoint}/{lastRelease.Id}/assets"); - assetReq.AuthenticatedBlockingPerform(); - var assets = assetReq.ResponseObject; - - //make sure our RELEASES file is the same as the last build on the server. - var releaseAsset = assets.FirstOrDefault(a => a.Name == "RELEASES"); - - //if we don't have a RELEASES asset then the previous release likely wasn't a Squirrel one. - if (releaseAsset == null) return; - - write($"Last GitHub release was {lastRelease.Name}."); - - bool requireDownload = false; - - if (!File.Exists(Path.Combine(ReleasesFolder, nupkgDistroFilename(lastRelease.Name)))) - { - write("Last version's package not found locally.", ConsoleColor.Red); - requireDownload = true; - } - else - { - var lastReleases = new RawFileWebRequest($"{GitHubApiEndpoint}/assets/{releaseAsset.Id}"); - lastReleases.AuthenticatedBlockingPerform(); - if (File.ReadAllText(Path.Combine(ReleasesFolder, "RELEASES")) != lastReleases.ResponseString) - { - write("Server's RELEASES differed from ours.", ConsoleColor.Red); - requireDownload = true; - } - } - - if (!requireDownload) return; - - write("Refreshing local releases directory..."); - refreshDirectory(ReleasesFolder); - - foreach (var a in assets) - { - if (a.Name.EndsWith(".exe")) continue; - - write($"- Downloading {a.Name}...", ConsoleColor.Yellow); - new FileWebRequest(Path.Combine(ReleasesFolder, a.Name), $"{GitHubApiEndpoint}/assets/{a.Id}").AuthenticatedBlockingPerform(); - } - } - - private static void refreshDirectory(string directory) - { - if (Directory.Exists(directory)) - Directory.Delete(directory, true); - Directory.CreateDirectory(directory); - } - - private static void updateCsprojVersion(string version) - { - var toUpdate = new[] { "", "" }; - string file = Path.Combine(ProjectName, $"{ProjectName}.csproj"); - - var l1 = File.ReadAllLines(file); - List l2 = new List(); - foreach (var l in l1) - { - string line = l; - - foreach (var tag in toUpdate) - { - int startIndex = l.IndexOf(tag, StringComparison.InvariantCulture); - if (startIndex == -1) - continue; - startIndex += tag.Length; - - int endIndex = l.IndexOf("<", startIndex, StringComparison.InvariantCulture); - line = $"{l.Substring(0, startIndex)}{version}{l.Substring(endIndex)}"; - } - - l2.Add(line); - } - - File.WriteAllLines(file, l2); - } - - /// - /// Find the base path of the active solution (git checkout location) - /// - private static void findSolutionPath() - { - string path = Path.GetDirectoryName(Environment.CommandLine.Replace("\"", "").Trim()); - - if (string.IsNullOrEmpty(path)) - path = Environment.CurrentDirectory; - - while (!File.Exists(Path.Combine(path, $"{SolutionName}.sln"))) - path = path.Remove(path.LastIndexOf(Path.DirectorySeparatorChar)); - path += Path.DirectorySeparatorChar; - - Environment.CurrentDirectory = path; - } - - private static bool runCommand(string command, string args) - { - var psi = new ProcessStartInfo(command, args) - { - WorkingDirectory = solutionPath, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - WindowStyle = ProcessWindowStyle.Hidden - }; - - Process p = Process.Start(psi); - if (p == null) return false; - - string output = p.StandardOutput.ReadToEnd(); - output += p.StandardError.ReadToEnd(); - - if (p.ExitCode == 0) return true; - - write(output); - error($"Command {command} {args} failed!"); - return false; - } - - private static string readLineMasked() - { - var fg = Console.ForegroundColor; - Console.ForegroundColor = Console.BackgroundColor; - var ret = Console.ReadLine(); - Console.ForegroundColor = fg; - - return ret; - } - - private static void error(string message) - { - Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine($"FATAL ERROR: {message}"); - - 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) - { - Console.ForegroundColor = ConsoleColor.Green; - Console.Write(sw.ElapsedMilliseconds.ToString().PadRight(8)); - } - Console.ForegroundColor = col; - Console.WriteLine(message); - } - - public static void AuthenticatedBlockingPerform(this WebRequest r) - { - r.AddHeader("Authorization", $"token {GitHubAccessToken}"); - r.Perform(); - } - } - - internal class RawFileWebRequest : WebRequest - { - public RawFileWebRequest(string url) : base(url) - { - } - - protected override string Accept => "application/octet-stream"; - } - - internal class ReleaseLine - { - public string Hash; - public string Filename; - public int Filesize; - - public ReleaseLine(string line) - { - var split = line.Split(' '); - Hash = split[0]; - Filename = split[1]; - Filesize = int.Parse(split[2]); - } - - public override string ToString() => $"{Hash} {Filename} {Filesize}"; - } -} diff --git a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj b/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj deleted file mode 100644 index 063fb89918..0000000000 --- a/osu.Desktop.Deploy/osu.Desktop.Deploy.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net471 - Exe - AnyCPU - true - - - - - - - - - - - - \ No newline at end of file diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index a495d7048d..844db4a80f 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -12,6 +12,7 @@ using osu.Framework.Platform; using osu.Game; using OpenTK.Input; using Microsoft.Win32; +using osu.Framework.Platform.Windows; namespace osu.Desktop { @@ -40,7 +41,7 @@ namespace osu.Desktop /// /// A method of accessing an osu-stable install in a controlled fashion. /// - private class StableStorage : DesktopStorage + private class StableStorage : WindowsStorage { protected override string LocateBasePath() { diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index b984c0bbba..26e80b3f48 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -14,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Utils; using OpenTK; using OpenTK.Graphics; @@ -118,7 +118,7 @@ namespace osu.Desktop.Overlays Icon = FontAwesome.fa_check_square; Activated = delegate { - Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}"); + Process.Start($"https://osu.ppy.sh/home/changelog/{version}"); return true; }; } diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 3d64cab84e..0d4efc7eed 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,7 +1,7 @@ - + - net471;netcoreapp2.0 + net471;netcoreapp2.1 WinExe AnyCPU true @@ -20,7 +20,6 @@ osu.Desktop.Program - @@ -30,7 +29,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json index eb80f4474c..2a82d65014 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json @@ -22,7 +22,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Catch.Tests.exe", + "program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Catch.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, @@ -30,12 +30,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Debug, netcoreapp2.0)", + "name": "VisualTests (Debug, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, dotnet)", @@ -43,12 +43,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, netcoreapp2.0)", + "name": "VisualTests (Release, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, dotnet)", diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json index 41ae88f425..6c6d562512 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json @@ -40,7 +40,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Catch.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -56,7 +56,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Catch.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:Configuration=Release", "/p:GenerateFullPaths=true", "/m", @@ -75,7 +75,7 @@ "problemMatcher": [] }, { - "label": "Restore (netcoreapp2.0)", + "label": "Restore (netcoreapp2.1)", "type": "shell", "command": "dotnet", "args": [ diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs index 097750d7e0..34e07170bd 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs @@ -53,10 +53,10 @@ namespace osu.Game.Rulesets.Catch.Tests return beatmap; } - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + protected override Player CreatePlayer(Ruleset ruleset) { - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - return base.CreatePlayer(beatmap, ruleset); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + return base.CreatePlayer(ruleset); } } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs index f239290ed4..5119260c53 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) { Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft + Origin = Anchor.TopLeft }, }; } diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs index 275752523d..e77dd76353 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs @@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableCatchHitObject), typeof(DrawableFruit), typeof(DrawableDroplet), + typeof(BananaShower), typeof(Pulp), }; @@ -53,12 +54,19 @@ namespace osu.Game.Rulesets.Catch.Tests private DrawableFruit createDrawable(int index) { - var fruit = new Fruit - { - StartTime = 1000000000000, - IndexInBeatmap = index, - Scale = 1.5f, - }; + Fruit fruit = index == 5 + ? new BananaShower.Banana + { + StartTime = 1000000000000, + IndexInBeatmap = index, + Scale = 1.5f, + } + : new Fruit + { + StartTime = 1000000000000, + IndexInBeatmap = index, + Scale = 1.5f, + }; return new DrawableFruit(fruit) { 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 3797edde61..93fa2c4d67 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;net471 + netcoreapp2.1;net471 diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index f40ef67dfb..ad500606ed 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -28,7 +28,20 @@ namespace osu.Game.Rulesets.Catch.Beatmaps var endTime = obj as IHasEndTime; if (positionData == null) + { + if (endTime != null) + { + yield return new BananaShower + { + StartTime = obj.StartTime, + Samples = obj.Samples, + Duration = endTime.Duration, + NewCombo = comboData?.NewCombo ?? false + }; + } + yield break; + } if (curveData != null) { @@ -48,19 +61,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps yield break; } - if (endTime != null) - { - yield return new BananaShower - { - StartTime = obj.StartTime, - Samples = obj.Samples, - Duration = endTime.Duration, - NewCombo = comboData?.NewCombo ?? false - }; - - yield break; - } - yield return new Fruit { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 41792b10a4..4603148114 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -18,12 +18,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private Circle border; + private const float drawable_radius = (float)CatchHitObject.OBJECT_RADIUS * radius_adjust; + + /// + /// Because we're adding a border around the fruit, we need to scale down some. + /// + private const float radius_adjust = 1.1f; + public DrawableFruit(Fruit h) : base(h) { Origin = Anchor.Centre; - Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS); + Size = new Vector2(drawable_radius); Masking = false; Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; @@ -44,14 +51,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { Hollow = !HitObject.HyperDash, Type = EdgeEffectType.Glow, - Radius = 4, + Radius = 4 * radius_adjust, Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f) }, - Size = new Vector2(Height * 1.5f), + Size = new Vector2(Height), Anchor = Anchor.Centre, Origin = Anchor.Centre, BorderColour = Color4.White, - BorderThickness = 4f, + BorderThickness = 3f * radius_adjust, Children = new Framework.Graphics.Drawable[] { new Box @@ -82,8 +89,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation) { - const float large_pulp_3 = 13f; - const float distance_from_centre_3 = 0.23f; + const float large_pulp_3 = 8f * radius_adjust; + const float distance_from_centre_3 = 0.15f; const float large_pulp_4 = large_pulp_3 * 0.925f; const float distance_from_centre_4 = distance_from_centre_3 / 0.925f; @@ -106,11 +113,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, AccentColour = AccentColour, Size = new Vector2(small_pulp), - Y = 0.05f, + Y = -0.34f, }, new Pulp { @@ -146,11 +151,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, AccentColour = AccentColour, Size = new Vector2(small_pulp), - Y = 0.1f, + Y = -0.3f, }, new Pulp { @@ -186,11 +189,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, AccentColour = AccentColour, Size = new Vector2(small_pulp), - Y = -0.1f, + Y = -0.33f, }, new Pulp { @@ -220,10 +221,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, AccentColour = AccentColour, Size = new Vector2(small_pulp), + Y = -0.25f, }, new Pulp { @@ -253,16 +253,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { new Pulp { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, AccentColour = AccentColour, Size = new Vector2(small_pulp), - Y = -0.15f + Y = -0.3f }, new Pulp { AccentColour = AccentColour, - Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3), + Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f), + Y = 0.05f, }, } }; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index d17a72a165..250dc8c7f1 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -29,14 +29,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces set { accentColour = value; - - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Radius = 8, - Colour = accentColour.Darken(0.2f).Opacity(0.75f) - }; + if (IsLoaded) updateAccentColour(); } } + + private void updateAccentColour() + { + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = Size.X / 2, + Colour = accentColour.Darken(0.2f).Opacity(0.75f) + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateAccentColour(); + } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs index 070dc19a6f..52763e09af 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + protected override Vector2 PlayfieldArea => new Vector2(0.86f); // matches stable's vertical offset for catcher plate + protected override DrawableHitObject GetVisualRepresentation(CatchHitObject h) { switch (h) diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 181536a91e..d8c7b5130d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatcherArea : Container { - public const float CATCHER_SIZE = 172; + public const float CATCHER_SIZE = 84; protected readonly Catcher MovableCatcher; @@ -99,8 +99,6 @@ namespace osu.Game.Rulesets.Catch.UI public class Catcher : Container, IKeyBindingHandler { - private Texture texture; - private Container caughtFruit; public Container ExplodingFruitTarget; @@ -121,10 +119,8 @@ namespace osu.Game.Rulesets.Catch.UI } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load() { - texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); - Children = new Drawable[] { caughtFruit = new Container @@ -196,13 +192,7 @@ namespace osu.Game.Rulesets.Catch.UI Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); } - private Sprite createCatcherSprite() => new Sprite - { - Size = new Vector2(CATCHER_SIZE), - FillMode = FillMode.Fill, - Texture = texture, - OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly. - }; + private Sprite createCatcherSprite() => new CatcherSprite(); /// /// Add a caught fruit to the catcher's stack. @@ -411,6 +401,23 @@ namespace osu.Game.Rulesets.Catch.UI f.Expire(); } } + + private class CatcherSprite : Sprite + { + public CatcherSprite() + { + Size = new Vector2(CATCHER_SIZE); + + // Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling. + OriginPosition = new Vector2(-0.02f, 0.06f) * CATCHER_SIZE; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); + } + } } } } diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json index fceb403f30..bc41d4ccf9 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json @@ -22,7 +22,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Mania.Tests.exe", + "program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Mania.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, @@ -30,12 +30,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Debug, netcoreapp2.0)", + "name": "VisualTests (Debug, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, dotnet)", @@ -43,12 +43,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, netcoreapp2.0)", + "name": "VisualTests (Release, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, dotnet)", diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json index b04b068b0d..7fc2f7b2ef 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/tasks.json @@ -40,7 +40,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Mania.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -56,7 +56,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Mania.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:Configuration=Release", "/p:GenerateFullPaths=true", "/m", @@ -75,7 +75,7 @@ "problemMatcher": [] }, { - "label": "Restore (netcoreapp2.0)", + "label": "Restore (netcoreapp2.1)", "type": "shell", "command": "dotnet", "args": [ diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs index dff2b2d56a..b064d82a23 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestCaseManiaPlayfield.cs @@ -98,17 +98,12 @@ namespace osu.Game.Rulesets.Mania.Tests }); } - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); - [BackgroundDependencyLoader] private void load(RulesetStore rulesets, SettingsStore settings) { maniaRuleset = rulesets.GetRuleset(3); - dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4)); + Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4)); } private ManiaPlayfield createPlayfield(int cols, bool inverted = false) 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 e90155568e..77504fdc3c 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;net471 + netcoreapp2.1;net471 diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index e6e3028d62..93652f7610 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -105,7 +105,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private double computeAccuracyValue(double strainValue) { - double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future + double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate; if (hitWindowGreat <= 0) return 0; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index 7f3985b26d..256811b4c1 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string Name => "Dual Stages"; public override string ShortenedName => "DS"; public override string Description => @"Double the stages, double the fun!"; - public override double ScoreMultiplier => 0; + public override double ScoreMultiplier => 1; private bool isForCurrentRuleset; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 5af898287a..2f951461c3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods public override string ShortenedName => "RD"; public override FontAwesome Icon => FontAwesome.fa_osu_dice; public override string Description => @"Shuffle around the keys!"; - public override double ScoreMultiplier => 0; + public override double ScoreMultiplier => 1; public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) { diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index 2147c5a761..d0fc6aa3d6 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -5,6 +5,7 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Objects.Drawables; +using OpenTK.Graphics; namespace osu.Game.Rulesets.Mania.Objects.Drawables { @@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : base(barLine) { RelativeSizeAxes = Axes.X; - Height = 1; + Height = 2f; AddInternal(new Box { @@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, + Colour = new Color4(255, 204, 33, 255), }); bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 8791e8ed86..e008fa952e 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -2,7 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Linq; -using osu.Game.Rulesets.Objects.Drawables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using OpenTK.Graphics; @@ -18,12 +18,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableHoldNote : DrawableManiaHitObject, IKeyBindingHandler { + public override bool DisplayJudgement => false; + private readonly DrawableNote head; private readonly DrawableNote tail; - private readonly GlowPiece glowPiece; private readonly BodyPiece bodyPiece; - private readonly Container fullHeightContainer; /// /// Time at which the user started holding this hold note. Null if the user is not holding this hold note. @@ -35,25 +35,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// private bool hasBroken; + private readonly Container tickContainer; + public DrawableHoldNote(HoldNote hitObject, ManiaAction action) : base(hitObject, action) { - Container tickContainer; RelativeSizeAxes = Axes.X; InternalChildren = new Drawable[] { - // The hit object itself cannot be used for various elements because the tail overshoots it - // So a specialized container that is updated to contain the tail height is used - fullHeightContainer = new Container - { - RelativeSizeAxes = Axes.X, - Child = glowPiece = new GlowPiece() - }, bodyPiece = new BodyPiece { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.X, }, tickContainer = new Container @@ -90,21 +82,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { base.AccentColour = value; - glowPiece.AccentColour = value; bodyPiece.AccentColour = value; head.AccentColour = value; tail.AccentColour = value; - } - } - - protected override void UpdateState(ArmedState state) - { - switch (state) - { - case ArmedState.Hit: - // Good enough for now, we just want them to have a lifetime end - this.Delay(2000).Expire(); - break; + tickContainer.ForEach(t => t.AccentColour = value); } } @@ -119,12 +100,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables base.Update(); // Make the body piece not lie under the head note - bodyPiece.Y = head.Height; - bodyPiece.Height = DrawHeight - head.Height; - - // Make the fullHeightContainer "contain" the height of the tail note, keeping in mind - // that the tail note overshoots the height of this hit object - fullHeightContainer.Height = DrawHeight + tail.Height; + bodyPiece.Y = head.Height / 2; + bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2; } public bool OnPressed(ManiaAction action) @@ -173,8 +150,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : base(holdNote.HitObject.Head, action) { this.holdNote = holdNote; - - GlowPiece.Alpha = 0; } public override bool OnPressed(ManiaAction action) @@ -192,11 +167,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables return true; } - - protected override void UpdateState(ArmedState state) - { - // The holdnote keeps scrolling through for now, so having the head disappear looks weird - } } /// @@ -217,8 +187,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables : base(holdNote.HitObject.Tail, action) { this.holdNote = holdNote; - - GlowPiece.Alpha = 0; } protected override void CheckForJudgements(bool userTriggered, double timeOffset) @@ -251,11 +219,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables }); } - protected override void UpdateState(ArmedState state) - { - // The holdnote keeps scrolling through, so having the tail disappear looks weird - } - public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down public override bool OnReleased(ManiaAction action) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 74c17ad0fb..5df6079efa 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Mania.Judgements; -using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Scoring; @@ -87,16 +86,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect }); } - protected override void UpdateState(ArmedState state) - { - switch (State.Value) - { - case ArmedState.Hit: - AccentColour = Color4.Green; - break; - } - } - protected override void Update() { if (AllJudged) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index db1fec40de..fbf1ee8460 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -27,5 +27,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables if (action != null) Action = action.Value; } + + protected override void UpdateState(ArmedState state) + { + switch (state) + { + case ArmedState.Miss: + this.FadeOut(150, Easing.In).Expire(); + break; + case ArmedState.Hit: + this.FadeOut(150, Easing.OutQuint).Expire(); + break; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs index 0340e6bba3..3de0a9c5cc 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -1,12 +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.Extensions.Color4Extensions; using OpenTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Objects.Drawables @@ -16,9 +17,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// public class DrawableNote : DrawableManiaHitObject, IKeyBindingHandler { - protected readonly GlowPiece GlowPiece; - - private readonly LaneGlowPiece laneGlowPiece; private readonly NotePiece headPiece; public DrawableNote(Note hitObject, ManiaAction action) @@ -27,14 +25,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + CornerRadius = 5; + Masking = true; + InternalChildren = new Drawable[] { - laneGlowPiece = new LaneGlowPiece - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - GlowPiece = new GlowPiece(), headPiece = new NotePiece { Anchor = Anchor.TopCentre, @@ -49,9 +44,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables set { base.AccentColour = value; - laneGlowPiece.AccentColour = AccentColour; - GlowPiece.AccentColour = AccentColour; headPiece.AccentColour = AccentColour; + + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = AccentColour.Lighten(1f).Opacity(0.6f), + Radius = 10, + }; } } @@ -71,17 +71,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddJudgement(new ManiaJudgement { Result = result }); } - protected override void UpdateState(ArmedState state) - { - switch (state) - { - case ArmedState.Hit: - case ArmedState.Miss: - this.FadeOut(100).Expire(); - break; - } - } - public virtual bool OnPressed(ManiaAction action) { if (action != Action) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 17644a78a5..4ab2da208a 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -123,8 +123,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces if (!IsLoaded) return; - foreground.Colour = AccentColour.Opacity(0.4f); - background.Colour = AccentColour.Opacity(0.2f); + foreground.Colour = AccentColour.Opacity(0.9f); + background.Colour = AccentColour.Opacity(0.6f); subtractionCache.Invalidate(); } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs index e23b24508b..9ebeb91e0c 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// internal class NotePiece : Container, IHasAccentColour { - private const float head_height = 10; + public const float NOTE_HEIGHT = 10; private const float head_colour_height = 6; private readonly Box colouredBox; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces public NotePiece() { RelativeSizeAxes = Axes.X; - Height = head_height; + Height = NOTE_HEIGHT; Children = new[] { diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 52d514221d..dee113c82f 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Mania.UI public ManiaAction Action; private readonly Box background; + private readonly Box backgroundOverlay; private readonly Container hitTargetBar; private readonly Container keyIcon; @@ -42,22 +43,32 @@ namespace osu.Game.Rulesets.Mania.UI protected override Container Content => content; private readonly Container content; - private const float opacity_released = 0.1f; - private const float opacity_pressed = 0.25f; - public Column() : base(ScrollingDirection.Up) { RelativeSizeAxes = Axes.Y; Width = column_width; + Masking = true; + CornerRadius = 5; + InternalChildren = new Drawable[] { background = new Box { Name = "Background", RelativeSizeAxes = Axes.Both, - Alpha = opacity_released + Alpha = 0.3f + }, + backgroundOverlay = new Box + { + Name = "Background Gradient Overlay", + RelativeSizeAxes = Axes.Both, + Height = 0.5f, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Blending = BlendingMode.Additive, + Alpha = 0 }, new Container { @@ -182,6 +193,7 @@ namespace osu.Game.Rulesets.Mania.UI accentColour = value; background.Colour = accentColour; + backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0)); hitTargetBar.EdgeEffect = new EdgeEffectParameters { @@ -213,7 +225,7 @@ namespace osu.Game.Rulesets.Mania.UI internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { - if (!judgement.IsHit) + if (!judgement.IsHit || !judgedObject.DisplayJudgement) return; explosionContainer.Add(new HitExplosion(judgedObject)); @@ -223,8 +235,8 @@ namespace osu.Game.Rulesets.Mania.UI { if (action == Action) { - background.FadeTo(opacity_pressed, 50, Easing.OutQuint); - keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint); + backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint); + keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint); } return false; @@ -234,8 +246,8 @@ namespace osu.Game.Rulesets.Mania.UI { if (action == Action) { - background.FadeTo(opacity_released, 800, Easing.OutQuart); - keyIcon.ScaleTo(1f, 400, Easing.OutQuart); + backgroundOverlay.FadeTo(0, 250, Easing.OutQuint); + keyIcon.ScaleTo(1f, 125, Easing.OutQuint); } return false; diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index f01dfc0db1..f19c3a811b 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -1,19 +1,21 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK; using OpenTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; namespace osu.Game.Rulesets.Mania.UI { internal class HitExplosion : CompositeDrawable { - private readonly Box inner; + private readonly CircularContainer circle; public HitExplosion(DrawableHitObject judgedObject) { @@ -22,33 +24,32 @@ namespace osu.Game.Rulesets.Mania.UI Anchor = Anchor.TopCentre; Origin = Anchor.Centre; - RelativeSizeAxes = Axes.Both; - Size = new Vector2(isTick ? 0.5f : 1); - FillMode = FillMode.Fit; + RelativeSizeAxes = Axes.X; + Y = NotePiece.NOTE_HEIGHT / 2; + Height = NotePiece.NOTE_HEIGHT; - Blending = BlendingMode.Additive; + // scale roughly in-line with visual appearance of notes + Scale = new Vector2(isTick ? 0.4f : 0.8f); - Color4 accent = isTick ? Color4.White : judgedObject.AccentColour; - - InternalChild = new CircularContainer + InternalChild = circle = new CircularContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Masking = true, - BorderThickness = 1, - BorderColour = accent, + // we want our size to be very small so the glow dominates it. + Size = new Vector2(0.1f), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = accent, - Radius = 10, - Hollow = true + Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour, Color4.White, 0, 1), + Radius = 100, }, - Child = inner = new Box + Child = new Box { + Alpha = 0, RelativeSizeAxes = Axes.Both, - Colour = accent, - Alpha = 1, - AlwaysPresent = true, + AlwaysPresent = true } }; } @@ -57,8 +58,8 @@ namespace osu.Game.Rulesets.Mania.UI { base.LoadComplete(); - this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500); - inner.FadeOut(250); + circle.ResizeTo(circle.Size * new Vector2(4, 20), 1000, Easing.OutQuint); + this.FadeIn(16).Then().FadeOut(500, Easing.OutQuint); Expire(true); } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 904be3a9e3..cb93613c7d 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -9,7 +9,6 @@ using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; @@ -78,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, + CornerRadius = 5, Children = new Drawable[] { new Box @@ -171,6 +171,9 @@ namespace osu.Game.Rulesets.Mania.UI internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) { + if (!judgedObject.DisplayJudgement) + return; + judgements.Clear(); judgements.Add(new DrawableManiaJudgement(judgement, judgedObject) { @@ -180,15 +183,15 @@ namespace osu.Game.Rulesets.Mania.UI } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { normalColumnColours = new List { - colours.RedDark, - colours.GreenDark + new Color4(94, 0, 57, 255), + new Color4(6, 84, 0, 255) }; - specialColumnColour = colours.BlueDark; + specialColumnColour = new Color4(0, 48, 63, 255); // Set the special column + colour + key foreach (var column in Columns) diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json index 714fb6db6f..13aba025fd 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json @@ -22,7 +22,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Osu.Tests.exe", + "program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Osu.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, @@ -30,12 +30,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Debug, netcoreapp2.0)", + "name": "VisualTests (Debug, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, dotnet)", @@ -43,12 +43,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, netcoreapp2.0)", + "name": "VisualTests (Release, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, dotnet)", diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json index 657fe07e1a..62cf51382f 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/tasks.json @@ -40,7 +40,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Osu.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -56,7 +56,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Osu.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:Configuration=Release", "/p:GenerateFullPaths=true", "/m", @@ -75,7 +75,7 @@ "problemMatcher": [] }, { - "label": "Restore (netcoreapp2.0)", + "label": "Restore (netcoreapp2.1)", "type": "shell", "command": "dotnet", "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 1695ceacee..c5d9b26145 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;net471 + netcoreapp2.1;net471 diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index eeb776fa6e..57cf962fa7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -61,20 +61,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(m => !m.Ranked)) return 0; - // Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done - // locally for now as doing so would modify animations and other things unexpectedly - // DO NOT MODIFY THIS - double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate; - if (mods.Any(m => m is OsuModHardRock)) - ar = Math.Min(10, ar * 1.4); - if (mods.Any(m => m is OsuModEasy)) - ar = Math.Max(0, ar / 2); - - double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / TimeRate; - double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future + double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate; + double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate; realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5; - realOverallDifficulty = (80 - 0.5 - hitWindowGreat) / 6; + realOverallDifficulty = (80 - hitWindowGreat) / 6; // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 3573c133c2..b2ddd65e38 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -13,8 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string ShortenedName => "AP"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; public override string Description => @"Automatic cursor movement - just follow the rhythm."; - public override double ScoreMultiplier => 0; - public override bool Ranked => false; + public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) }; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index e72d667c0b..4220b72b16 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -7,33 +7,34 @@ using System.Linq; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects + public class OsuModHidden : ModHidden { public override string Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => 1.06; - private const double fade_in_duration_multiplier = 0.4; private const double fade_out_duration_multiplier = 0.3; - public void ApplyToDrawableHitObjects(IEnumerable drawables) + public override void ApplyToDrawableHitObjects(IEnumerable drawables) { + void adjustFadeIn(OsuHitObject h) => h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier; + foreach (var d in drawables.OfType()) { - d.ApplyCustomUpdateState += ApplyHiddenState; - - d.HitObject.TimeFadein = d.HitObject.TimePreempt * fade_in_duration_multiplier; + adjustFadeIn(d.HitObject); foreach (var h in d.HitObject.NestedHitObjects.OfType()) - h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier; + adjustFadeIn(h); } + + base.ApplyToDrawableHitObjects(drawables); } - protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) + protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state) { if (!(drawable is DrawableOsuHitObject d)) return; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index e7f17dd86b..240d8dc396 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private Bindable cursorScale; private Bindable autoCursorScale; - private Bindable beatmap; + private readonly IBindable beatmap = new Bindable(); public OsuCursor() { @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuGameBase game) + private void load(OsuConfigManager config, IBindableBeatmap beatmap) { Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer { @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor RelativeSizeAxes = Axes.Both, }; - beatmap = game.Beatmap.GetBoundCopy(); + this.beatmap.BindTo(beatmap); beatmap.ValueChanged += v => calculateScale(); cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json index e1df54e99b..df49e177dc 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json @@ -22,7 +22,7 @@ }, "type": "mono", "request": "launch", - "program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Taiko.Tests.exe", + "program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Taiko.Tests.exe", "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, msbuild)", "runtimeExecutable": null, @@ -30,12 +30,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Debug, netcoreapp2.0)", + "name": "VisualTests (Debug, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug, dotnet)", @@ -43,12 +43,12 @@ "console": "internalConsole" }, { - "name": "VisualTests (Release, netcoreapp2.0)", + "name": "VisualTests (Release, netcoreapp2.1)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release, dotnet)", diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json index 8bdbcd8e8e..7c8beed00f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/tasks.json @@ -40,7 +40,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Taiko.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -56,7 +56,7 @@ "build", "--no-restore", "osu.Game.Rulesets.Taiko.Tests.csproj", - "/p:TargetFramework=netcoreapp2.0", + "/p:TargetFramework=netcoreapp2.1", "/p:Configuration=Release", "/p:GenerateFullPaths=true", "/m", @@ -75,7 +75,7 @@ "problemMatcher": [] }, { - "label": "Restore (netcoreapp2.0)", + "label": "Restore (netcoreapp2.1)", "type": "shell", "command": "dotnet", "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 1221584a2b..dea34d25e7 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;net471 + netcoreapp2.1;net471 diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 9c9cd1f0fb..6b1a25d667 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -94,7 +94,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double computeAccuracyValue() { - double hitWindowGreat = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate; + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future + double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate; if (hitWindowGreat <= 0) return 0; diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index f60caf2397..1c9696901c 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -12,6 +12,7 @@ using osu.Framework.Platform; using osu.Game.IPC; using osu.Framework.Allocation; using osu.Game.Beatmaps; +using SharpCompress.Archives.Zip; namespace osu.Game.Tests.Beatmaps.IO { @@ -77,8 +78,69 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); + Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count); + Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public void TestRollbackOnFailure() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure")) + { + try + { + var osu = loadOsu(host); + var manager = osu.Dependencies.Get(); + + int fireCount = 0; + + // ReSharper disable once AccessToModifiedClosure + manager.ItemAdded += _ => fireCount++; + manager.ItemRemoved += _ => fireCount++; + + var imported = loadOszIntoOsu(osu); + + Assert.AreEqual(0, fireCount -= 1); + + imported.Hash += "-changed"; + manager.Update(imported); + + Assert.AreEqual(0, fireCount -= 2); + + var breakTemp = createTemporaryBeatmap(); + + MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 }); + MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp)); + + File.Delete(breakTemp); + + using (var outStream = File.Open(breakTemp, FileMode.CreateNew)) + using (var zip = ZipArchive.Open(brokenOsz)) + { + zip.AddEntry("broken.osu", brokenOsu, false); + zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate); + } + + Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count); + Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); + Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count); + + // this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu. + manager.Import(breakTemp); + + // no events should be fired in the case of a rollback. + Assert.AreEqual(0, fireCount); + + Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count); + Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); + Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count); } finally { @@ -100,18 +162,17 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = loadOszIntoOsu(osu); - //var change = manager.QueryBeatmapSets(_ => true).First(); imported.Hash += "-changed"; manager.Update(imported); var importedSecondTime = loadOszIntoOsu(osu); - // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID != importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); - Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1); - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); + // only one beatmap will exist as the online set ID matched, causing purging of the first import. + Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count); + Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); } finally { @@ -162,8 +223,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); - var temp = prepareTempCopy(osz_path); - Assert.IsTrue(File.Exists(temp)); + var temp = createTemporaryBeatmap(); var importer = new ArchiveImportIPCChannel(client); if (!importer.ImportAsync(temp).Wait(10000)) @@ -188,8 +248,7 @@ namespace osu.Game.Tests.Beatmaps.IO try { var osu = loadOsu(host); - var temp = prepareTempCopy(osz_path); - Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated"); + var temp = createTemporaryBeatmap(); using (File.OpenRead(temp)) osu.Dependencies.Get().Import(temp); ensureLoaded(osu); @@ -203,11 +262,17 @@ namespace osu.Game.Tests.Beatmaps.IO } } - private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu) + private string createTemporaryBeatmap() { - var temp = prepareTempCopy(osz_path); - + var temp = Path.GetTempFileName() + ".osz"; + File.Copy(osz_path, temp, true); Assert.IsTrue(File.Exists(temp)); + return temp; + } + + private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu, string path = null) + { + var temp = path ?? createTemporaryBeatmap(); var manager = osu.Dependencies.Get(); @@ -219,7 +284,7 @@ namespace osu.Game.Tests.Beatmaps.IO waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return imported.FirstOrDefault(); + return imported.LastOrDefault(); } private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) @@ -228,16 +293,10 @@ namespace osu.Game.Tests.Beatmaps.IO manager.Delete(imported); Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0); - Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1); + Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } - private string prepareTempCopy(string path) - { - var temp = Path.GetTempFileName(); - return new FileInfo(path).CopyTo(temp, true).FullName; - } - private OsuGameBase loadOsu(GameHost host) { var osu = new OsuGameBase(); @@ -286,12 +345,12 @@ namespace osu.Game.Tests.Beatmaps.IO private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) { - Action waitAction = () => + Task task = Task.Run(() => { while (!result()) Thread.Sleep(200); - }; + }); - Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage); + Assert.IsTrue(task.Wait(timeout), failureMessage); } } } diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index c6863d1cb5..f9b055ed55 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); Assert.AreEqual("Deif", meta.AuthorString); Assert.AreEqual("machinetop_background.jpg", meta.BackgroundFile); - Assert.AreEqual(164471 + LegacyBeatmapDecoder.UniversalOffset, meta.PreviewTime); + Assert.AreEqual(164471, meta.PreviewTime); Assert.AreEqual(string.Empty, meta.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", meta.Tags); Assert.AreEqual("Renatus", meta.Title); diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs index cecb327b6c..4abfec4371 100644 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ b/osu.Game.Tests/Visual/TestCaseAutoplay.cs @@ -3,8 +3,8 @@ using System.ComponentModel; using System.Linq; -using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; namespace osu.Game.Tests.Visual @@ -12,10 +12,22 @@ namespace osu.Game.Tests.Visual [Description("Player instantiated with an autoplay mod.")] public class TestCaseAutoplay : TestCasePlayer { - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + protected override Player CreatePlayer(Ruleset ruleset) { - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - return base.CreatePlayer(beatmap, ruleset); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + return new ScoreAccessiblePlayer + { + AllowPause = false, + AllowLeadIn = false, + AllowResults = false, + }; + } + + protected override bool ContinueCondition(Player player) => base.ContinueCondition(player) && ((ScoreAccessiblePlayer)player).ScoreProcessor.TotalScore > 0; + + private class ScoreAccessiblePlayer : Player + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; } } } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index f819e882d9..4679fca855 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -65,11 +65,7 @@ namespace osu.Game.Tests.Visual carousel.SelectionChanged = s => currentSelection = s; - AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); - - bool changed = false; - carousel.BeatmapSetsChanged = () => changed = true; - AddUntilStep(() => changed, "Wait for load"); + loadBeatmaps(beatmapSets); testTraversal(); testFiltering(); @@ -84,6 +80,17 @@ namespace osu.Game.Tests.Visual testCarouselRootIsRandom(); } + private void loadBeatmaps(List beatmapSets) + { + bool changed = false; + AddStep($"Load {beatmapSets.Count} Beatmaps", () => + { + carousel.BeatmapSetsChanged = () => changed = true; + carousel.BeatmapSets = beatmapSets; + }); + AddUntilStep(() => changed, "Wait for load"); + } + private void ensureRandomFetchSuccess() => AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); @@ -423,7 +430,7 @@ namespace osu.Game.Tests.Visual for (int i = 1; i <= 50; i++) beatmapSets.Add(createTestBeatmapSet(i)); - AddStep("Load 50 Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); + loadBeatmaps(beatmapSets); advanceSelection(direction: 1, diff: false); checkNonmatchingFilter(); checkNonmatchingFilter(); diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs index 0d3e08154f..f52fecfd02 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -6,7 +6,6 @@ using System.Linq; using NUnit.Framework; using OpenTK; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -29,14 +28,11 @@ namespace osu.Game.Tests.Visual private RulesetStore rulesets; private TestBeatmapInfoWedge infoWedge; private readonly List beatmaps = new List(); - private readonly Bindable beatmap = new Bindable(); [BackgroundDependencyLoader] - private void load(OsuGameBase game, RulesetStore rulesets) + private void load(RulesetStore rulesets) { this.rulesets = rulesets; - - beatmap.BindTo(game.Beatmap); } protected override void LoadComplete() @@ -53,11 +49,11 @@ namespace osu.Game.Tests.Visual AddStep("show", () => { infoWedge.State = Visibility.Visible; - infoWedge.UpdateBeatmap(beatmap); + infoWedge.Beatmap = Beatmap; }); // select part is redundant, but wait for load isn't - selectBeatmap(beatmap.Value.Beatmap); + selectBeatmap(Beatmap.Value.Beatmap); AddWaitStep(3); @@ -120,8 +116,8 @@ namespace osu.Game.Tests.Visual { selectNullBeatmap(); AddAssert("check empty version", () => string.IsNullOrEmpty(infoWedge.Info.VersionLabel.Text)); - AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Title); - AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == beatmap.Default.BeatmapInfo.Metadata.Artist); + AddAssert("check default title", () => infoWedge.Info.TitleLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Title); + AddAssert("check default artist", () => infoWedge.Info.ArtistLabel.Text == Beatmap.Default.BeatmapInfo.Metadata.Artist); AddAssert("check empty author", () => !infoWedge.Info.MapperContainer.Children.Any()); AddAssert("check no infolabels", () => !infoWedge.Info.InfoLabelContainer.Children.Any()); } @@ -133,7 +129,7 @@ namespace osu.Game.Tests.Visual AddStep($"select {b.Metadata.Title} beatmap", () => { infoBefore = infoWedge.Info; - infoWedge.UpdateBeatmap(beatmap.Value = new TestWorkingBeatmap(b)); + infoWedge.Beatmap = Beatmap.Value = new TestWorkingBeatmap(b); }); AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); @@ -143,8 +139,8 @@ namespace osu.Game.Tests.Visual { AddStep("select null beatmap", () => { - beatmap.Value = beatmap.Default; - infoWedge.UpdateBeatmap(beatmap); + Beatmap.Value = Beatmap.Default; + infoWedge.Beatmap = Beatmap; }); } diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 89b1c52010..12a7ee9c12 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -35,9 +35,6 @@ namespace osu.Game.Tests.Visual typeof(MessageFormatter) }; - private DependencyContainer dependencies; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - public TestCaseChatLink() { Add(textContainer = new TestChatLineContainer @@ -53,7 +50,7 @@ namespace osu.Game.Tests.Visual private void load(OsuColour colours) { linkColour = colours.Blue; - dependencies.Cache(new ChatOverlay + Dependencies.Cache(new ChatOverlay { AvailableChannels = { diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs index 977f241f7a..0aa8e9691e 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/TestCaseCursors.cs @@ -19,12 +19,12 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseCursors : ManualInputManagerTestCase { - private readonly CursorOverrideContainer cursorOverrideContainer; + private readonly MenuCursorContainer menuCursorContainer; private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; public TestCaseCursors() { - Child = cursorOverrideContainer = new CursorOverrideContainer + Child = menuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both, Children = new[] @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); AddStep("Move out", moveOut); AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); } /// @@ -112,11 +112,11 @@ namespace osu.Game.Tests.Visual AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); - AddAssert("Check global cursor at mouse", () => checkAtMouse(cursorOverrideContainer.Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); + AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor)); AddStep("Move out", moveOut); AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor)); + AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); } /// diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs index 96a754a5ce..e7bcfbf500 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorCompose.cs @@ -17,14 +17,10 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(Compose) }; [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - - var compose = new Compose(); - compose.Beatmap.BindTo(osuGame.Beatmap); - - Child = compose; + Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + Child = new Compose(); } } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs index a5053bafe8..81b491799b 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using NUnit.Framework; using OpenTK; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; @@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) }; - private readonly ScrollableTimeline timeline; - public TestCaseEditorComposeTimeline() { Children = new Drawable[] @@ -30,7 +27,7 @@ namespace osu.Game.Tests.Visual Origin = Anchor.TopCentre, State = Visibility.Visible }, - timeline = new ScrollableTimeline + new ScrollableTimeline { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -38,11 +35,5 @@ namespace osu.Game.Tests.Visual } }; } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) - { - timeline.Beatmap.BindTo(osuGame.Beatmap); - } } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs index f037d70493..94b99d483c 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { var testBeatmap = new Beatmap { @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual } }; - osuGame.Beatmap.Value = new TestWorkingBeatmap(testBeatmap); + Beatmap.Value = new TestWorkingBeatmap(testBeatmap); Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs index d01c2d2b92..cafd1b6f1a 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -19,19 +19,16 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); - SummaryTimeline summaryTimeline; - Add(summaryTimeline = new SummaryTimeline + Add(new SummaryTimeline { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 50) }); - - summaryTimeline.Beatmap.BindTo(osuGame.Beatmap); } } } diff --git a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs new file mode 100644 index 0000000000..7d8535f428 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs @@ -0,0 +1,23 @@ +// 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 osu.Game.Graphics.UserInterface; +using OpenTK; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseExternalLinkButton : OsuTestCase + { + public override IReadOnlyList RequiredTypes => new[] { typeof(ExternalLinkButton) }; + + public TestCaseExternalLinkButton() + { + Child = new ExternalLinkButton("https://osu.ppy.sh/home") + { + Size = new Vector2(50) + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs index d0c46ecdd7..5df371dd09 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs @@ -32,15 +32,10 @@ namespace osu.Game.Tests.Visual typeof(NotNullAttribute) }; - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(new Beatmap + Beatmap.Value = new TestWorkingBeatmap(new Beatmap { HitObjects = new List { @@ -63,8 +58,8 @@ namespace osu.Game.Tests.Visual }); var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); Child = new OsuHitObjectComposer(new OsuRuleset()); } diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs new file mode 100644 index 0000000000..600784f8db --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs @@ -0,0 +1,115 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using OpenTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseLoaderAnimation : OsuTestCase + { + private TestLoader loader; + + protected override void LoadComplete() + { + base.LoadComplete(); + + // required to preload the logo in a headless run (so it doesn't delay the loading itself). + Add(new OsuLogo()); + + bool logoVisible = false; + AddStep("almost instant display", () => Child = loader = new TestLoader(250)); + AddUntilStep(() => + { + logoVisible = loader.Logo?.Alpha > 0; + return loader.Logo != null && loader.ScreenLoaded; + }, "loaded"); + AddAssert("logo not visible", () => !logoVisible); + + AddStep("short load", () => Child = loader = new TestLoader(800)); + AddUntilStep(() => + { + logoVisible = loader.Logo?.Alpha > 0; + return loader.Logo != null && loader.ScreenLoaded; + }, "loaded"); + AddAssert("logo visible", () => logoVisible); + AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone"); + + AddStep("longer load", () => Child = loader = new TestLoader(1400)); + AddUntilStep(() => + { + logoVisible = loader.Logo?.Alpha > 0; + return loader.Logo != null && loader.ScreenLoaded; + }, "loaded"); + AddAssert("logo visible", () => logoVisible); + AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone"); + } + + private class TestLoader : Loader + { + private readonly double delay; + + public OsuLogo Logo; + private TestScreen screen; + + public bool ScreenLoaded => screen.IsCurrentScreen; + + public TestLoader(double delay) + { + this.delay = delay; + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + Logo = logo; + base.LogoArriving(logo, resuming); + } + + protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen(); + protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(delay); + + private class TestShaderPrecompiler : ShaderPrecompiler + { + private readonly double delay; + private double startTime; + + public TestShaderPrecompiler(double delay) + { + this.delay = delay; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + startTime = Time.Current; + } + + protected override bool AllLoaded => Time.Current > startTime + delay; + } + + private class TestScreen : OsuScreen + { + public TestScreen() + { + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.DarkSlateGray, + Alpha = 0, + }; + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + Child.FadeInFromZero(200); + } + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseLounge.cs b/osu.Game.Tests/Visual/TestCaseLounge.cs new file mode 100644 index 0000000000..b96d705d5c --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseLounge.cs @@ -0,0 +1,211 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Screens.Multi.Components; +using osu.Game.Screens.Multi.Screens.Lounge; +using osu.Game.Users; +using OpenTK.Input; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseLounge : ManualInputManagerTestCase + { + private TestLounge lounge; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + lounge = new TestLounge(); + + Room[] rooms = + { + new Room + { + Name = { Value = @"Just Another Room" }, + Host = { Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } } }, + Status = { Value = new RoomStatusPlaying() }, + Availability = { Value = RoomAvailability.Public }, + Type = { Value = new GameTypeTagTeam() }, + Beatmap = + { + Value = new BeatmapInfo + { + StarDifficulty = 5.65, + Ruleset = rulesets.GetRuleset(0), + Metadata = new BeatmapMetadata + { + Title = @"Sidetracked Day (Short Ver.)", + Artist = @"VINXIS", + AuthorString = @"Hobbes2", + }, + BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh/beatmaps/767600/covers/cover.jpg?1526243446", + }, + }, + }, + } + }, + MaxParticipants = { Value = 10 }, + Participants = + { + Value = new[] + { + new User { Username = @"flyte", Id = 3103765, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 142 } } }, + new User { Username = @"Cookiezi", Id = 124493, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 546 } } }, + new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 287 } } }, + new User { Username = @"Rafis", Id = 2558286, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 468 } } }, + new User { Username = @"hvick225", Id = 50265, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 325 } } }, + new User { Username = @"peppy", Id = 2, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 625 } } }, + } + } + }, + new Room + { + Name = { Value = @"Not Just Any Room" }, + Host = { Value = new User { Username = @"Monstrata", Id = 2706438, Country = new Country { FlagName = @"CA" } } }, + Status = { Value = new RoomStatusOpen() }, + Availability = { Value = RoomAvailability.FriendsOnly }, + Type = { Value = new GameTypeTeamVersus() }, + Beatmap = + { + Value = new BeatmapInfo + { + StarDifficulty = 2.73, + Ruleset = rulesets.GetRuleset(0), + Metadata = new BeatmapMetadata + { + Title = @"lit(var)", + Artist = @"kensuke ushio", + AuthorString = @"Monstrata", + }, + BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh/beatmaps/623972/covers/cover.jpg?1521167183", + }, + }, + }, + } + }, + Participants = + { + Value = new[] + { + new User { Username = @"Jeby", Id = 3136279, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 3497 } } }, + new User { Username = @"DualAkira", Id = 5220933, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 643 } } }, + new User { Username = @"Datenshi Yohane", Id = 7171857, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10555 } } }, + } + } + }, + new Room + { + Name = { Value = @"room THE FINAL" }, + Host = { Value = new User { Username = @"Delis", Id = 1603923, Country = new Country { FlagName = @"JP" } } }, + Status = { Value = new RoomStatusPlaying() }, + Availability = { Value = RoomAvailability.Public }, + Type = { Value = new GameTypeTagTeam() }, + Beatmap = + { + Value = new BeatmapInfo + { + StarDifficulty = 4.48, + Ruleset = rulesets.GetRuleset(3), + Metadata = new BeatmapMetadata + { + Title = @"ONIGIRI FREEWAY", + Artist = @"OISHII", + AuthorString = @"Mentholzzz", + }, + BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers + { + Cover = @"https://assets.ppy.sh/beatmaps/663098/covers/cover.jpg?1521898837", + }, + }, + }, + } + }, + MaxParticipants = { Value = 30 }, + Participants = + { + Value = new[] + { + new User { Username = @"KizuA", Id = 6510442, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 5372 } } }, + new User { Username = @"Colored", Id = 827563, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 810 } } }, + new User { Username = @"Beryl", Id = 3817591, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10096 } } }, + } + } + }, + }; + + AddStep(@"show", () => Add(lounge)); + AddStep(@"set rooms", () => lounge.Rooms = rooms); + selectAssert(0); + AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {}); + AddAssert(@"no room selected", () => lounge.SelectedRoom == null); + AddStep(@"set rooms", () => lounge.Rooms = rooms); + selectAssert(1); + AddStep(@"open room 1", () => clickRoom(1)); + AddStep(@"make lounge current", lounge.MakeCurrent); + filterAssert(@"THE FINAL", LoungeTab.Public, 1); + filterAssert(string.Empty, LoungeTab.Public, 2); + filterAssert(string.Empty, LoungeTab.Private, 1); + filterAssert(string.Empty, LoungeTab.Public, 2); + filterAssert(@"no matches", LoungeTab.Public, 0); + AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {}); + AddStep(@"set rooms", () => lounge.Rooms = rooms); + AddAssert(@"no matches after clear", () => !lounge.ChildRooms.Any()); + filterAssert(string.Empty, LoungeTab.Public, 2); + AddStep(@"exit", lounge.Exit); + } + + private void clickRoom(int n) + { + InputManager.MoveMouseTo(lounge.ChildRooms.ElementAt(n)); + InputManager.Click(MouseButton.Left); + } + + private void selectAssert(int n) + { + AddStep($@"select room {n}", () => clickRoom(n)); + AddAssert($@"room {n} selected", () => lounge.SelectedRoom == lounge.ChildRooms.ElementAt(n).Room); + } + + private void filterAssert(string filter, LoungeTab tab, int endCount) + { + AddStep($@"filter '{filter}', {tab}", () => lounge.SetFilter(filter, tab)); + AddAssert(@"filtered correctly", () => lounge.ChildRooms.Count() == endCount); + } + + private class TestLounge : Lounge + { + public IEnumerable ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter); + public Room SelectedRoom => Inspector.Room; + + public void SetFilter(string filter, LoungeTab tab) + { + Filter.Search.Current.Value = filter; + Filter.Tabs.Current.Value = tab; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/TestCaseMods.cs index d3d21509fd..3255478bea 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/TestCaseMods.cs @@ -39,8 +39,6 @@ namespace osu.Game.Tests.Visual typeof(SpecialSection), }; - private const string unranked_suffix = " (Unranked)"; - private RulesetStore rulesets; private ModDisplay modDisplay; private TestModSelectOverlay modSelect; @@ -121,7 +119,7 @@ namespace osu.Game.Tests.Visual private void testManiaMods(ManiaRuleset ruleset) { - testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom)); + testRankedText(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom)); } private void testSingleMod(Mod mod) @@ -198,13 +196,16 @@ namespace osu.Game.Tests.Visual checkLabelColor(Color4.White); } - private void testMultiplierTextUnranked(Mod mod) + private void testRankedText(Mod mod) { - AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + AddWaitStep(1, "wait for fade"); + AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); selectNext(mod); - AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + AddWaitStep(1, "wait for fade"); + AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0); selectPrevious(mod); - AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix)); + AddWaitStep(1, "wait for fade"); + AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); } private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); @@ -240,6 +241,7 @@ namespace osu.Game.Tests.Visual } public new OsuSpriteText MultiplierLabel => base.MultiplierLabel; + public new OsuSpriteText UnrankedLabel => base.UnrankedLabel; public new TriangleButton DeselectAllButton => base.DeselectAllButton; public new Color4 LowMultiplierColour => base.LowMultiplierColour; diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs index af51a6221f..d27a447077 100644 --- a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs +++ b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Multi; -using osu.Game.Screens.Multi.Screens; +using osu.Game.Screens.Multi.Screens.Lounge; namespace osu.Game.Tests.Visual { @@ -13,14 +13,14 @@ namespace osu.Game.Tests.Visual { public TestCaseMultiHeader() { - Lobby lobby; + Lounge lounge; Children = new Drawable[] { - lobby = new Lobby + lounge = new Lounge { Padding = new MarginPadding { Top = Header.HEIGHT }, }, - new Header(lobby), + new Header(lounge), }; } } diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/TestCaseMusicController.cs index 10c813b2f8..5ba0167f12 100644 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ b/osu.Game.Tests/Visual/TestCaseMusicController.cs @@ -2,12 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Game.Beatmaps; using osu.Game.Overlays; namespace osu.Game.Tests.Visual @@ -15,8 +12,6 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseMusicController : OsuTestCase { - private readonly Bindable beatmapBacking = new Bindable(); - public TestCaseMusicController() { Clock = new FramedClock(); @@ -30,13 +25,7 @@ namespace osu.Game.Tests.Visual AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); AddStep(@"show", () => mc.State = Visibility.Visible); - AddToggleStep(@"toggle beatmap lock", state => beatmapBacking.Disabled = state); - } - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - beatmapBacking.BindTo(game.Beatmap); + AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); } } } diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 8c52360db8..10121738f1 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -27,7 +27,6 @@ namespace osu.Game.Tests.Visual private RulesetStore rulesets; - private DependencyContainer dependencies; private WorkingBeatmap defaultBeatmap; public override IReadOnlyList RequiredTypes => new[] @@ -48,8 +47,6 @@ namespace osu.Game.Tests.Visual typeof(DrawableCarouselBeatmapSet), }; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - private class TestSongSelect : PlaySongSelect { public WorkingBeatmap CurrentBeatmap => Beatmap.Value; @@ -58,7 +55,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load() { TestSongSelect songSelect = null; @@ -67,10 +64,10 @@ namespace osu.Game.Tests.Visual // this is by no means clean. should be replacing inside of OsuGameBase somehow. IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext()); - dependencies.Cache(rulesets = new RulesetStore(factory)); - dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) + Dependencies.Cache(rulesets = new RulesetStore(factory)); + Dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null) { - DefaultBeatmap = defaultBeatmap = game.Beatmap.Default + DefaultBeatmap = defaultBeatmap = Beatmap.Default }); void loadNewSongSelect(bool deleteMaps = false) => AddStep("reload song select", () => @@ -78,7 +75,7 @@ namespace osu.Game.Tests.Visual if (deleteMaps) { manager.Delete(manager.GetAllUsableBeatmapSets()); - game.Beatmap.SetDefault(); + Beatmap.SetDefault(); } if (songSelect != null) diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs index 24ebb534c1..36fb1bcedd 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/TestCasePlaybackControl.cs @@ -15,17 +15,12 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCasePlaybackControl : OsuTestCase { - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - [BackgroundDependencyLoader] private void load() { var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); + Dependencies.CacheAs(clock); var playback = new PlaybackControl { @@ -34,7 +29,7 @@ namespace osu.Game.Tests.Visual Size = new Vector2(200,100) }; - playback.Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); + Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); Child = playback; } diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs index 1e7618232d..52a9db080d 100644 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs @@ -12,9 +12,10 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(OsuGameBase game) { + Beatmap.Value = new DummyWorkingBeatmap(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/TestCaseQuitButton.cs b/osu.Game.Tests/Visual/TestCaseQuitButton.cs index f0f8d41074..a427b7a20a 100644 --- a/osu.Game.Tests/Visual/TestCaseQuitButton.cs +++ b/osu.Game.Tests/Visual/TestCaseQuitButton.cs @@ -31,10 +31,6 @@ namespace osu.Game.Tests.Visual var text = quitButton.Children.OfType().First(); - // initial display - AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); - AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible"); - AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(quitButton)); AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); @@ -44,13 +40,13 @@ namespace osu.Game.Tests.Visual { exitAction = false; InputManager.MoveMouseTo(quitButton); - InputManager.ButtonDown(MouseButton.Left); + InputManager.PressButton(MouseButton.Left); }); - AddStep("Early release", () => InputManager.ButtonUp(MouseButton.Left)); + AddStep("Early release", () => InputManager.ReleaseButton(MouseButton.Left)); AddAssert("action not triggered", () => !exitAction); - AddStep("Trigger exit action", () => InputManager.ButtonDown(MouseButton.Left)); + AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); AddUntilStep(() => exitAction, $"{nameof(quitButton.Action)} was triggered"); } } diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs index 5bc16fe420..1f2d99a7d8 100644 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ b/osu.Game.Tests/Visual/TestCaseReplay.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.Linq; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; @@ -13,23 +12,20 @@ namespace osu.Game.Tests.Visual [Description("Player instantiated with a replay.")] public class TestCaseReplay : TestCasePlayer { - protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) + protected override Player CreatePlayer(Ruleset ruleset) { // We create a dummy RulesetContainer just to get the replay - we don't want to use mods here // to simulate setting a replay rather than having the replay already set for us - beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); - var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(beatmap); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + var dummyRulesetContainer = ruleset.CreateRulesetContainerWith(Beatmap.Value); // We have the replay var replay = dummyRulesetContainer.Replay; // Reset the mods - beatmap.Mods.Value = beatmap.Mods.Value.Where(m => !(m is ModAutoplay)); + Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); - return new ReplayPlayer(replay) - { - InitialBeatmap = beatmap - }; + return new ReplayPlayer(replay); } } } diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/TestCaseResults.cs index 35e1db7c9e..ee36fb0afc 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/TestCaseResults.cs @@ -32,18 +32,13 @@ namespace osu.Game.Tests.Visual this.beatmaps = beatmaps; } - private WorkingBeatmap beatmap; - protected override void LoadComplete() { base.LoadComplete(); - if (beatmap == null) - { - var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); - if (beatmapInfo != null) - beatmap = beatmaps.GetWorkingBeatmap(beatmapInfo); - } + var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); + if (beatmapInfo != null) + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); Add(new Results(new Score { @@ -63,10 +58,7 @@ namespace osu.Game.Tests.Visual { Username = "peppy", } - }) - { - InitialBeatmap = beatmap - }); + })); } } } diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs index 7a743655f4..83bbbfddd1 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs @@ -84,12 +84,9 @@ namespace osu.Game.Tests.Visual private abstract class TestScreen : OsuScreen { - protected abstract string Title { get; } protected abstract string NextTitle { get; } protected abstract TestScreen CreateNextScreen(); - public override string ToString() => Title; - public TestScreen PushNext() { TestScreen screen = CreateNextScreen(); @@ -130,14 +127,14 @@ namespace osu.Game.Tests.Visual private class TestScreenOne : TestScreen { - protected override string Title => @"Screen One"; + public override string Title => @"Screen One"; protected override string NextTitle => @"Two"; protected override TestScreen CreateNextScreen() => new TestScreenTwo(); } private class TestScreenTwo : TestScreen { - protected override string Title => @"Screen Two"; + public override string Title => @"Screen Two"; protected override string NextTitle => @"One"; protected override TestScreen CreateNextScreen() => new TestScreenOne(); } diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs index 5dad48c6d7..c942342168 100644 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ b/osu.Game.Tests/Visual/TestCaseSettings.cs @@ -14,10 +14,6 @@ namespace osu.Game.Tests.Visual private readonly SettingsOverlay settings; private readonly DialogOverlay dialogOverlay; - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - public TestCaseSettings() { settings = new MainSettings @@ -33,7 +29,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - dependencies.Cache(dialogOverlay); + Dependencies.Cache(dialogOverlay); Add(settings); } diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/TestCaseStoryboard.cs index e721c5ced0..b63881ffa7 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/TestCaseStoryboard.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -18,8 +17,6 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseStoryboard : OsuTestCase { - private readonly Bindable beatmapBacking = new Bindable(); - private readonly Container storyboardContainer; private DrawableStoryboard storyboard; @@ -43,6 +40,7 @@ namespace osu.Game.Tests.Visual }, }, }); + Add(new MusicController { Origin = Anchor.TopRight, @@ -55,10 +53,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load() { - beatmapBacking.BindTo(game.Beatmap); - beatmapBacking.ValueChanged += beatmapChanged; + Beatmap.ValueChanged += beatmapChanged; } private void beatmapChanged(WorkingBeatmap working) @@ -66,10 +63,10 @@ namespace osu.Game.Tests.Visual private void restart() { - var track = beatmapBacking.Value.Track; + var track = Beatmap.Value.Track; track.Reset(); - loadStoryboard(beatmapBacking.Value); + loadStoryboard(Beatmap); track.Start(); } @@ -81,7 +78,7 @@ namespace osu.Game.Tests.Visual var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; - storyboard = working.Storyboard.CreateDrawable(beatmapBacking); + storyboard = working.Storyboard.CreateDrawable(Beatmap); storyboard.Passing = false; storyboardContainer.Add(storyboard); diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs index 776adab0d1..b0966cf5c4 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ b/osu.Game.Tests/Visual/TestCaseWaveform.cs @@ -5,11 +5,9 @@ using NUnit.Framework; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Screens.Edit.Screens.Compose.Timeline; @@ -19,9 +17,8 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseWaveform : OsuTestCase { - private readonly Bindable beatmapBacking = new Bindable(); - - public TestCaseWaveform() + [BackgroundDependencyLoader] + private void load() { FillFlowContainer flow; Child = flow = new FillFlowContainer @@ -46,10 +43,11 @@ namespace osu.Game.Tests.Visual var newDisplay = new BeatmapWaveformGraph { RelativeSizeAxes = Axes.Both, - Resolution = 1f / i + Resolution = 1f / i, + Beatmap = Beatmap }; - newDisplay.Beatmap.BindTo(beatmapBacking); + Beatmap.ValueChanged += b => newDisplay.Beatmap = b; flow.Add(new Container { @@ -83,8 +81,5 @@ namespace osu.Game.Tests.Visual }); } } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap); } } diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 057c2c2de1..532915100b 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -2,7 +2,7 @@ WinExe - netcoreapp2.0;net471 + netcoreapp2.1;net471 diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 36fde8a319..806bcc4132 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) { - model.Beatmaps = createBeatmapDifficulties(archive); + model.Beatmaps = createBeatmapDifficulties(model, archive); // remove metadata from difficulties where it matches the set foreach (BeatmapInfo b in model.Beatmaps) @@ -107,6 +107,7 @@ namespace osu.Game.Beatmaps { Delete(existingOnlineId); beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); + Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database); } } @@ -169,7 +170,7 @@ namespace osu.Game.Beatmaps { if (error is OperationCanceledException) return; - downloadNotification.State = ProgressNotificationState.Completed; + downloadNotification.State = ProgressNotificationState.Cancelled; Logger.Error(error, "Beatmap download failed!"); currentDownloads.Remove(request); }; @@ -241,7 +242,13 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList(); + public List GetAllUsableBeatmapSets() => GetAllUsableBeatmapSetsEnumerable().ToList(); + + /// + /// Returns a list of all usable s. + /// + /// A list of available . + public IQueryable GetAllUsableBeatmapSetsEnumerable() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected); /// /// Perform a lookup query on available s. @@ -303,7 +310,7 @@ namespace osu.Game.Beatmaps { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); - if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder."); + if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive."); BeatmapMetadata metadata; using (var stream = new StreamReader(reader.GetStream(mapName))) @@ -321,7 +328,7 @@ namespace osu.Game.Beatmaps /// /// Create all required s for the provided archive. /// - private List createBeatmapDifficulties(ArchiveReader reader) + private List createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader) { var beatmapInfos = new List(); @@ -341,6 +348,14 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash(); beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash(); + // ensure we have the same online set ID as the set itself. + beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID; + beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID; + + // check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence. + if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null) + beatmap.BeatmapInfo.OnlineBeatmapID = null; + RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID); beatmap.BeatmapInfo.Ruleset = ruleset; diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index a70caf0207..8f985306e3 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -36,6 +36,11 @@ namespace osu.Game.Beatmaps /// public bool HasVideo { get; set; } + /// + /// Whether or not this beatmap set has a storyboard. + /// + public bool HasStoryboard { get; set; } + /// /// The different sizes of cover art for this beatmap set. /// diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs new file mode 100644 index 0000000000..34d9cd3d25 --- /dev/null +++ b/osu.Game/Beatmaps/BindableBeatmap.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Diagnostics; +using JetBrains.Annotations; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration; + +namespace osu.Game.Beatmaps +{ + /// + /// A for the beatmap. + /// This should be used sparingly in-favour of . + /// + public abstract class BindableBeatmap : NonNullableBindable, IBindableBeatmap + { + private AudioManager audioManager; + private WorkingBeatmap lastBeatmap; + + protected BindableBeatmap(WorkingBeatmap defaultValue) + : base(defaultValue) + { + } + + /// + /// Registers an for s to be added to. + /// + /// The to register. + protected void RegisterAudioManager([NotNull] AudioManager audioManager) + { + if (this.audioManager != null) throw new InvalidOperationException($"Cannot register multiple {nameof(AudioManager)}s."); + + this.audioManager = audioManager; + + ValueChanged += registerAudioTrack; + + // If the track has changed prior to this being called, let's register it + if (Value != Default) + registerAudioTrack(Value); + } + + private void registerAudioTrack(WorkingBeatmap beatmap) + { + var trackLoaded = lastBeatmap?.TrackLoaded ?? false; + + // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) + if (!trackLoaded || lastBeatmap?.Track != beatmap.Track) + { + if (trackLoaded) + { + Debug.Assert(lastBeatmap != null); + Debug.Assert(lastBeatmap.Track != null); + + lastBeatmap.RecycleTrack(); + } + + audioManager.Track.AddItem(beatmap.Track); + } + + lastBeatmap = beatmap; + } + + [NotNull] + IBindableBeatmap IBindableBeatmap.GetBoundCopy() => GetBoundCopy(); + + /// + /// Retrieve a new instance weakly bound to this . + /// If you are further binding to events of the retrieved , ensure a local reference is held. + /// + [NotNull] + public abstract BindableBeatmap GetBoundCopy(); + } +} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs new file mode 100644 index 0000000000..6acb58e165 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs @@ -0,0 +1,84 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; + +namespace osu.Game.Beatmaps.Drawables +{ + /// + /// A component to allow downloading of a beatmap set. Automatically handles state syncing between other instances. + /// + public class BeatmapSetDownloader : Component + { + private readonly BeatmapSetInfo set; + private readonly bool noVideo; + + private BeatmapManager beatmaps; + + /// + /// Whether the associated beatmap set has been downloading (by this instance or any other instance). + /// + public readonly BindableBool Downloaded = new BindableBool(); + + public BeatmapSetDownloader(BeatmapSetInfo set, bool noVideo = false) + { + this.set = set; + this.noVideo = noVideo; + } + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + + beatmaps.ItemAdded += setAdded; + beatmaps.ItemRemoved += setRemoved; + + // initial value + if (set.OnlineBeatmapSetID != null) + Downloaded.Value = beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmaps != null) + { + beatmaps.ItemAdded -= setAdded; + beatmaps.ItemRemoved -= setRemoved; + } + } + + /// + /// Begin downloading the associated beatmap set. + /// + /// True if downloading began. False if an existing download is active or completed. + public bool Download() + { + if (Downloaded.Value) + return false; + + if (beatmaps.GetExistingDownload(set) != null) + return false; + + beatmaps.Download(set, noVideo); + return true; + } + + private void setAdded(BeatmapSetInfo s) + { + if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) + Downloaded.Value = true; + } + + private void setRemoved(BeatmapSetInfo s) + { + if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) + Downloaded.Value = false; + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs new file mode 100644 index 0000000000..1976db907c --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using OpenTK.Graphics; + +namespace osu.Game.Beatmaps.Drawables +{ + public class UpdateableBeatmapSetCover : Container + { + private Drawable displayedCover; + + private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet + { + get { return beatmapSet; } + set + { + if (value == beatmapSet) return; + beatmapSet = value; + + if (IsLoaded) + updateCover(); + } + } + + private BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover; + public BeatmapSetCoverType CoverType + { + get { return coverType; } + set + { + if (value == coverType) return; + coverType = value; + + if (IsLoaded) + updateCover(); + } + } + + public UpdateableBeatmapSetCover() + { + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateCover(); + } + + private void updateCover() + { + displayedCover?.FadeOut(400); + displayedCover?.Expire(); + displayedCover = null; + + if (beatmapSet != null) + { + Add(displayedCover = new DelayedLoadWrapper( + new BeatmapSetCover(beatmapSet, coverType) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), + }) + ); + } + } + } +} diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 8094abe5ed..ee1fc6aec3 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps { private readonly OsuGameBase game; - public DummyWorkingBeatmap(OsuGameBase game) + public DummyWorkingBeatmap(OsuGameBase game = null) : base(new BeatmapInfo { Metadata = new BeatmapMetadata @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps protected override IBeatmap GetBeatmap() => new Beatmap(); - protected override Texture GetBackground() => game.Textures.Get(@"Backgrounds/bg4"); + protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4"); protected override Track GetTrack() => new TrackVirtual(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 2aee419d20..a83ac26fb3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -8,7 +8,6 @@ using System.Linq; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; -using osu.Framework; namespace osu.Game.Beatmaps.Formats { @@ -28,23 +27,17 @@ namespace osu.Game.Beatmaps.Formats AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); } - /// - /// lazer's audio timings in general doesn't match stable. this is the result of user testing, albeit limited. - /// This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. - /// - public static int UniversalOffset => RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? -22 : 0; - /// /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. /// public bool ApplyOffsets = true; - private readonly int offset = UniversalOffset; + private readonly int offset; public LegacyBeatmapDecoder(int version = LATEST_VERSION) : base(version) { // BeatmapVersion 4 and lower had an incorrect offset (stable has this set as 24ms off) - offset += FormatVersion < 5 ? 24 : 0; + offset = FormatVersion < 5 ? 24 : 0; } protected override void ParseStreamInto(StreamReader stream, Beatmap beatmap) diff --git a/osu.Game/Beatmaps/IBindableBeatmap.cs b/osu.Game/Beatmaps/IBindableBeatmap.cs new file mode 100644 index 0000000000..329c0b6a3c --- /dev/null +++ b/osu.Game/Beatmaps/IBindableBeatmap.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; + +namespace osu.Game.Beatmaps +{ + /// + /// Read-only interface for the beatmap. + /// + public interface IBindableBeatmap : IBindable + { + /// + /// Retrieve a new instance weakly bound to this . + /// If you are further binding to events of the retrieved , ensure a local reference is held. + /// + IBindableBeatmap GetBoundCopy(); + } +} diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index b3082e49de..597960c352 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -82,6 +82,8 @@ namespace osu.Game.Configuration Set(OsuSetting.SpeedChangeVisualisation, SpeedChangeVisualisationMethod.Sequential); + Set(OsuSetting.IncreaseFirstObjectVisibility, true); + // Update Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); @@ -144,6 +146,7 @@ namespace osu.Game.Configuration ScreenshotCaptureMenuCursor, SongSelectRightMouseScroll, BeatmapSkins, - BeatmapHitsounds + BeatmapHitsounds, + IncreaseFirstObjectVisibility } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e04559d547..cbf0df3227 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.EntityFrameworkCore; +using osu.Framework.IO.File; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IO; @@ -56,13 +57,49 @@ namespace osu.Game.Database // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; + private readonly List cachedEvents = new List(); + + /// + /// Allows delaying of outwards events until an operation is confirmed (at a database level). + /// + private bool delayingEvents; + + /// + /// Begin delaying outwards events. + /// + private void delayEvents() => delayingEvents = true; + + /// + /// Flush delayed events and disable delaying. + /// + /// Whether the flushed events should be performed. + private void flushEvents(bool perform) + { + if (perform) + { + foreach (var a in cachedEvents) + a.Invoke(); + } + + cachedEvents.Clear(); + delayingEvents = false; + } + + private void handleEvent(Action a) + { + if (delayingEvents) + cachedEvents.Add(a); + else + a.Invoke(); + } + protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore modelStore, IIpcHost importHost = null) { ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += s => ItemAdded?.Invoke(s); - ModelStore.ItemRemoved += s => ItemRemoved?.Invoke(s); + ModelStore.ItemAdded += s => handleEvent(() => ItemAdded?.Invoke(s)); + ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s)); Files = new FileStore(contextFactory, storage); @@ -138,24 +175,56 @@ namespace osu.Game.Database /// The archive to be imported. public TModel Import(ArchiveReader archive) { - using (ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. + TModel item = null; + delayEvents(); + + try { - // create a new model (don't yet add to database) - var item = CreateModel(archive); + using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. + { + try + { + if (!write.IsTransactionLeader) throw new InvalidOperationException($"Ensure there is no parent transaction so errors can correctly be handled by {this}"); - var existing = CheckForExisting(item); + // create a new model (don't yet add to database) + item = CreateModel(archive); - if (existing != null) return existing; + var existing = CheckForExisting(item); - item.Files = createFileInfos(archive, Files); + if (existing != null) + { + Logger.Log($"Found existing {typeof(TModel)} for {archive.Name} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); + return existing; + } - Populate(item, archive); + item.Files = createFileInfos(archive, Files); - // import to store - ModelStore.Add(item); + Populate(item, archive); - return item; + // import to store + ModelStore.Add(item); + } + catch (Exception e) + { + write.Errors.Add(e); + throw; + } + } + + Logger.Log($"Import of {archive.Name} successfully completed!", LoggingTarget.Database); } + catch (Exception e) + { + Logger.Error(e, $"Import of {archive.Name} failed and has been rolled back.", LoggingTarget.Database); + item = null; + } + finally + { + // we only want to flush events after we've confirmed the write context didn't have any errors. + flushEvents(item != null); + } + + return item; } /// @@ -178,12 +247,8 @@ namespace osu.Game.Database /// The item to delete. public void Delete(TModel item) { - using (var usage = ContextFactory.GetForWrite()) + using (ContextFactory.GetForWrite()) { - var context = usage.Context; - - context.ChangeTracker.AutoDetectChangesEnabled = false; - // re-fetch the model on the import context. var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == item.ID); @@ -191,8 +256,6 @@ namespace osu.Game.Database if (ModelStore.Delete(foundModel)) Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); - - context.ChangeTracker.AutoDetectChangesEnabled = true; } } @@ -302,7 +365,7 @@ namespace osu.Game.Database using (Stream s = reader.GetStream(file)) fileInfos.Add(new TFileModel { - Filename = file, + Filename = FileSafety.PathSanitise(file), FileInfo = files.Add(s) }); diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 71960303b5..5160239c38 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -1,7 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using System.Linq; using System.Threading; +using Microsoft.EntityFrameworkCore.Storage; using osu.Framework.Platform; namespace osu.Game.Database @@ -17,8 +20,12 @@ namespace osu.Game.Database private readonly object writeLock = new object(); private bool currentWriteDidWrite; + private bool currentWriteDidError; + private int currentWriteUsages; + private IDbContextTransaction currentWriteTransaction; + public DatabaseContextFactory(GameHost host) { this.host = host; @@ -35,14 +42,41 @@ namespace osu.Game.Database /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). /// This method may block if a write is already active on a different thread. /// + /// Whether to start a transaction for this write. /// A usage containing a usable context. - public DatabaseWriteUsage GetForWrite() + public DatabaseWriteUsage GetForWrite(bool withTransaction = true) { Monitor.Enter(writeLock); + OsuDbContext context; + + try + { + if (currentWriteTransaction == null && withTransaction) + { + // this mitigates the fact that changes on tracked entities will not be rolled back with the transaction by ensuring write operations are always executed in isolated contexts. + // if this results in sub-optimal efficiency, we may need to look into removing Database-level transactions in favour of running SaveChanges where we currently commit the transaction. + if (threadContexts.IsValueCreated) + recycleThreadContexts(); + + context = threadContexts.Value; + currentWriteTransaction = context.Database.BeginTransaction(); + } + else + { + // we want to try-catch the retrieval of the context because it could throw an error (in CreateContext). + context = threadContexts.Value; + } + } + catch (Exception e) + { + // retrieval of a context could trigger a fatal error. + Monitor.Exit(writeLock); + throw; + } Interlocked.Increment(ref currentWriteUsages); - return new DatabaseWriteUsage(threadContexts.Value, usageCompleted); + return new DatabaseWriteUsage(context, usageCompleted) { IsTransactionLeader = currentWriteTransaction != null && currentWriteUsages == 1 }; } private void usageCompleted(DatabaseWriteUsage usage) @@ -52,18 +86,27 @@ namespace osu.Game.Database try { currentWriteDidWrite |= usage.PerformedWrite; + currentWriteDidError |= usage.Errors.Any(); - if (usages > 0) return; - - if (currentWriteDidWrite) + if (usages == 0) { - // explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged). - usage.Context.Dispose(); + if (currentWriteDidError) + currentWriteTransaction?.Rollback(); + else + currentWriteTransaction?.Commit(); + if (currentWriteDidWrite || currentWriteDidError) + { + // explicitly dispose to ensure any outstanding flushes happen as soon as possible (and underlying resources are purged). + usage.Context.Dispose(); + + // once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches. + recycleThreadContexts(); + } + + currentWriteTransaction = null; currentWriteDidWrite = false; - - // once all writes are complete, we want to refresh thread-specific contexts to make sure they don't have stale local caches. - recycleThreadContexts(); + currentWriteDidError = false; } } finally @@ -74,19 +117,18 @@ namespace osu.Game.Database private void recycleThreadContexts() => threadContexts = new ThreadLocal(CreateContext); - protected virtual OsuDbContext CreateContext() + protected virtual OsuDbContext CreateContext() => new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)) { - var ctx = new OsuDbContext(host.Storage.GetDatabaseConnectionString(database_name)); - ctx.Database.AutoTransactionsEnabled = false; - - return ctx; - } + Database = { AutoTransactionsEnabled = false } + }; public void ResetDatabase() { lock (writeLock) { recycleThreadContexts(); + GC.Collect(); + GC.WaitForPendingFinalizers(); host.Storage.DeleteDatabase(database_name); } } diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs index 7858c1a0d1..64ab24e824 100644 --- a/osu.Game/Database/DatabaseWriteUsage.cs +++ b/osu.Game/Database/DatabaseWriteUsage.cs @@ -2,34 +2,50 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using Microsoft.EntityFrameworkCore.Storage; +using System.Collections.Generic; namespace osu.Game.Database { public class DatabaseWriteUsage : IDisposable { public readonly OsuDbContext Context; - private readonly IDbContextTransaction transaction; private readonly Action usageCompleted; public DatabaseWriteUsage(OsuDbContext context, Action onCompleted) { Context = context; - transaction = Context.BeginTransaction(); usageCompleted = onCompleted; } public bool PerformedWrite { get; private set; } private bool isDisposed; + public List Errors = new List(); + + /// + /// Whether this write usage will commit a transaction on completion. + /// If false, there is a parent usage responsible for transaction commit. + /// + public bool IsTransactionLeader = false; protected void Dispose(bool disposing) { if (isDisposed) return; isDisposed = true; - PerformedWrite |= Context.SaveChanges(transaction) > 0; - usageCompleted?.Invoke(this); + try + { + PerformedWrite |= Context.SaveChanges() > 0; + } + catch (Exception e) + { + Errors.Add(e); + throw; + } + finally + { + usageCompleted?.Invoke(this); + } } public void Dispose() diff --git a/osu.Game/Database/IDatabaseContextFactory.cs b/osu.Game/Database/IDatabaseContextFactory.cs index 372e1770e4..d38d15b252 100644 --- a/osu.Game/Database/IDatabaseContextFactory.cs +++ b/osu.Game/Database/IDatabaseContextFactory.cs @@ -14,7 +14,8 @@ namespace osu.Game.Database /// Request a context for write usage. Can be consumed in a nested fashion (and will return the same underlying context). /// This method may block if a write is already active on a different thread. /// + /// Whether to start a transaction for this write. /// A usage containing a usable context. - DatabaseWriteUsage GetForWrite(); + DatabaseWriteUsage GetForWrite(bool withTransaction = true); } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 8569d81f01..69a1f57cc4 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -50,11 +50,10 @@ namespace osu.Game.Database /// The item to update. public void Update(T item) { - ItemRemoved?.Invoke(item); - using (var usage = ContextFactory.GetForWrite()) usage.Context.Update(item); + ItemRemoved?.Invoke(item); ItemAdded?.Invoke(item); } diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 1979ce3648..4b0de57c4c 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -3,7 +3,6 @@ using System; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.Extensions.Logging; using osu.Framework.Logging; @@ -59,11 +58,20 @@ namespace osu.Game.Database this.connectionString = connectionString; var connection = Database.GetDbConnection(); - connection.Open(); - using (var cmd = connection.CreateCommand()) + try { - cmd.CommandText = "PRAGMA journal_mode=WAL;"; - cmd.ExecuteNonQuery(); + connection.Open(); + + using (var cmd = connection.CreateCommand()) + { + cmd.CommandText = "PRAGMA journal_mode=WAL;"; + cmd.ExecuteNonQuery(); + } + } + catch (Exception e) + { + connection.Close(); + throw; } } @@ -83,8 +91,8 @@ namespace osu.Game.Database base.OnModelCreating(modelBuilder); modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapID).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.MD5Hash).IsUnique(); - modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.MD5Hash); + modelBuilder.Entity().HasIndex(b => b.Hash); modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapSetID).IsUnique(); modelBuilder.Entity().HasIndex(b => b.DeletePending); @@ -104,19 +112,6 @@ namespace osu.Game.Database modelBuilder.Entity().HasOne(b => b.BaseDifficulty); } - public IDbContextTransaction BeginTransaction() - { - // return Database.BeginTransaction(); - return null; - } - - public int SaveChanges(IDbContextTransaction transaction = null) - { - var ret = base.SaveChanges(); - if (ret > 0) transaction?.Commit(); - return ret; - } - private class OsuDbLoggerFactory : ILoggerFactory { #region Disposal diff --git a/osu.Game/Database/SingletonContextFactory.cs b/osu.Game/Database/SingletonContextFactory.cs index 74951e8433..ce3fbf6881 100644 --- a/osu.Game/Database/SingletonContextFactory.cs +++ b/osu.Game/Database/SingletonContextFactory.cs @@ -14,6 +14,6 @@ namespace osu.Game.Database public OsuDbContext Get() => context; - public DatabaseWriteUsage GetForWrite() => new DatabaseWriteUsage(context, null); + public DatabaseWriteUsage GetForWrite(bool withTransaction = true) => new DatabaseWriteUsage(context, null); } } diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index bf16af4706..f0d49af988 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Containers { public class BeatSyncedContainer : Container { - protected readonly Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); private int lastBeat; private TimingControlPoint lastTimingPoint; @@ -74,9 +74,9 @@ namespace osu.Game.Graphics.Containers } [BackgroundDependencyLoader] - private void load(OsuGameBase game) + private void load(IBindableBeatmap beatmap) { - Beatmap.BindTo(game.Beatmap); + Beatmap.BindTo(beatmap); } protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index 11a2034a8f..0186a170c9 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using OpenTK; using osu.Framework.Configuration; +using osu.Game.Overlays; namespace osu.Game.Graphics.Containers { @@ -16,13 +17,13 @@ namespace osu.Game.Graphics.Containers private SampleChannel samplePopIn; private SampleChannel samplePopOut; - private readonly BindableBool allowOpeningOverlays = new BindableBool(true); + protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); [BackgroundDependencyLoader(true)] private void load(OsuGame osuGame, AudioManager audio) { if (osuGame != null) - allowOpeningOverlays.BindTo(osuGame.AllowOpeningOverlays); + OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in"); samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out"); @@ -52,20 +53,18 @@ namespace osu.Game.Graphics.Containers private void onStateChanged(Visibility visibility) { - if (allowOpeningOverlays) + switch (visibility) { - switch (visibility) - { - case Visibility.Visible: + case Visibility.Visible: + if (OverlayActivationMode != OverlayActivation.Disabled) samplePopIn?.Play(); - break; - case Visibility.Hidden: - samplePopOut?.Play(); - break; - } + else + State = Visibility.Hidden; + break; + case Visibility.Hidden: + samplePopOut?.Play(); + break; } - else - State = Visibility.Hidden; } } } diff --git a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs similarity index 94% rename from osu.Game/Graphics/Cursor/CursorOverrideContainer.cs rename to osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 1e56cb6052..5823fad93a 100644 --- a/osu.Game/Graphics/Cursor/CursorOverrideContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.Cursor /// /// A container which provides a which can be overridden by hovered s. /// - public class CursorOverrideContainer : Container, IProvideCursor + public class MenuCursorContainer : Container, IProvideCursor { protected override Container Content => content; private readonly Container content; @@ -25,7 +25,7 @@ namespace osu.Game.Graphics.Cursor public CursorContainer Cursor { get; } public bool ProvidingUserCursor => true; - public CursorOverrideContainer() + public MenuCursorContainer() { AddRangeInternal(new Drawable[] { diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs new file mode 100644 index 0000000000..77079894cc --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; +using OpenTK; +using OpenTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public class ExternalLinkButton : CompositeDrawable, IHasTooltip + { + public string Link { get; set; } + + private Color4 hoverColour; + + public ExternalLinkButton(string link = null) + { + Link = link; + Size = new Vector2(12); + InternalChild = new SpriteIcon + { + Icon = FontAwesome.fa_external_link, + RelativeSizeAxes = Axes.Both + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + } + + protected override bool OnHover(InputState state) + { + InternalChild.FadeColour(hoverColour, 500, Easing.OutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + InternalChild.FadeColour(Color4.White, 500, Easing.OutQuint); + base.OnHoverLost(state); + } + + protected override bool OnClick(InputState state) + { + if(Link != null) + Process.Start(new ProcessStartInfo + { + FileName = Link, + UseShellExecute = true //see https://github.com/dotnet/corefx/issues/10361 + }); + return true; + } + + public string TooltipText => "View in browser"; + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index fc14a9c6ba..d015a563f6 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -6,6 +6,7 @@ using System.Linq; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -157,7 +158,7 @@ namespace osu.Game.Graphics.UserInterface Margin = new MarginPadding { Top = 5, Bottom = 5 }, Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, - Text = (value as Enum)?.GetDescription() ?? value.ToString(), + Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetDescription() ?? value.ToString(), TextSize = 14, Font = @"Exo2.0-Bold", // Font should only turn bold when active? }, diff --git a/osu.Game/IO/Archives/LegacyFilesystemReader.cs b/osu.Game/IO/Archives/LegacyFilesystemReader.cs index 7871f1439a..dcaaf41c77 100644 --- a/osu.Game/IO/Archives/LegacyFilesystemReader.cs +++ b/osu.Game/IO/Archives/LegacyFilesystemReader.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using osu.Framework.IO.File; namespace osu.Game.IO.Archives { @@ -15,19 +14,20 @@ namespace osu.Game.IO.Archives { private readonly string path; - public LegacyFilesystemReader(string path) : base(Path.GetFileName(path)) + public LegacyFilesystemReader(string path) + : base(Path.GetFileName(path)) { - this.path = path; + // re-get full path to standardise with Directory.GetFiles return values below. + this.path = Path.GetFullPath(path); } public override Stream GetStream(string name) => File.OpenRead(Path.Combine(path, name)); public override void Dispose() { - // no-op } - public override IEnumerable Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => FileSafety.GetRelativePath(f, path)).ToArray(); + public override IEnumerable Filenames => Directory.GetFiles(path, "*", SearchOption.AllDirectories).Select(f => f.Replace(path, string.Empty).Trim(Path.DirectorySeparatorChar)).ToArray(); public override Stream GetUnderlyingStream() => null; } diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs new file mode 100644 index 0000000000..f28408bfb3 --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.Designer.cs @@ -0,0 +1,377 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180529055154_RemoveUniqueHashConstraints")] + partial class RemoveUniqueHashConstraints + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash"); + + b.HasIndex("MD5Hash"); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs new file mode 100644 index 0000000000..fe8c983a3f --- /dev/null +++ b/osu.Game/Migrations/20180529055154_RemoveUniqueHashConstraints.cs @@ -0,0 +1,53 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class RemoveUniqueHashConstraints : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo"); + + migrationBuilder.DropIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo"); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_Hash", + table: "BeatmapInfo", + column: "Hash", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_BeatmapInfo_MD5Hash", + table: "BeatmapInfo", + column: "MD5Hash", + unique: true); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 2abbe7785f..d750d50b5b 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -1,7 +1,11 @@ // using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; using osu.Game.Database; +using System; namespace osu.Game.Migrations { @@ -12,7 +16,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + .HasAnnotation("ProductVersion", "2.0.3-rtm-10026"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -27,9 +31,9 @@ namespace osu.Game.Migrations b.Property("OverallDifficulty"); - b.Property("SliderMultiplier"); + b.Property("SliderMultiplier"); - b.Property("SliderTickRate"); + b.Property("SliderTickRate"); b.HasKey("ID"); @@ -91,11 +95,9 @@ namespace osu.Game.Migrations b.HasIndex("BeatmapSetInfoID"); - b.HasIndex("Hash") - .IsUnique(); + b.HasIndex("Hash"); - b.HasIndex("MD5Hash") - .IsUnique(); + b.HasIndex("MD5Hash"); b.HasIndex("MetadataID"); diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 9af142b9e8..dfd181b98a 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -73,6 +73,7 @@ namespace osu.Game.Online.API throw new TimeoutException(@"API request timeout hit"); WebRequest = CreateWebRequest(); + WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); diff --git a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs index 2661303652..44c1216959 100644 --- a/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/APIResponseBeatmapSet.cs @@ -30,6 +30,9 @@ namespace osu.Game.Online.API.Requests [JsonProperty(@"video")] private bool hasVideo { get; set; } + [JsonProperty(@"storyboard")] + private bool hasStoryboard { get; set; } + [JsonProperty(@"status")] private BeatmapSetOnlineStatus status { get; set; } @@ -65,6 +68,7 @@ namespace osu.Game.Online.API.Requests BPM = bpm, Status = status, HasVideo = hasVideo, + HasStoryboard = hasStoryboard, Submitted = submitted, Ranked = ranked, LastUpdated = lastUpdated, diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index f1c23e9e84..b076afbcdb 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using osu.Framework.Configuration; using osu.Game.Beatmaps; using osu.Game.Users; @@ -12,9 +13,10 @@ namespace osu.Game.Online.Multiplayer public Bindable Name = new Bindable(); public Bindable Host = new Bindable(); public Bindable Status = new Bindable(); + public Bindable Availability = new Bindable(); public Bindable Type = new Bindable(); public Bindable Beatmap = new Bindable(); public Bindable MaxParticipants = new Bindable(); - public Bindable Participants = new Bindable(); + public Bindable> Participants = new Bindable>(); } } diff --git a/osu.Game/Online/Multiplayer/RoomAvailability.cs b/osu.Game/Online/Multiplayer/RoomAvailability.cs new file mode 100644 index 0000000000..6c154207ff --- /dev/null +++ b/osu.Game/Online/Multiplayer/RoomAvailability.cs @@ -0,0 +1,18 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Online.Multiplayer +{ + public enum RoomAvailability + { + Public, + + [Description(@"Friends Only")] + FriendsOnly, + + [Description(@"Invite Only")] + InviteOnly, + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ec93b1ae46..36c76851c6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -77,8 +77,7 @@ namespace osu.Game public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; - public readonly BindableBool HideOverlaysOnEnter = new BindableBool(); - public readonly BindableBool AllowOpeningOverlays = new BindableBool(true); + public readonly Bindable OverlayActivationMode = new Bindable(); private OsuScreen screenStack; @@ -94,6 +93,8 @@ namespace osu.Game private SettingsOverlay settings; + private readonly List overlays = new List(); + // todo: move this to SongSelect once Screen has the ability to unsuspend. public readonly Bindable> SelectedMods = new Bindable>(new List()); @@ -106,6 +107,17 @@ namespace osu.Game public void ToggleDirect() => direct.ToggleVisibility(); + /// + /// Close all game-wide overlays. + /// + /// Whether the toolbar should also be hidden. + public void CloseAllOverlays(bool toolbar = true) + { + foreach (var o in overlays) + o.State = Visibility.Hidden; + if (toolbar) Toolbar.State = Visibility.Hidden; + } + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => @@ -212,7 +224,7 @@ namespace osu.Game protected override void LoadComplete() { - // this needs to be cached before base.LoadComplete as it is used by CursorOverrideContainer. + // this needs to be cached before base.LoadComplete as it is used by MenuCursorContainer. dependencies.Cache(screenshotManager = new ScreenshotManager()); base.LoadComplete(); @@ -220,7 +232,7 @@ namespace osu.Game // The next time this is updated is in UpdateAfterChildren, which occurs too late and results // in the cursor being shown for a few frames during the intro. // This prevents the cursor from showing until we have a screen with CursorVisible = true - CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; // hook up notifications to components. SkinManager.PostNotification = n => notifications?.Post(n); @@ -251,7 +263,7 @@ namespace osu.Game Depth = -5, OnHome = delegate { - hideAllOverlays(); + CloseAllOverlays(false); intro?.ChildScreen?.MakeCurrent(); }, }, overlayContent.Add); @@ -308,6 +320,8 @@ namespace osu.Game // ensure only one of these overlays are open at once. var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct }; + overlays.AddRange(singleDisplayOverlays); + foreach (var overlay in singleDisplayOverlays) { overlay.StateChanged += state => @@ -323,6 +337,8 @@ namespace osu.Game } var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications }; + overlays.AddRange(singleDisplaySideOverlays); + foreach (var overlay in singleDisplaySideOverlays) { overlay.StateChanged += state => @@ -339,6 +355,8 @@ namespace osu.Game // eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time. var informationalOverlays = new OverlayContainer[] { beatmapSetOverlay, userProfile }; + overlays.AddRange(informationalOverlays); + foreach (var overlay in informationalOverlays) { overlay.StateChanged += state => @@ -353,6 +371,11 @@ namespace osu.Game }; } + OverlayActivationMode.ValueChanged += v => + { + if (v != OverlayActivation.All) CloseAllOverlays(); + }; + void updateScreenOffset() { float offset = 0; @@ -367,21 +390,6 @@ namespace osu.Game settings.StateChanged += _ => updateScreenOffset(); notifications.StateChanged += _ => updateScreenOffset(); - - notifications.Enabled.BindTo(AllowOpeningOverlays); - - HideOverlaysOnEnter.ValueChanged += hide => - { - //central game screen change logic. - if (hide) - { - hideAllOverlays(); - musicController.State = Visibility.Hidden; - Toolbar.State = Visibility.Hidden; - } - else - Toolbar.State = Visibility.Visible; - }; } private void forwardLoggedErrorsToNotifications() @@ -498,16 +506,6 @@ namespace osu.Game private OsuScreen currentScreen; private FrameworkConfigManager frameworkConfig; - private void hideAllOverlays() - { - settings.State = Visibility.Hidden; - chat.State = Visibility.Hidden; - direct.State = Visibility.Hidden; - social.State = Visibility.Hidden; - userProfile.State = Visibility.Hidden; - notifications.State = Visibility.Hidden; - } - protected override bool OnExiting() { if (screenStack.ChildScreen == null) return false; @@ -548,7 +546,7 @@ namespace osu.Game mainContent.Padding = new MarginPadding { Top = ToolbarOffset }; - CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; } private void screenAdded(Screen newScreen) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 487cb50c9a..b968d7d4d0 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -3,14 +3,12 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Configuration; -using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.IO.Stores; @@ -31,6 +29,7 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using DebugUtils = osu.Game.Utils.DebugUtils; namespace osu.Game { @@ -57,15 +56,14 @@ namespace osu.Game protected SettingsStore SettingsStore; - protected CursorOverrideContainer CursorOverrideContainer; - - protected override string MainResourceFile => @"osu.Game.Resources.dll"; + protected MenuCursorContainer MenuCursorContainer; private Container content; protected override Container Content => content; - public Bindable Beatmap { get; private set; } + private OsuBindableBeatmap beatmap; + protected BindableBeatmap Beatmap => beatmap; private Bindable fpsDisplayVisible; @@ -100,6 +98,8 @@ namespace osu.Game [BackgroundDependencyLoader] private void load() { + Resources.AddStore(new DllResourceStore(@"osu.Game.Resources.dll")); + dependencies.Cache(contextFactory = new DatabaseContextFactory(Host)); dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures")))); @@ -157,33 +157,15 @@ namespace osu.Game Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Venera-Light")); var defaultBeatmap = new DummyWorkingBeatmap(this); - Beatmap = new NonNullableBindable(defaultBeatmap); + beatmap = new OsuBindableBeatmap(defaultBeatmap, Audio); BeatmapManager.DefaultBeatmap = defaultBeatmap; // tracks play so loud our samples can't keep up. // this adds a global reduction of track volume for the time being. Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); - Beatmap.ValueChanged += b => - { - var trackLoaded = lastBeatmap?.TrackLoaded ?? false; - - // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) - if (!trackLoaded || lastBeatmap?.Track != b.Track) - { - if (trackLoaded) - { - Debug.Assert(lastBeatmap != null); - Debug.Assert(lastBeatmap.Track != null); - - lastBeatmap.RecycleTrack(); - } - - Audio.Track.AddItem(b.Track); - } - - lastBeatmap = b; - }; + dependencies.CacheAs(beatmap); + dependencies.CacheAs(beatmap); FileStore.Cleanup(); @@ -191,42 +173,19 @@ namespace osu.Game GlobalActionContainer globalBinding; - CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; - CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this) + MenuCursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; + MenuCursorContainer.Child = globalBinding = new GlobalActionContainer(this) { RelativeSizeAxes = Axes.Both, - Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } + Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } }; - base.Content.Add(new DrawSizePreservingFillContainer { Child = CursorOverrideContainer }); + base.Content.Add(new DrawSizePreservingFillContainer { Child = MenuCursorContainer }); KeyBindingStore.Register(globalBinding); dependencies.Cache(globalBinding); } - private void runMigrations() - { - try - { - using (var db = contextFactory.GetForWrite()) - db.Context.Migrate(); - } - catch (MigrationFailedException e) - { - Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); - - // if we failed, let's delete the database and start fresh. - // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. - contextFactory.ResetDatabase(); - Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); - - using (var db = contextFactory.GetForWrite()) - db.Context.Migrate(); - } - } - - private WorkingBeatmap lastBeatmap; - protected override void LoadComplete() { base.LoadComplete(); @@ -238,6 +197,29 @@ namespace osu.Game fpsDisplayVisible.TriggerChange(); } + private void runMigrations() + { + try + { + using (var db = contextFactory.GetForWrite(false)) + db.Context.Migrate(); + } + catch (Exception e) + { + Logger.Error(e.InnerException ?? e, "Migration failed! We'll be starting with a fresh database.", LoggingTarget.Database); + + // if we failed, let's delete the database and start fresh. + // todo: we probably want a better (non-destructive) migrations/recovery process at a later point than this. + contextFactory.ResetDatabase(); + + Logger.Log("Database purged successfully.", LoggingTarget.Database, LogLevel.Important); + + // only run once more, then hard bail. + using (var db = contextFactory.GetForWrite(false)) + db.Context.Migrate(); + } + } + public override void SetHost(GameHost host) { if (LocalConfig == null) @@ -256,5 +238,26 @@ namespace osu.Game } public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); + + private class OsuBindableBeatmap : BindableBeatmap + { + public OsuBindableBeatmap(WorkingBeatmap defaultValue, AudioManager audioManager) + : this(defaultValue) + { + RegisterAudioManager(audioManager); + } + + private OsuBindableBeatmap(WorkingBeatmap defaultValue) + : base(defaultValue) + { + } + + public override BindableBeatmap GetBoundCopy() + { + var copy = new OsuBindableBeatmap(Default); + copy.BindTo(this); + return copy; + } + } } } diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 66e3148065..398518ef2b 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -49,8 +49,8 @@ namespace osu.Game.Overlays.BeatmapSet 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") + new Field("mapped by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"), + new Field("submitted on", online.Submitted.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold") { Margin = new MarginPadding { Top = 5 }, }, @@ -58,11 +58,11 @@ namespace osu.Game.Overlays.BeatmapSet if (online.Ranked.HasValue) { - fields.Add(new Field("ranked on ", online.Ranked.Value.ToString(@"MMM d, yyyy"), @"Exo2.0-Bold")); + fields.Add(new Field("ranked on", online.Ranked.Value.ToString(@"MMMM 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")); + fields.Add(new Field("last updated on", online.LastUpdated.Value.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold")); } } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index c699ae2328..4fce6a49fb 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -3,6 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; @@ -11,10 +13,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class DownloadButton : HeaderButton { - public DownloadButton(string title, string subtitle) + public DownloadButton(BeatmapSetInfo set, bool noVideo = false) { Width = 120; + BeatmapSetDownloader downloader; Add(new Container { Depth = -1, @@ -22,6 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Padding = new MarginPadding { Horizontal = 10 }, Children = new Drawable[] { + downloader = new BeatmapSetDownloader(set, noVideo), new FillFlowContainer { Anchor = Anchor.CentreLeft, @@ -32,13 +36,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { new OsuSpriteText { - Text = title, + Text = "Download", TextSize = 13, Font = @"Exo2.0-Bold", }, new OsuSpriteText { - Text = subtitle, + Text = set.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty, TextSize = 11, Font = @"Exo2.0-Bold", }, @@ -54,6 +58,25 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }, }, }); + + Action = () => + { + if (!downloader.Download()) + { + Content.MoveToX(-5, 50, Easing.OutSine).Then() + .MoveToX(5, 100, Easing.InOutSine).Then() + .MoveToX(-5, 100, Easing.InOutSine).Then() + .MoveToX(0, 50, Easing.InSine); + } + }; + + downloader.Downloaded.ValueChanged += d => + { + if (d) + this.FadeOut(200); + else + this.FadeIn(200); + }; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 9b25d61f58..afba99f928 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.Graphics.UserInterface; using osu.Game.Overlays.BeatmapSet.Buttons; using OpenTK; using OpenTK.Graphics; @@ -25,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet private const float buttons_spacing = 5; private readonly Box tabsBg; - private readonly Container coverContainer; + private readonly UpdateableBeatmapSetCover cover; private readonly OsuSpriteText title, artist; private readonly Container noVideoButtons; private readonly FillFlowContainer videoButtons; @@ -34,9 +35,6 @@ namespace osu.Game.Overlays.BeatmapSet private readonly BeatmapSetOnlineStatusPill onlineStatusPill; public Details Details; - private BeatmapManager beatmaps; - private DelayedLoadWrapper cover; - public readonly BeatmapPicker Picker; private BeatmapSetInfo beatmapSet; @@ -61,28 +59,31 @@ namespace osu.Game.Overlays.BeatmapSet title.Text = BeatmapSet?.Metadata.Title ?? string.Empty; artist.Text = BeatmapSet?.Metadata.Artist ?? string.Empty; onlineStatusPill.Status = BeatmapSet?.OnlineInfo.Status ?? BeatmapSetOnlineStatus.None; + cover.BeatmapSet = BeatmapSet; - 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); - - coverContainer.Add(cover = new DelayedLoadWrapper( - new BeatmapSetCover(BeatmapSet) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), - }, 300) + if (BeatmapSet.OnlineInfo.HasVideo) { - RelativeSizeAxes = Axes.Both, - }); + videoButtons.Children = new[] + { + new DownloadButton(BeatmapSet), + new DownloadButton(BeatmapSet, true), + }; + + videoButtons.FadeIn(transition_duration); + noVideoButtons.FadeOut(transition_duration); + } + else + { + noVideoButtons.Child = new DownloadButton(BeatmapSet); + + noVideoButtons.FadeIn(transition_duration); + videoButtons.FadeOut(transition_duration); + } } else { @@ -93,6 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet public Header() { + ExternalLinkButton externalLink; RelativeSizeAxes = Axes.X; Height = 400; Masking = true; @@ -128,12 +130,7 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - coverContainer = new Container + cover = new UpdateableBeatmapSetCover { RelativeSizeAxes = Axes.Both, }, @@ -160,10 +157,24 @@ namespace osu.Game.Overlays.BeatmapSet Height = 113, Child = Picker = new BeatmapPicker(), }, - title = new OsuSpriteText + new FillFlowContainer { - Font = @"Exo2.0-BoldItalic", - TextSize = 37, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + title = new OsuSpriteText + { + Font = @"Exo2.0-BoldItalic", + TextSize = 37, + }, + externalLink = new ExternalLinkButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 3, Bottom = 4 }, //To better lineup with the font + }, + } }, artist = new OsuSpriteText { @@ -195,27 +206,12 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.Both, Alpha = 0f, - Child = new DownloadButton("Download", @"") - { - Action = () => download(false), - }, }, videoButtons = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Spacing = new Vector2(buttons_spacing), Alpha = 0f, - Children = new[] - { - new DownloadButton("Download", "with Video") - { - Action = () => download(false), - }, - new DownloadButton("Download", "without Video") - { - Action = () => download(true), - }, - }, }, }, }, @@ -247,44 +243,14 @@ namespace osu.Game.Overlays.BeatmapSet }; Picker.Beatmap.ValueChanged += b => Details.Beatmap = b; + Picker.Beatmap.ValueChanged += b => externalLink.Link = $@"https://osu.ppy.sh/beatmapsets/{BeatmapSet?.OnlineBeatmapSetID}#{b?.Ruleset.ShortName}/{b?.OnlineBeatmapID}"; } [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapManager beatmaps) + private void load(OsuColour colours) { tabsBg.Colour = colours.Gray3; - this.beatmaps = beatmaps; - - beatmaps.ItemAdded += handleBeatmapAdd; - updateDisplay(); } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd; - } - - private void handleBeatmapAdd(BeatmapSetInfo beatmap) => Schedule(() => - { - if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID) - downloadButtonsContainer.FadeOut(transition_duration); - }); - - private void download(bool noVideo) - { - if (beatmaps.GetExistingDownload(BeatmapSet) != null) - { - downloadButtonsContainer.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine).Then(); - - return; - } - - beatmaps.Download(BeatmapSet, noVideo); - } } } diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index cd0b7386e8..53216ad666 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet private const float metadata_width = 225; private const float spacing = 20; - private readonly MetadataSection description, source, tags; + private readonly MetadataSection source, tags; private readonly Box successRateBackground; private readonly SuccessRate successRate; @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapSet Child = new Container { RelativeSizeAxes = Axes.Both, - Child = description = new MetadataSection("Description"), + Child = new MetadataSection("Description"), }, }, new Container @@ -135,8 +135,6 @@ namespace osu.Game.Overlays.BeatmapSet private void load(OsuColour colours) { successRateBackground.Colour = colours.GrayE; - source.TextColour = description.TextColour = colours.Gray5; - tags.TextColour = colours.BlueDark; updateDisplay(); } @@ -195,7 +193,7 @@ namespace osu.Game.Overlays.BeatmapSet [BackgroundDependencyLoader] private void load(OsuColour colours) { - header.Colour = colours.Gray5; + header.Colour = textFlow.Colour = colours.Gray5; } } } diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 9715615d14..723e9e8b35 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -149,7 +149,7 @@ namespace osu.Game.Overlays.Direct { new OsuSpriteText { - Text = $"from {SetInfo.Metadata.Source}", + Text = $"{SetInfo.Metadata.Source}", TextSize = 14, Shadow = false, Colour = colours.Gray5, @@ -166,14 +166,13 @@ namespace osu.Game.Overlays.Direct }, }, }, - new DownloadButton + new DownloadButton(SetInfo) { Size = new Vector2(30), Margin = new MarginPadding(horizontal_padding), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Colour = colours.Gray5, - Action = StartDownload }, }, }, @@ -195,18 +194,18 @@ namespace osu.Game.Overlays.Direct new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, - playButton = new PlayButton(SetInfo) - { - Margin = new MarginPadding { Top = 5, Left = 10 }, - Size = new Vector2(30), - Alpha = 0, - }, statusContainer = new FillFlowContainer { AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 5, Left = 5 }, Spacing = new Vector2(5), }, + playButton = new PlayButton(SetInfo) + { + Margin = new MarginPadding { Top = 5, Left = 10 }, + Size = new Vector2(30), + Alpha = 0, + }, }); if (SetInfo.OnlineInfo?.HasVideo ?? false) @@ -214,24 +213,31 @@ namespace osu.Game.Overlays.Direct statusContainer.Add(new IconPill(FontAwesome.fa_film)); } + if (SetInfo.OnlineInfo?.HasStoryboard ?? false) + { + statusContainer.Add(new IconPill(FontAwesome.fa_image)); + } + statusContainer.Add(new BeatmapSetOnlineStatusPill(12, new MarginPadding { Horizontal = 10, Vertical = 5 }) { Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, }); + + PreviewPlaying.ValueChanged += _ => updateStatusContainer(); } protected override bool OnHover(InputState state) { - statusContainer.FadeOut(120, Easing.InOutQuint); - + updateStatusContainer(); return base.OnHover(state); } protected override void OnHoverLost(InputState state) { base.OnHoverLost(state); - - statusContainer.FadeIn(120, Easing.InOutQuint); + updateStatusContainer(); } + + private void updateStatusContainer() => statusContainer.FadeTo(IsHovered || PreviewPlaying ? 0 : 1, 120, Easing.InOutQuint); } } diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index 13398a4a32..6e3483604b 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -12,28 +12,31 @@ using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; using osu.Framework.Localisation; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Game.Beatmaps; namespace osu.Game.Overlays.Direct { public class DirectListPanel : DirectPanel { + private const float transition_duration = 120; private const float horizontal_padding = 10; private const float vertical_padding = 5; private const float height = 70; + private PlayButton playButton; + private Box progressBar; + private Container downloadContainer; + + protected override PlayButton PlayButton => playButton; + protected override Box PreviewBar => progressBar; + public DirectListPanel(BeatmapSetInfo beatmap) : base(beatmap) { RelativeSizeAxes = Axes.X; Height = height; } - private PlayButton playButton; - private Box progressBar; - - protected override PlayButton PlayButton => playButton; - protected override Box PreviewBar => progressBar; - [BackgroundDependencyLoader] private void load(LocalisationEngine localisation, OsuColour colours) { @@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Direct AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, LayoutEasing = Easing.OutQuint, - LayoutDuration = 120, + LayoutDuration = transition_duration, Spacing = new Vector2(10, 0), Children = new Drawable[] { @@ -104,53 +107,69 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Right = height - vertical_padding * 2 + vertical_padding }, + Direction = FillDirection.Horizontal, + LayoutEasing = Easing.OutQuint, + LayoutDuration = transition_duration, Children = new Drawable[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) + downloadContainer = new Container { - Margin = new MarginPadding { Right = 1 }, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Alpha = 0, + Child = new DownloadButton(SetInfo) + { + Size = new Vector2(height - vertical_padding * 2), + Margin = new MarginPadding { Left = vertical_padding }, + }, }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] + Direction = FillDirection.Vertical, + Children = new Drawable[] { - new OsuSpriteText + new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) { - Text = "mapped by ", - TextSize = 14, + Margin = new MarginPadding { Right = 1 }, + }, + new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Text = "mapped by ", + TextSize = 14, + }, + new OsuSpriteText + { + Text = SetInfo.Metadata.Author.Username, + TextSize = 14, + Font = @"Exo2.0-SemiBoldItalic", + }, + }, }, new OsuSpriteText { - Text = SetInfo.Metadata.Author.Username, + Text = $"from {SetInfo.Metadata.Source}", + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, TextSize = 14, - Font = @"Exo2.0-SemiBoldItalic", + Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, }, }, }, - new OsuSpriteText - { - Text = $"from {SetInfo.Metadata.Source}", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - TextSize = 14, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, - }, }, }, - new DownloadButton - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Size = new Vector2(height - vertical_padding * 2), - Action = StartDownload - }, }, }, progressBar = new Box @@ -165,5 +184,17 @@ namespace osu.Game.Overlays.Direct }, }); } + + protected override bool OnHover(InputState state) + { + downloadContainer.FadeIn(transition_duration, Easing.InOutQuint); + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + downloadContainer.FadeOut(transition_duration, Easing.InOutQuint); + base.OnHoverLost(state); + } } } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index cc0123dabc..e767f6ec83 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -27,8 +27,6 @@ namespace osu.Game.Overlays.Direct { public readonly BeatmapSetInfo SetInfo; - protected Box BlackBackground; - private const double hover_transition_time = 400; private Container content; @@ -81,12 +79,6 @@ namespace osu.Game.Overlays.Direct EdgeEffect = edgeEffectNormal, Children = new[] { - // temporary blackness until the actual background loads. - BlackBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, CreateBackground(), progressBar = new ProgressBar { @@ -155,22 +147,6 @@ namespace osu.Game.Overlays.Direct protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo); - protected void StartDownload() - { - if (beatmaps.GetExistingDownload(SetInfo) != null) - { - // we already have an active download running. - content.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine).Then(); - - return; - } - - beatmaps.Download(SetInfo); - } - private void attachDownload(DownloadBeatmapSetRequest request) { if (request.BeatmapSet.OnlineBeatmapSetID != SetInfo.OnlineBeatmapSetID) @@ -215,21 +191,10 @@ namespace osu.Game.Overlays.Direct return icons; } - protected Drawable CreateBackground() => new DelayedLoadWrapper( - new BeatmapSetCover(SetInfo) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - OnLoadComplete = d => - { - d.FadeInFromZero(400, Easing.Out); - BlackBackground.Delay(400).FadeOut(); - }, - }, 300) + protected Drawable CreateBackground() => new UpdateableBeatmapSetCover { RelativeSizeAxes = Axes.Both, + BeatmapSet = SetInfo, }; public class Statistic : FillFlowContainer diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index f01c9dac59..1ffa8dbd35 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -3,6 +3,8 @@ using osu.Framework.Graphics; using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using OpenTK; @@ -13,10 +15,12 @@ namespace osu.Game.Overlays.Direct { private readonly SpriteIcon icon; - public DownloadButton() + public DownloadButton(BeatmapSetInfo set, bool noVideo = false) { + BeatmapSetDownloader downloader; Children = new Drawable[] { + downloader = new BeatmapSetDownloader(set, noVideo), icon = new SpriteIcon { Anchor = Anchor.Centre, @@ -25,6 +29,25 @@ namespace osu.Game.Overlays.Direct Icon = FontAwesome.fa_osu_chevron_down_o, }, }; + + Action = () => + { + if (!downloader.Download()) + { + Content.MoveToX(-5, 50, Easing.OutSine).Then() + .MoveToX(5, 100, Easing.InOutSine).Then() + .MoveToX(-5, 100, Easing.InOutSine).Then() + .MoveToX(0, 50, Easing.InSine); + } + }; + + downloader.Downloaded.ValueChanged += d => + { + if (d) + this.FadeOut(200); + else + this.FadeIn(200); + }; } protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 4913b11ae1..131083c6ff 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays.Direct return; } - icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play; + icon.Icon = playing ? FontAwesome.fa_stop : FontAwesome.fa_play; icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); if (playing) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index f437546888..b33f271986 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -28,7 +28,6 @@ namespace osu.Game.Overlays private APIAccess api; private RulesetStore rulesets; - private BeatmapManager beatmaps; private readonly FillFlowContainer resultCountsContainer; private readonly OsuSpriteText resultCountsText; @@ -177,24 +176,14 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, BeatmapManager beatmaps) + private void load(OsuColour colours, APIAccess api, RulesetStore rulesets) { this.api = api; this.rulesets = rulesets; - this.beatmaps = beatmaps; resultCountsContainer.Colour = colours.Yellow; - - beatmaps.ItemAdded += setAdded; } - private void setAdded(BeatmapSetInfo set) => Schedule(() => - { - // if a new map was imported, we should remove it from search results (download completed etc.) - panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire(); - BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID); - }); - private void updateResultCounts() { resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint); @@ -297,9 +286,7 @@ namespace osu.Game.Overlays { Task.Run(() => { - var onlineIds = response.Select(r => r.OnlineBeatmapSetID).ToList(); - var presentOnlineIds = beatmaps.QueryBeatmapSets(s => onlineIds.Contains(s.OnlineBeatmapSetID) && !s.DeletePending).Select(r => r.OnlineBeatmapSetID).ToList(); - var sets = response.Select(r => r.ToBeatmapSet(rulesets)).Where(b => !presentOnlineIds.Contains(b.OnlineBeatmapSetID)).ToList(); + var sets = response.Select(r => r.ToBeatmapSet(rulesets)).ToList(); // may not need scheduling; loads async internally. Schedule(() => @@ -323,14 +310,6 @@ namespace osu.Game.Overlays private int distinctCount(List list) => list.Distinct().ToArray().Length; - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - beatmaps.ItemAdded -= setAdded; - } - public class ResultCounts { public readonly int Artists; diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 7406a9ec4f..ffe1560627 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -180,7 +180,7 @@ namespace osu.Game.Overlays.KeyBinding return true; } - protected override bool OnWheel(InputState state) + protected override bool OnScroll(InputState state) { if (HasFocus) { @@ -192,7 +192,7 @@ namespace osu.Game.Overlays.KeyBinding } } - return base.OnWheel(state); + return base.OnScroll(state); } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 632c00d1fd..f1624721da 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays.Mods protected Color4 LowMultiplierColour, HighMultiplierColour; protected readonly TriangleButton DeselectAllButton; - protected readonly OsuSpriteText MultiplierLabel; + protected readonly OsuSpriteText MultiplierLabel, UnrankedLabel; private readonly FillFlowContainer footerContainer; protected override bool BlockPassThroughKeyboard => false; @@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Mods LowMultiplierColour = colours.Red; HighMultiplierColour = colours.Green; + UnrankedLabel.Colour = colours.Blue; if (osu != null) Ruleset.BindTo(osu.Ruleset); @@ -99,15 +100,14 @@ namespace osu.Game.Overlays.Mods } MultiplierLabel.Text = $"{multiplier:N2}x"; - if (!ranked) - MultiplierLabel.Text += " (Unranked)"; - if (multiplier > 1.0) MultiplierLabel.FadeColour(HighMultiplierColour, 200); else if (multiplier < 1.0) MultiplierLabel.FadeColour(LowMultiplierColour, 200); else MultiplierLabel.FadeColour(Color4.White, 200); + + UnrankedLabel.FadeTo(ranked ? 0 : 1, 200); } protected override void PopOut() @@ -352,23 +352,33 @@ namespace osu.Game.Overlays.Mods }, new OsuSpriteText { - Text = @"Score Multiplier: ", + Text = @"Score Multiplier:", TextSize = 30, - Shadow = true, Margin = new MarginPadding { - Top = 5 + Top = 5, + Right = 10 } }, MultiplierLabel = new OsuSpriteText { Font = @"Exo2.0-Bold", TextSize = 30, - Shadow = true, Margin = new MarginPadding { Top = 5 } + }, + UnrankedLabel = new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = @"(Unranked)", + TextSize = 30, + Margin = new MarginPadding + { + Top = 5, + Left = 10 + } } } } diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 8c8ff89420..19ce0cf932 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -73,13 +73,13 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, OsuGameBase osuGame) + private void load(BeatmapManager beatmaps, IBindableBeatmap beatmap) { beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet); beatmaps.ItemAdded += addBeatmapSet; beatmaps.ItemRemoved += removeBeatmapSet; - beatmapBacking.BindTo(osuGame.Beatmap); + beatmapBacking.BindTo(beatmap); beatmapBacking.ValueChanged += _ => updateSelectedSet(); } @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Music private void updateSelectedSet() { foreach (PlaylistItem s in items.Children) - s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo.ID; + s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo?.ID; } public string SearchTerm diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 76c2222f8b..b74e7e1178 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -21,17 +21,22 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; + /// + /// Invoked when the order of an item in the list has changed. + /// The second parameter indicates the new index of the item. + /// public Action OrderChanged; + private readonly Bindable beatmap = new Bindable(); private BeatmapManager beatmaps; + private FilterControl filter; private PlaylistList list; - private readonly Bindable beatmapBacking = new Bindable(); - [BackgroundDependencyLoader] - private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours) + private void load(OsuColour colours, BindableBeatmap beatmap, BeatmapManager beatmaps) { + this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; Children = new Drawable[] @@ -73,13 +78,14 @@ namespace osu.Game.Overlays.Music }, }; - beatmapBacking.BindTo(game.Beatmap); - filter.Search.OnCommit = (sender, newText) => { - BeatmapInfo beatmap = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); - if (beatmap != null) - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(beatmap); + BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (toSelect != null) + { + beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); + beatmap.Value.Track.Restart(); + } }; } @@ -102,13 +108,14 @@ namespace osu.Game.Overlays.Music private void itemSelected(BeatmapSetInfo set) { - if (set.ID == (beatmapBacking.Value?.BeatmapSetInfo?.ID ?? -1)) + if (set.ID == (beatmap.Value?.BeatmapSetInfo?.ID ?? -1)) { - beatmapBacking.Value?.Track?.Seek(0); + beatmap.Value?.Track?.Seek(0); return; } - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); + beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); + beatmap.Value.Track.Restart(); } } diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index fb4e278b0c..d96bb40165 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -30,11 +30,8 @@ namespace osu.Game.Overlays public class MusicController : OsuFocusedOverlayContainer { private const float player_height = 130; - private const float transition_length = 800; - private const float progress_height = 10; - private const float bottom_black_area_height = 55; private Drawable background; @@ -49,16 +46,17 @@ namespace osu.Game.Overlays private PlaylistOverlay playlist; + private BeatmapManager beatmaps; private LocalisationEngine localisation; - private BeatmapManager beatmaps; - private readonly Bindable beatmapBacking = new Bindable(); private List beatmapSets; private BeatmapSetInfo currentSet; private Container dragContainer; private Container playerContainer; + private readonly Bindable beatmap = new Bindable(); + public MusicController() { Width = 400; @@ -97,8 +95,9 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(OsuGameBase game, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) + private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours, LocalisationEngine localisation) { + this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; this.localisation = localisation; @@ -224,8 +223,6 @@ namespace osu.Game.Overlays beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; - beatmapBacking.BindTo(game.Beatmap); - playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } @@ -240,10 +237,8 @@ namespace osu.Game.Overlays protected override void LoadComplete() { - beatmapBacking.ValueChanged += beatmapChanged; - beatmapBacking.DisabledChanged += beatmapDisabledChanged; - beatmapBacking.TriggerChange(); - + beatmap.BindValueChanged(beatmapChanged, true); + beatmap.BindDisabledChanged(beatmapDisabledChanged, true); base.LoadComplete(); } @@ -276,7 +271,7 @@ namespace osu.Game.Overlays playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; - if (track.HasCompleted && !track.Looping && !beatmapBacking.Disabled && beatmapSets.Any()) + if (track.HasCompleted && !track.Looping && !beatmap.Disabled && beatmapSets.Any()) next(); } else @@ -289,7 +284,7 @@ namespace osu.Game.Overlays if (track == null) { - if (!beatmapBacking.Disabled) + if (!beatmap.Disabled) next(true); return; } @@ -307,8 +302,8 @@ namespace osu.Game.Overlays var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); if (playable != null) { - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking); - beatmapBacking.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); + beatmap.Value.Track.Restart(); } } @@ -320,8 +315,8 @@ namespace osu.Game.Overlays var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); if (playable != null) { - beatmapBacking.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmapBacking); - beatmapBacking.Value.Track.Restart(); + beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); + beatmap.Value.Track.Restart(); } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index 09b6022ac5..3dc8f5ec15 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -22,11 +22,6 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - /// - /// Whether posted notifications should be processed. - /// - public readonly BindableBool Enabled = new BindableBool(true); - private FlowContainer sections; /// @@ -34,27 +29,6 @@ namespace osu.Game.Overlays /// public Func GetToolbarHeight; - public NotificationOverlay() - { - ScheduledDelegate notificationsEnabler = null; - Enabled.ValueChanged += v => - { - if (!IsLoaded) - { - processingPosts = v; - return; - } - - notificationsEnabler?.Cancel(); - - if (v) - // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. - notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, 1000); - else - processingPosts = false; - }; - } - [BackgroundDependencyLoader] private void load() { @@ -103,6 +77,29 @@ namespace osu.Game.Overlays }; } + private ScheduledDelegate notificationsEnabler; + private void updateProcessingMode() + { + bool enabled = OverlayActivationMode == OverlayActivation.All || State == Visibility.Visible; + + notificationsEnabler?.Cancel(); + + if (enabled) + // we want a slight delay before toggling notifications on to avoid the user becoming overwhelmed. + notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State == Visibility.Visible ? 0 : 1000); + else + processingPosts = false; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + StateChanged += _ => updateProcessingMode(); + OverlayActivationMode.ValueChanged += _ => updateProcessingMode(); + OverlayActivationMode.TriggerChange(); + } + private int totalCount => sections.Select(c => c.DisplayedCount).Sum(); private int unreadCount => sections.Select(c => c.UnreadCount).Sum(); diff --git a/osu.Game/Overlays/OverlayActivation.cs b/osu.Game/Overlays/OverlayActivation.cs new file mode 100644 index 0000000000..da4e153ce9 --- /dev/null +++ b/osu.Game/Overlays/OverlayActivation.cs @@ -0,0 +1,12 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Overlays +{ + public enum OverlayActivation + { + Disabled, + UserTriggered, + All + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 4c411b3210..067144c26e 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Diagnostics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -10,13 +9,13 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; @@ -105,11 +104,28 @@ namespace osu.Game.Overlays.Profile Y = -75, Size = new Vector2(25, 25) }, - new ProfileLink(user) + new FillFlowContainer { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Y = -48, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = user.Username, + Font = @"Exo2.0-RegularItalic", + TextSize = 30, + }, + new ExternalLinkButton($@"https://osu.ppy.sh/users/{user.Id}") + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 3, Bottom = 3 }, //To better lineup with the font + }, + } }, countryFlag = new DrawableFlag(user.Country) { @@ -455,28 +471,6 @@ namespace osu.Game.Overlays.Profile infoTextRight.NewLine(); } - private class ProfileLink : OsuHoverContainer, IHasTooltip - { - public string TooltipText => "View Profile in Browser"; - - public override bool HandleMouseInput => true; - - public ProfileLink(User user) - { - Action = () => Process.Start($@"https://osu.ppy.sh/users/{user.Id}"); - - AutoSizeAxes = Axes.Both; - - Child = new OsuSpriteText - { - Text = user.Username, - Font = @"Exo2.0-RegularItalic", - TextSize = 30, - }; - } - } - - private class GradeBadge : Container { private const float width = 50; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs index be8e09105b..0a2b2fe121 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs @@ -24,19 +24,13 @@ namespace osu.Game.Overlays.Profile.Sections.Historical this.playCount = playCount; } - protected override Drawable CreateLeftVisual() => new DelayedLoadWrapper(new BeatmapSetCover(beatmap.BeatmapSet, BeatmapSetCoverType.List) + protected override Drawable CreateLeftVisual() => new UpdateableBeatmapSetCover { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(500, Easing.OutQuint) - }) - { - Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - RelativeSizeAxes = Axes.None, + Origin = Anchor.CentreLeft, Size = new Vector2(80, 50), + BeatmapSet = beatmap.BeatmapSet, + CoverType = BeatmapSetCoverType.List, }; [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs new file mode 100644 index 0000000000..a9cefa81da --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -0,0 +1,26 @@ +// 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.Configuration; + +namespace osu.Game.Overlays.Settings.Sections.Gameplay +{ + public class ModsSettings : SettingsSubsection + { + protected override string Header => "Mods"; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + Children = new[] + { + new SettingsCheckbox + { + LabelText = "Increase visibility of first object with \"Hidden\" mod", + Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility) + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index 3851a73901..8add0b01ec 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -21,7 +21,8 @@ namespace osu.Game.Overlays.Settings.Sections { new GeneralSettings(), new SongSelectSettings(), - new ScrollingSettings() + new ScrollingSettings(), + new ModsSettings(), }; } diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index 900f03fe7b..909fc20446 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -12,6 +11,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using OpenTK; using OpenTK.Graphics; +using DebugUtils = osu.Game.Utils.DebugUtils; namespace osu.Game.Overlays.Settings { diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 424a457110..48d0674b3d 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -10,6 +10,8 @@ using osu.Framework.Input; using osu.Game.Graphics; using OpenTK; using osu.Framework.Graphics.Shapes; +using osu.Framework.Allocation; +using osu.Framework.Configuration; namespace osu.Game.Overlays.Toolbar { @@ -29,6 +31,8 @@ namespace osu.Game.Overlays.Toolbar private const float alpha_hovering = 0.8f; private const float alpha_normal = 0.6f; + private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); + public Toolbar() { Children = new Drawable[] @@ -76,6 +80,19 @@ namespace osu.Game.Overlays.Toolbar Size = new Vector2(1, HEIGHT); } + [BackgroundDependencyLoader(true)] + private void load(OsuGame osuGame) + { + if (osuGame != null) + overlayActivationMode.BindTo(osuGame.OverlayActivationMode); + + StateChanged += visibility => + { + if (overlayActivationMode == OverlayActivation.Disabled) + State = Visibility.Hidden; + }; + } + public class ToolbarBackground : Container { private readonly Box solidBackground; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 64106967f4..b2cf43704b 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -167,9 +167,25 @@ namespace osu.Game.Overlays.Volume private set => Bindable.Value = value; } - public void Increase() => Volume += 0.05f; + private const float adjust_step = 0.05f; - public void Decrease() => Volume -= 0.05f; + public void Increase() => adjust(1); + public void Decrease() => adjust(-1); + + private void adjust(int direction) + { + float amount = adjust_step * direction; + + var mouse = GetContainingInputManager().CurrentState.Mouse; + if (mouse.HasPreciseScroll) + { + float scrollDelta = mouse.ScrollDelta.Y; + if (scrollDelta != 0) + amount *= Math.Abs(scrollDelta / 10); + } + + Volume += amount; + } public bool OnPressed(GlobalAction action) { diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5f1b9a6bad..0c91c9f548 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Edit private RulesetContainer rulesetContainer; private readonly List layerContainers = new List(); - private readonly Bindable beatmap = new Bindable(); + private readonly IBindable beatmap = new Bindable(); protected HitObjectComposer(Ruleset ruleset) { @@ -38,9 +38,9 @@ namespace osu.Game.Rulesets.Edit } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, IFrameBasedClock framedClock) + private void load(IBindableBeatmap beatmap, IFrameBasedClock framedClock) { - beatmap.BindTo(osuGame.Beatmap); + this.beatmap.BindTo(beatmap); try { diff --git a/osu.Game/Rulesets/Mods/IReadFromConfig.cs b/osu.Game/Rulesets/Mods/IReadFromConfig.cs new file mode 100644 index 0000000000..93c9ae0c34 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IReadFromConfig.cs @@ -0,0 +1,15 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Configuration; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that require reading access to the osu! configuration. + /// + public interface IReadFromConfig + { + void ReadFromConfig(OsuConfigManager config); + } +} diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 75f37b993d..7058d1bed6 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods public override string ShortenedName => "AT"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; public override string Description => "Watch a perfect automated play through the song."; - public override double ScoreMultiplier => 0; + public override double ScoreMultiplier => 1; public bool AllowFail => false; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; } diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index b489a665d9..45da628ce8 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -1,16 +1,37 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework.Configuration; +using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Rulesets.Objects.Drawables; +using System.Collections.Generic; +using System.Linq; namespace osu.Game.Rulesets.Mods { - public abstract class ModHidden : Mod + public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects { public override string Name => "Hidden"; public override string ShortenedName => "HD"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; + + protected Bindable IncreaseFirstObjectVisibility = new Bindable(); + + public void ReadFromConfig(OsuConfigManager config) + { + IncreaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility); + } + + public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) + { + // todo: fix ordering of objects so we don't have to do this (#2740). + foreach (var d in drawables.Reverse().Skip(IncreaseFirstObjectVisibility ? 1 : 0)) + d.ApplyCustomUpdateState += ApplyHiddenState; + } + + protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { } } } diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index e4137254e6..0bd9becd78 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods public override string Name => "Relax"; public override string ShortenedName => "RX"; public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; - public override double ScoreMultiplier => 0; + public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 1847b63658..8d267f48e9 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using osu.Framework.Logging; using osu.Game.Database; namespace osu.Game.Rulesets @@ -114,8 +115,9 @@ namespace osu.Game.Rulesets var assembly = Assembly.LoadFrom(file); loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset))); } - catch (Exception) + catch (Exception e) { + Logger.Error(e, "Failed to load ruleset"); } } } diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index e42e74c245..384b71cccc 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -57,6 +57,7 @@ namespace osu.Game.Rulesets.UI public abstract IEnumerable Objects { get; } private readonly Lazy playfield; + /// /// The playfield. /// @@ -130,7 +131,6 @@ namespace osu.Game.Rulesets.UI HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; } - /// /// Creates the cursor. May be null if the doesn't provide a custom cursor. /// @@ -194,6 +194,7 @@ namespace osu.Game.Rulesets.UI protected override Container Content => content; private Container content; + private IEnumerable mods; /// /// Whether to assume the beatmap passed into this is for the current ruleset. @@ -216,13 +217,10 @@ namespace osu.Game.Rulesets.UI KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager.RelativeSizeAxes = Axes.Both; - - // Add mods, should always be the last thing applied to give full control to mods - applyMods(Mods); } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { KeyBindingInputManager.Add(content = new Container { @@ -235,6 +233,9 @@ namespace osu.Game.Rulesets.UI if (Cursor != null) KeyBindingInputManager.Add(Cursor); + // Apply mods + applyMods(Mods, config); + loadObjects(); } @@ -242,13 +243,16 @@ namespace osu.Game.Rulesets.UI /// Applies the active mods to this RulesetContainer. /// /// - private void applyMods(IEnumerable mods) + private void applyMods(IEnumerable mods, OsuConfigManager config) { if (mods == null) return; foreach (var mod in mods.OfType>()) mod.ApplyToRulesetContainer(this); + + foreach (var mod in mods.OfType()) + mod.ReadFromConfig(config); } public override void SetReplay(Replay replay) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 58a66a5224..b35616985a 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -121,8 +121,6 @@ namespace osu.Game.Rulesets.UI /// private bool validState; - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - private bool isAttached => replayInputHandler != null && !UseParentState; private const int max_catch_up_updates_per_frame = 50; diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index 399f9274a6..caf9ba27ff 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Edit.Components private const float corner_radius = 5; private const float contents_padding = 15; - public readonly Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); protected Track Track => Beatmap.Value.Track; private readonly Drawable background; @@ -42,8 +42,9 @@ namespace osu.Game.Screens.Edit.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(IBindableBeatmap beatmap, OsuColour colours) { + Beatmap.BindTo(beatmap); background.Colour = colours.Gray1; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs index c00e9ac4d5..07d9398d38 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; using OpenTK; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -15,7 +16,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public abstract class TimelinePart : CompositeDrawable { - public Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); private readonly Container timeline; @@ -30,6 +31,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts }; } + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap) + { + Beatmap.BindTo(beatmap); + } + private void updateRelativeChildSize() { // the track may not be loaded completely (only has a length once it is). diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 0301870588..77878288f9 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -20,19 +20,17 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary [BackgroundDependencyLoader] private void load(OsuColour colours, IAdjustableClock adjustableClock) { - TimelinePart markerPart, controlPointPart, bookmarkPart, breakPart; - Children = new Drawable[] { - markerPart = new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, - controlPointPart = new ControlPointPart + new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, + new ControlPointPart { Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.Both, Height = 0.35f }, - bookmarkPart = new BookmarkPart + new BookmarkPart { Anchor = Anchor.Centre, Origin = Anchor.TopCentre, @@ -67,7 +65,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary }, } }, - breakPart = new BreakPart + new BreakPart { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -75,11 +73,6 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary Height = 0.25f } }; - - markerPart.Beatmap.BindTo(Beatmap); - controlPointPart.Beatmap.BindTo(Beatmap); - bookmarkPart.Beatmap.BindTo(Beatmap); - breakPart.Beatmap.BindTo(Beatmap); } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b657fe5597..d4f66c2f09 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -41,14 +41,14 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); + => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); [BackgroundDependencyLoader] private void load(OsuColour colours) { // TODO: should probably be done at a RulesetContainer level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); - clock = new EditorClock(Beatmap.Value.Beatmap.ControlPointInfo, beatDivisor) { IsCoupled = false }; + clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); dependencies.CacheAs(clock); @@ -128,9 +128,9 @@ namespace osu.Game.Screens.Edit { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 10 }, - Child = timeInfo = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, + Child = new TimeInfoContainer { RelativeSizeAxes = Axes.Both }, }, - timeline = new SummaryTimeline + new SummaryTimeline { RelativeSizeAxes = Axes.Both, }, @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 10 }, - Child = playback = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, } }, } @@ -148,9 +148,6 @@ namespace osu.Game.Screens.Edit }, }; - timeInfo.Beatmap.BindTo(Beatmap); - timeline.Beatmap.BindTo(Beatmap); - playback.Beatmap.BindTo(Beatmap); menuBar.Mode.ValueChanged += onModeChanged; bottomBackground.Colour = colours.Gray2; @@ -178,13 +175,12 @@ namespace osu.Game.Screens.Edit break; } - currentScreen.Beatmap.BindTo(Beatmap); LoadComponentAsync(currentScreen, screenContainer.Add); } - protected override bool OnWheel(InputState state) + protected override bool OnScroll(InputState state) { - if (state.Mouse.WheelDelta > 0) + if (state.Mouse.ScrollDelta.X + state.Mouse.ScrollDelta.Y > 0) clock.SeekBackward(true); else clock.SeekForward(true); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 67025f0620..1c40181ec9 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -5,8 +5,10 @@ using System; using System.Linq; using osu.Framework.MathUtils; using osu.Framework.Timing; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Screens.Edit.Screens.Compose; +using OpenTK; namespace osu.Game.Screens.Edit { @@ -15,15 +17,26 @@ namespace osu.Game.Screens.Edit /// public class EditorClock : DecoupleableInterpolatingFramedClock { + public readonly double TrackLength; + public ControlPointInfo ControlPointInfo; private readonly BindableBeatDivisor beatDivisor; - public EditorClock(ControlPointInfo controlPointInfo, BindableBeatDivisor beatDivisor) + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) + { + this.beatDivisor = beatDivisor; + + ControlPointInfo = beatmap.Beatmap.ControlPointInfo; + TrackLength = beatmap.Track.Length; + } + + public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) { this.beatDivisor = beatDivisor; ControlPointInfo = controlPointInfo; + TrackLength = trackLength; } /// @@ -111,6 +124,8 @@ namespace osu.Game.Screens.Edit if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; + // Ensure the sought point is within the boundaries + seekTime = MathHelper.Clamp(seekTime, 0, TrackLength); Seek(seekTime); } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs index fea4883144..b991f7c2c0 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Compose.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Compose.cs @@ -28,7 +28,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose if (beatDivisor != null) this.beatDivisor.BindTo(beatDivisor); - ScrollableTimeline timeline; Children = new Drawable[] { new GridContainer @@ -65,7 +64,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } + Child = new ScrollableTimeline { RelativeSizeAxes = Axes.Both } }, new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both } }, @@ -94,8 +93,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose }, }; - timeline.Beatmap.BindTo(Beatmap); - var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); if (ruleset == null) { diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs index 72dda24b62..136ceb47c4 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/BeatmapWaveformGraph.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; @@ -11,14 +10,13 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline { public class BeatmapWaveformGraph : CompositeDrawable { - public readonly Bindable Beatmap = new Bindable(); + public WorkingBeatmap Beatmap { set => graph.Waveform = value.Waveform; } private readonly WaveformGraph graph; public BeatmapWaveformGraph() { InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both }; - Beatmap.ValueChanged += b => graph.Waveform = b.Waveform; } /// diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs index 3223c08c1f..3bebf78b17 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollableTimeline.cs @@ -2,11 +2,9 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -14,8 +12,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline { public class ScrollableTimeline : CompositeDrawable { - public readonly Bindable Beatmap = new Bindable(); - private readonly ScrollingTimelineContainer timelineContainer; public ScrollableTimeline() @@ -117,7 +113,6 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline hitSoundsCheckbox.Current.Value = true; waveformCheckbox.Current.Value = true; - timelineContainer.Beatmap.BindTo(Beatmap); timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current); } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs index 83aa86ba61..cc51ae1096 100644 --- a/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Timeline/ScrollingTimelineContainer.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using osu.Framework.Allocation; using OpenTK; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -17,7 +18,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline public readonly Bindable HitObjectsVisible = new Bindable(); public readonly Bindable HitSoundsVisible = new Bindable(); public readonly Bindable WaveformVisible = new Bindable(); - public readonly Bindable Beatmap = new Bindable(); + + private readonly IBindable beatmap = new Bindable(); private readonly BeatmapWaveformGraph waveform; @@ -36,12 +38,20 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline Content.AutoSizeAxes = Axes.None; Content.RelativeSizeAxes = Axes.Both; - waveform.Beatmap.BindTo(Beatmap); WaveformVisible.ValueChanged += waveformVisibilityChanged; Zoom = 10; } + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap) + { + this.beatmap.BindTo(beatmap); + this.beatmap.BindValueChanged(beatmapChanged, true); + } + + private void beatmapChanged(WorkingBeatmap beatmap) => waveform.Beatmap = beatmap; + private float minZoom = 1; /// /// The minimum zoom level allowed. @@ -123,15 +133,15 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline /// private float? localZoomTarget; - protected override bool OnWheel(InputState state) + protected override bool OnScroll(InputState state) { if (!state.Keyboard.ControlPressed) - return base.OnWheel(state); + return base.OnScroll(state); relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X; localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X; - Zoom += state.Mouse.WheelDelta; + Zoom += state.Mouse.ScrollDelta.Y; return true; } diff --git a/osu.Game/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index f70c462cd8..f8402b9a9f 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -1,6 +1,7 @@ // 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.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -13,7 +14,7 @@ namespace osu.Game.Screens.Edit.Screens /// public class EditorScreen : Container { - public readonly Bindable Beatmap = new Bindable(); + protected readonly IBindable Beatmap = new Bindable(); protected override Container Content => content; private readonly Container content; @@ -27,6 +28,12 @@ namespace osu.Game.Screens.Edit.Screens InternalChild = content = new Container { RelativeSizeAxes = Axes.Both }; } + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap) + { + Beatmap.BindTo(beatmap); + } + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index fb5c5ca84b..c3b3e747fd 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -1,7 +1,6 @@ // 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.Linq; using osu.Framework.Allocation; @@ -30,43 +29,48 @@ namespace osu.Game.Screens { base.LogoArriving(logo, resuming); + logo.BeatMatching = false; logo.Triangles = false; logo.Origin = Anchor.BottomRight; logo.Anchor = Anchor.BottomRight; logo.Position = new Vector2(-40); logo.Scale = new Vector2(0.2f); - logo.FadeInFromZero(5000, Easing.OutQuint); - } - - private OsuScreen loadScreen; - private ShaderPrecompiler precompiler; - - protected override void OnEntering(Screen last) - { - base.OnEntering(last); - - LoadComponentAsync(precompiler = new ShaderPrecompiler(loadIfReady), Add); - LoadComponentAsync(loadScreen = showDisclaimer ? (OsuScreen)new Disclaimer() : new Intro(), s => loadIfReady()); - } - - private void loadIfReady() - { - if (ChildScreen == loadScreen) return; - - if (loadScreen.LoadState != LoadState.Ready) - return; - - if (!precompiler.FinishedCompiling) - return; - - Push(loadScreen); + logo.Delay(500).FadeInFromZero(1000, Easing.OutQuint); } protected override void LogoSuspending(OsuLogo logo) { base.LogoSuspending(logo); - logo.FadeOut(100); + logo.FadeOut(logo.Alpha * 400); + } + + private OsuScreen loadableScreen; + private ShaderPrecompiler precompiler; + + protected virtual OsuScreen CreateLoadableScreen() => showDisclaimer ? (OsuScreen)new Disclaimer() : new Intro(); + + protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + + LoadComponentAsync(precompiler = CreateShaderPrecompiler(), Add); + LoadComponentAsync(loadableScreen = CreateLoadableScreen()); + + checkIfLoaded(); + } + + private void checkIfLoaded() + { + if (loadableScreen.LoadState != LoadState.Ready || !precompiler.FinishedCompiling) + { + Schedule(checkIfLoaded); + return; + } + + Push(loadableScreen); } [BackgroundDependencyLoader] @@ -80,16 +84,10 @@ namespace osu.Game.Screens /// public class ShaderPrecompiler : Drawable { - private readonly Action onLoaded; private readonly List loadTargets = new List(); public bool FinishedCompiling { get; private set; } - public ShaderPrecompiler(Action onLoaded) - { - this.onLoaded = onLoaded; - } - [BackgroundDependencyLoader] private void load(ShaderManager manager) { @@ -103,16 +101,17 @@ namespace osu.Game.Screens loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); } + protected virtual bool AllLoaded => loadTargets.All(s => s.Loaded); + protected override void Update() { base.Update(); // if our target is null we are done. - if (loadTargets.All(s => s.Loaded)) + if (AllLoaded) { FinishedCompiling = true; Expire(); - onLoaded?.Invoke(); } } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 8b88204ed0..81abc4cd3d 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -8,7 +8,6 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -17,6 +16,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Input.Bindings; +using osu.Game.Overlays; using OpenTK; using OpenTK.Graphics; using OpenTK.Input; @@ -27,9 +27,6 @@ namespace osu.Game.Screens.Menu { public event Action StateChanged; - private readonly BindableBool hideOverlaysOnEnter = new BindableBool(); - private readonly BindableBool allowOpeningOverlays = new BindableBool(); - public Action OnEdit; public Action OnExit; public Action OnDirect; @@ -133,15 +130,12 @@ namespace osu.Game.Screens.Menu buttonFlow.AddRange(buttonsTopLevel); } + private OsuGame game; + [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuGame game) { - if (game != null) - { - hideOverlaysOnEnter.BindTo(game.HideOverlaysOnEnter); - allowOpeningOverlays.BindTo(game.AllowOpeningOverlays); - } - + this.game = game; sampleBack = audio.Sample.Get(@"Menu/button-back-select"); } @@ -254,9 +248,6 @@ namespace osu.Game.Screens.Menu backButton.ContractStyle = 0; settingsButton.ContractStyle = 0; - if (state == MenuState.TopLevel) - buttonArea.FinishTransforms(true); - updateLogoState(lastState); using (buttonArea.BeginDelayedSequence(lastState == MenuState.Initial ? 150 : 0, true)) @@ -325,52 +316,63 @@ namespace osu.Game.Screens.Menu { if (logo == null) return; - logoDelayedAction?.Cancel(); - switch (state) { case MenuState.Exit: case MenuState.Initial: - logoTracking = false; - + logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => - { - hideOverlaysOnEnter.Value = true; - allowOpeningOverlays.Value = false; + { + logoTracking = false; - logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.Both; + if (game != null) + { + game.OverlayActivationMode.Value = state == MenuState.Exit ? OverlayActivation.Disabled : OverlayActivation.All; + game.Toolbar.Hide(); + } - logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); - logo.ScaleTo(1, 800, Easing.OutExpo); - }, 150); + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.Both; + logo.MoveTo(new Vector2(0.5f), 800, Easing.OutExpo); + logo.ScaleTo(1, 800, Easing.OutExpo); + }, buttonArea.Alpha * 150); break; case MenuState.TopLevel: case MenuState.Play: - logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; - switch (lastState) { case MenuState.TopLevel: // coming from toplevel to play + break; case MenuState.Initial: - logoTracking = false; - logo.ScaleTo(0.5f, 200, Easing.In); + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.None; + + bool impact = logo.Scale.X > 0.6f; + + if (lastState == MenuState.Initial) + logo.ScaleTo(0.5f, 200, Easing.In); logo.MoveTo(logoTrackingPosition, lastState == MenuState.EnteringMode ? 0 : 200, Easing.In); + logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { logoTracking = true; - logo.Impact(); + if (impact) + logo.Impact(); - hideOverlaysOnEnter.Value = false; - allowOpeningOverlays.Value = true; + if (game != null) + { + game.OverlayActivationMode.Value = OverlayActivation.All; + game.Toolbar.State = Visibility.Visible; + } }, 200); break; default: + logo.ClearTransforms(targetMember: nameof(Position)); + logo.RelativePositionAxes = Axes.None; logoTracking = true; logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 9a671cf780..0c70dbf570 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -9,6 +9,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; using OpenTK.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Menu { @@ -19,6 +20,7 @@ namespace osu.Game.Screens.Menu private Color4 iconColour; protected override bool HideOverlaysOnEnter => true; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; public override bool CursorVisible => false; diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index c174e2d470..c1032011f7 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -15,6 +15,7 @@ using osu.Game.IO.Archives; using osu.Game.Screens.Backgrounds; using OpenTK; using OpenTK.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Menu { @@ -27,12 +28,14 @@ namespace osu.Game.Screens.Menu /// public bool DidLoadMenu; + private readonly Bindable beatmap = new Bindable(); + private MainMenu mainMenu; private SampleChannel welcome; private SampleChannel seeya; protected override bool HideOverlaysOnEnter => true; - protected override bool AllowOpeningOverlays => false; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; public override bool CursorVisible => false; @@ -41,11 +44,13 @@ namespace osu.Game.Screens.Menu private Bindable menuVoice; private Bindable menuMusic; private Track track; - private WorkingBeatmap beatmap; + private WorkingBeatmap introBeatmap; [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) + private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game, BindableBeatmap beatmap) { + this.beatmap.BindTo(beatmap); + menuVoice = config.GetBindable(OsuSetting.MenuVoice); menuMusic = config.GetBindable(OsuSetting.MenuMusic); @@ -72,38 +77,13 @@ namespace osu.Game.Screens.Menu } } - beatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - track = beatmap.Track; + introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + track = introBeatmap.Track; welcome = audio.Sample.Get(@"welcome"); seeya = audio.Sample.Get(@"seeya"); } - protected override void OnEntering(Screen last) - { - base.OnEntering(last); - - Game.Beatmap.Value = beatmap; - - if (menuVoice) - welcome.Play(); - - Scheduler.AddDelayed(delegate - { - // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. - if (menuMusic) - track.Start(); - - LoadComponentAsync(mainMenu = new MainMenu()); - - Scheduler.AddDelayed(delegate - { - DidLoadMenu = true; - Push(mainMenu); - }, delay_step_one); - }, delay_step_two); - } - private const double delay_step_one = 2300; private const double delay_step_two = 600; @@ -113,6 +93,29 @@ namespace osu.Game.Screens.Menu { base.LogoArriving(logo, resuming); + if (!resuming) + { + beatmap.Value = introBeatmap; + + if (menuVoice) + welcome.Play(); + + Scheduler.AddDelayed(delegate + { + // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Manu. + if (menuMusic) + track.Start(); + + LoadComponentAsync(mainMenu = new MainMenu()); + + Scheduler.AddDelayed(delegate + { + DidLoadMenu = true; + Push(mainMenu); + }, delay_step_one); + }, delay_step_two); + } + logo.RelativePositionAxes = Axes.Both; logo.Colour = Color4.White; logo.Ripple = false; diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 1f2cb915b3..fb6130fa36 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Menu { public class LogoVisualisation : Drawable, IHasAccentColour { - private readonly Bindable beatmap = new Bindable(); + private readonly IBindable beatmap = new Bindable(); /// /// The number of bars to jump each update iteration. @@ -78,9 +78,9 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, OsuGameBase game) + private void load(ShaderManager shaders, IBindableBeatmap beatmap) { - beatmap.BindTo(game.Beatmap); + this.beatmap.BindTo(beatmap); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index d5f3b11467..cbdd8b4e8b 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -25,7 +25,6 @@ namespace osu.Game.Screens.Menu private readonly ButtonSystem buttons; protected override bool HideOverlaysOnEnter => buttons.State == MenuState.Initial; - protected override bool AllowOpeningOverlays => buttons.State != MenuState.Initial; protected override bool AllowBackButton => buttons.State != MenuState.Initial; diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index fae3e72552..7d46aad089 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -22,10 +22,10 @@ namespace osu.Game.Screens.Menu public override bool HandleKeyboardInput => false; public override bool HandleMouseInput => false; - private readonly Bindable beatmap = new Bindable(); + private readonly IBindable beatmap = new Bindable(); - private readonly Box leftBox; - private readonly Box rightBox; + private Box leftBox; + private Box rightBox; private const float amplitude_dead_zone = 0.25f; private const float alpha_multiplier = (1 - amplitude_dead_zone) / 0.55f; @@ -42,6 +42,17 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre; Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap, OsuColour colours) + { + this.beatmap.BindTo(beatmap); + + // linear colour looks better in this case, so let's use it for now. + Color4 gradientDark = colours.Blue.Opacity(0).ToLinear(); + Color4 gradientLight = colours.Blue.Opacity(0.6f).ToLinear(); + Children = new Drawable[] { leftBox = new Box @@ -49,35 +60,27 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, - Width = box_width, + Width = box_width * 2, + // align off-screen to make sure our edges don't become visible during parallax. + X = -box_width, Alpha = 0, Blending = BlendingMode.Additive, + Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark) }, rightBox = new Box { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = box_width, + Width = box_width * 2, + X = box_width, Alpha = 0, Blending = BlendingMode.Additive, + Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight) } }; } - [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuColour colours) - { - beatmap.BindTo(game.Beatmap); - - // linear colour looks better in this case, so let's use it for now. - Color4 gradientDark = colours.Blue.Opacity(0).ToLinear(); - Color4 gradientLight = colours.Blue.Opacity(0.3f).ToLinear(); - - leftBox.Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark); - rightBox.Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight); - } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) { if (beatIndex < 0) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 42a8dbd5da..16482b0e48 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -64,6 +64,8 @@ namespace osu.Game.Screens.Menu set { colourAndTriangles.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } } + public bool BeatMatching = true; + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => logoContainer.ReceiveMouseInputAt(screenSpacePos); public bool Ripple @@ -264,6 +266,8 @@ namespace osu.Game.Screens.Menu { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + if (!BeatMatching) return; + lastBeatIndex = beatIndex; var beatLength = timingPoint.BeatLength; diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs new file mode 100644 index 0000000000..42863754c5 --- /dev/null +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -0,0 +1,79 @@ +// 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.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Multi.Components +{ + public class BeatmapTitle : FillFlowContainer + { + private readonly OsuSpriteText beatmapTitle, beatmapDash, beatmapArtist; + + private LocalisationEngine localisation; + + public float TextSize + { + set { beatmapTitle.TextSize = beatmapDash.TextSize = beatmapArtist.TextSize = value; } + } + + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + set + { + if (value == beatmap) return; + beatmap = value; + + if (IsLoaded) + updateText(); + } + } + + public BeatmapTitle() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + + Children = new[] + { + beatmapTitle = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", }, + beatmapDash = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", }, + beatmapArtist = new OsuSpriteText { Font = @"Exo2.0-RegularItalic", }, + }; + } + + [BackgroundDependencyLoader] + private void load(LocalisationEngine localisation) + { + this.localisation = localisation; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateText(); + } + + private void updateText() + { + if (beatmap == null) + { + beatmapTitle.Current = beatmapArtist.Current = null; + beatmapTitle.Text = "Changing map"; + beatmapDash.Text = beatmapArtist.Text = string.Empty; + } + else + { + beatmapTitle.Current = localisation.GetUnicodePreference(beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title); + beatmapDash.Text = @" - "; + beatmapArtist.Current = localisation.GetUnicodePreference(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs new file mode 100644 index 0000000000..78ffe01ef0 --- /dev/null +++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs @@ -0,0 +1,70 @@ +// 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.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Multiplayer; +using OpenTK; + +namespace osu.Game.Screens.Multi.Components +{ + public class BeatmapTypeInfo : FillFlowContainer + { + private readonly ModeTypeInfo modeTypeInfo; + private readonly BeatmapTitle beatmapTitle; + private readonly OsuSpriteText beatmapAuthor; + + public BeatmapInfo Beatmap + { + set + { + modeTypeInfo.Beatmap = beatmapTitle.Beatmap = value; + beatmapAuthor.Text = value == null ? string.Empty : $"mapped by {value.Metadata.Author}"; + } + } + + public GameType Type + { + set { modeTypeInfo.Type = value; } + } + + public BeatmapTypeInfo() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + LayoutDuration = 100; + Spacing = new Vector2(5f, 0f); + + Children = new Drawable[] + { + modeTypeInfo = new ModeTypeInfo(), + new Container + { + AutoSizeAxes = Axes.X, + Height = 30, + Margin = new MarginPadding { Left = 5 }, + Children = new Drawable[] + { + beatmapTitle = new BeatmapTitle(), + beatmapAuthor = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + TextSize = 14, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + beatmapAuthor.Colour = colours.Gray9; + } + } +} diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Components/DrawableRoom.cs index 88a253d719..54bd0ae7cc 100644 --- a/osu.Game/Screens/Multi/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Components/DrawableRoom.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; @@ -9,7 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Localisation; +using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -23,11 +24,11 @@ using OpenTK.Graphics; namespace osu.Game.Screens.Multi.Components { - public class DrawableRoom : OsuClickableContainer, IStateful + public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable { + public const float SELECTION_BORDER_WIDTH = 4; private const float corner_radius = 5; - private const float selection_border_width = 4; - private const float transition_duration = 100; + private const float transition_duration = 60; private const float content_padding = 10; private const float height = 100; private const float side_strip_width = 5; @@ -40,7 +41,7 @@ namespace osu.Game.Screens.Multi.Components private readonly Bindable statusBind = new Bindable(); private readonly Bindable typeBind = new Bindable(); private readonly Bindable beatmapBind = new Bindable(); - private readonly Bindable participantsBind = new Bindable(); + private readonly Bindable> participantsBind = new Bindable>(); public readonly Room Room; @@ -62,6 +63,30 @@ namespace osu.Game.Screens.Multi.Components } } + public IEnumerable FilterTerms => new[] { Room.Name.Value }; + + private bool matchingFilter; + public bool MatchingFilter + { + get { return matchingFilter; } + set + { + matchingFilter = value; + this.FadeTo(MatchingFilter ? 1 : 0, 200); + } + } + + private Action action; + public new Action Action + { + get { return action; } + set + { + action = value; + Enabled.Value = action != null; + } + } + public event Action StateChanged; public DrawableRoom(Room room) @@ -69,8 +94,8 @@ namespace osu.Game.Screens.Multi.Components Room = room; RelativeSizeAxes = Axes.X; - Height = height + selection_border_width * 2; - CornerRadius = corner_radius + selection_border_width / 2; + Height = height + SELECTION_BORDER_WIDTH * 2; + CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2; Masking = true; // create selectionBox here so State can be set before being loaded @@ -79,17 +104,16 @@ namespace osu.Game.Screens.Multi.Components RelativeSizeAxes = Axes.Both, Alpha = 0f, }; - - Action += () => State = SelectionState.Selected; } [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationEngine localisation) + private void load(OsuColour colours) { Box sideStrip; - Container coverContainer; - OsuSpriteText name, status, beatmapTitle, beatmapDash, beatmapArtist; + UpdateableBeatmapSetCover cover; + OsuSpriteText name, status; ParticipantInfo participantInfo; + BeatmapTitle beatmapTitle; ModeTypeInfo modeTypeInfo; Children = new Drawable[] @@ -98,7 +122,7 @@ namespace osu.Game.Screens.Multi.Components new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(selection_border_width), + Padding = new MarginPadding(SELECTION_BORDER_WIDTH), Child = new Container { RelativeSizeAxes = Axes.Both, @@ -122,24 +146,12 @@ namespace osu.Game.Screens.Multi.Components RelativeSizeAxes = Axes.Y, Width = side_strip_width, }, - new Container + cover = new UpdateableBeatmapSetCover { Width = cover_width, RelativeSizeAxes = Axes.Y, Masking = true, Margin = new MarginPadding { Left = side_strip_width }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - coverContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }, }, new Container { @@ -181,30 +193,10 @@ namespace osu.Game.Screens.Multi.Components TextSize = 14, Font = @"Exo2.0-Bold", }, - new FillFlowContainer + beatmapTitle = new BeatmapTitle { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Colour = colours.Gray9, - Direction = FillDirection.Horizontal, - Children = new[] - { - beatmapTitle = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-BoldItalic", - }, - beatmapDash = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-BoldItalic", - }, - beatmapArtist = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-RegularItalic", - }, - }, + TextSize = 14, + Colour = colours.Gray9 }, }, }, @@ -230,39 +222,14 @@ namespace osu.Game.Screens.Multi.Components status.Text = s.Message; foreach (Drawable d in new Drawable[] { selectionBox, sideStrip, status }) - d.FadeColour(s.GetAppropriateColour(colours), 100); + d.FadeColour(s.GetAppropriateColour(colours), transition_duration); }; beatmapBind.ValueChanged += b => { + cover.BeatmapSet = b?.BeatmapSet; + beatmapTitle.Beatmap = b; modeTypeInfo.Beatmap = b; - - if (b != null) - { - coverContainer.FadeIn(transition_duration); - - LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), - }, coverContainer.Add); - - beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title); - beatmapDash.Text = @" - "; - beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist); - } - else - { - coverContainer.FadeOut(transition_duration); - - beatmapTitle.Current = null; - beatmapArtist.Current = null; - - beatmapTitle.Text = "Changing map"; - beatmapDash.Text = beatmapArtist.Text = string.Empty; - } }; nameBind.BindTo(Room.Name); @@ -272,5 +239,22 @@ namespace osu.Game.Screens.Multi.Components beatmapBind.BindTo(Room.Beatmap); participantsBind.BindTo(Room.Participants); } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(transition_duration); + } + + protected override bool OnClick(InputState state) + { + if (Enabled.Value) + { + Action?.Invoke(this); + State = SelectionState.Selected; + } + + return true; + } } } diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index e3aba685a7..e2d0268090 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -64,6 +64,7 @@ namespace osu.Game.Screens.Multi.Components AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(5f, 0f), + LayoutDuration = 100, Children = new[] { rulesetContainer = new Container diff --git a/osu.Game/Screens/Multi/Components/ParticipantCount.cs b/osu.Game/Screens/Multi/Components/ParticipantCount.cs new file mode 100644 index 0000000000..e7183cbd92 --- /dev/null +++ b/osu.Game/Screens/Multi/Components/ParticipantCount.cs @@ -0,0 +1,79 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Multi.Components +{ + public class ParticipantCount : FillFlowContainer + { + private const float text_size = 30; + private const float transition_duration = 100; + + private readonly OsuSpriteText count, slash, maxText; + + public int Count + { + set => count.Text = value.ToString(); + } + + private int? max; + public int? Max + { + get => max; + set + { + if (value == max) return; + max = value; + + updateMax(); + } + } + + public ParticipantCount() + { + AutoSizeAxes = Axes.Both; + Direction = FillDirection.Horizontal; + LayoutDuration = transition_duration; + + Children = new[] + { + count = new OsuSpriteText + { + TextSize = text_size, + Font = @"Exo2.0-Bold" + }, + slash = new OsuSpriteText + { + Text = @"/", + TextSize = text_size, + Font = @"Exo2.0-Light" + }, + maxText = new OsuSpriteText + { + TextSize = text_size, + Font = @"Exo2.0-Light" + }, + }; + + updateMax(); + } + + private void updateMax() + { + if (Max == null) + { + slash.FadeOut(transition_duration); + maxText.FadeOut(transition_duration); + } + else + { + slash.FadeIn(transition_duration); + maxText.Text = Max.ToString(); + maxText.FadeIn(transition_duration); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs index 3bd054b042..22bca1efc7 100644 --- a/osu.Game/Screens/Multi/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Components/RoomInspector.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; @@ -10,7 +11,6 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -34,13 +34,15 @@ namespace osu.Game.Screens.Multi.Components private readonly Bindable typeBind = new Bindable(); private readonly Bindable beatmapBind = new Bindable(); private readonly Bindable maxParticipantsBind = new Bindable(); - private readonly Bindable participantsBind = new Bindable(); + private readonly Bindable> participantsBind = new Bindable>(); private OsuColour colours; private Box statusStrip; - private Container coverContainer; - private FillFlowContainer topFlow, participantsFlow, participantNumbersFlow, infoPanelFlow; + private UpdateableBeatmapSetCover cover; + private ParticipantCount participantCount; + private FillFlowContainer topFlow, participantsFlow; private OsuSpriteText name, status; + private BeatmapTypeInfo beatmapTypeInfo; private ScrollContainer participantsScroll; private ParticipantInfo participantInfo; @@ -77,13 +79,10 @@ namespace osu.Game.Screens.Multi.Components } [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationEngine localisation) + private void load(OsuColour colours) { this.colours = colours; - ModeTypeInfo modeTypeInfo; - OsuSpriteText participants, participantsSlash, maxParticipants, beatmapTitle, beatmapDash, beatmapArtist, beatmapAuthor; - Children = new Drawable[] { new Box @@ -105,21 +104,9 @@ namespace osu.Game.Screens.Multi.Components Masking = true, Children = new Drawable[] { - new Container + cover = new UpdateableBeatmapSetCover { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - coverContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - }, }, new Box { @@ -132,32 +119,10 @@ namespace osu.Game.Screens.Multi.Components Padding = new MarginPadding(20), Children = new Drawable[] { - participantNumbersFlow = new FillFlowContainer + participantCount = new ParticipantCount { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - LayoutDuration = transition_duration, - Children = new[] - { - participants = new OsuSpriteText - { - TextSize = 30, - Font = @"Exo2.0-Bold" - }, - participantsSlash = new OsuSpriteText - { - Text = @"/", - TextSize = 30, - Font = @"Exo2.0-Light" - }, - maxParticipants = new OsuSpriteText - { - TextSize = 30, - Font = @"Exo2.0-Light" - }, - }, }, name = new OsuSpriteText { @@ -200,54 +165,7 @@ namespace osu.Game.Screens.Multi.Components TextSize = 14, Font = @"Exo2.0-Bold", }, - infoPanelFlow = new FillFlowContainer - { - AutoSizeAxes = Axes.X, - Height = 30, - Direction = FillDirection.Horizontal, - LayoutDuration = transition_duration, - Spacing = new Vector2(5f, 0f), - Children = new Drawable[] - { - modeTypeInfo = new ModeTypeInfo(), - new Container - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = 5 }, - Children = new[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - beatmapTitle = new OsuSpriteText - { - Font = @"Exo2.0-BoldItalic", - }, - beatmapDash = new OsuSpriteText - { - Font = @"Exo2.0-BoldItalic", - }, - beatmapArtist = new OsuSpriteText - { - Font = @"Exo2.0-RegularItalic", - }, - }, - }, - beatmapAuthor = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - TextSize = 14, - Colour = colours.Gray9, - }, - }, - }, - }, - }, + beatmapTypeInfo = new BeatmapTypeInfo(), }, }, }, @@ -285,61 +203,19 @@ namespace osu.Game.Screens.Multi.Components nameBind.ValueChanged += n => name.Text = n; hostBind.ValueChanged += h => participantInfo.Host = h; - typeBind.ValueChanged += t => modeTypeInfo.Type = t; + typeBind.ValueChanged += t => beatmapTypeInfo.Type = t; + maxParticipantsBind.ValueChanged += m => participantCount.Max = m; statusBind.ValueChanged += displayStatus; beatmapBind.ValueChanged += b => { - modeTypeInfo.Beatmap = b; - - if (b != null) - { - coverContainer.FadeIn(transition_duration); - - LoadComponentAsync(new BeatmapSetCover(b.BeatmapSet) - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), - }, coverContainer.Add); - - beatmapTitle.Current = localisation.GetUnicodePreference(b.Metadata.TitleUnicode, b.Metadata.Title); - beatmapDash.Text = @" - "; - beatmapArtist.Current = localisation.GetUnicodePreference(b.Metadata.ArtistUnicode, b.Metadata.Artist); - beatmapAuthor.Text = $"mapped by {b.Metadata.Author}"; - } - else - { - coverContainer.FadeOut(transition_duration); - - beatmapTitle.Current = null; - beatmapArtist.Current = null; - - beatmapTitle.Text = "Changing map"; - beatmapDash.Text = beatmapArtist.Text = beatmapAuthor.Text = string.Empty; - } - }; - - maxParticipantsBind.ValueChanged += m => - { - if (m == null) - { - participantsSlash.FadeOut(transition_duration); - maxParticipants.FadeOut(transition_duration); - } - else - { - participantsSlash.FadeIn(transition_duration); - maxParticipants.FadeIn(transition_duration); - maxParticipants.Text = m.ToString(); - } + cover.BeatmapSet = b?.BeatmapSet; + beatmapTypeInfo.Beatmap = b; }; participantsBind.ValueChanged += p => { - participants.Text = p.Length.ToString(); + participantCount.Count = p.Count(); participantInfo.Participants = p; participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u)); }; @@ -367,10 +243,10 @@ namespace osu.Game.Screens.Multi.Components { if (Room == null) { - coverContainer.FadeOut(transition_duration); + cover.BeatmapSet = null; participantsFlow.FadeOut(transition_duration); - participantNumbersFlow.FadeOut(transition_duration); - infoPanelFlow.FadeOut(transition_duration); + participantCount.FadeOut(transition_duration); + beatmapTypeInfo.FadeOut(transition_duration); name.FadeOut(transition_duration); participantInfo.FadeOut(transition_duration); @@ -379,8 +255,8 @@ namespace osu.Game.Screens.Multi.Components else { participantsFlow.FadeIn(transition_duration); - participantNumbersFlow.FadeIn(transition_duration); - infoPanelFlow.FadeIn(transition_duration); + participantCount.FadeIn(transition_duration); + beatmapTypeInfo.FadeIn(transition_duration); name.FadeIn(transition_duration); participantInfo.FadeIn(transition_duration); diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index db8898495f..46610a36d8 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -10,6 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.SearchableList; +using osu.Game.Screens.Multi.Screens; using OpenTK; using OpenTK.Graphics; @@ -19,7 +20,7 @@ namespace osu.Game.Screens.Multi { public const float HEIGHT = 121; - private readonly OsuSpriteText screenTitle; + private readonly OsuSpriteText screenType; private readonly HeaderBreadcrumbControl breadcrumbs; public Header(Screen initialScreen) @@ -66,7 +67,7 @@ namespace osu.Game.Screens.Multi Text = "multiplayer ", TextSize = 25, }, - screenTitle = new OsuSpriteText + screenType = new OsuSpriteText { TextSize = 25, Font = @"Exo2.0-Light", @@ -85,14 +86,14 @@ namespace osu.Game.Screens.Multi }, }; - breadcrumbs.Current.ValueChanged += s => screenTitle.Text = s.ToString(); + breadcrumbs.Current.ValueChanged += s => screenType.Text = ((MultiplayerScreen)s).Type.ToLower(); breadcrumbs.Current.TriggerChange(); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - screenTitle.Colour = colours.Yellow; + screenType.Colour = colours.Yellow; breadcrumbs.StripColour = colours.Green; } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index b3d393209c..7822fa68dc 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -8,7 +8,8 @@ using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; -using osu.Game.Screens.Multi.Screens; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Multi.Screens.Lounge; namespace osu.Game.Screens.Multi { @@ -25,7 +26,7 @@ namespace osu.Game.Screens.Multi RelativeSizeAxes = Axes.Both, }; - Lobby lobby; + Lounge lounge; Children = new Drawable[] { new Container @@ -52,12 +53,12 @@ namespace osu.Game.Screens.Multi { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Child = lobby = new Lobby(), + Child = lounge = new Lounge(), }, - new Header(lobby), + new Header(lounge), }; - lobby.Exited += s => Exit(); + lounge.Exited += s => Exit(); } protected override void OnEntering(Screen last) @@ -84,6 +85,13 @@ namespace osu.Game.Screens.Multi waves.Hide(); } + protected override void LogoExiting(OsuLogo logo) + { + // the wave overlay transition takes longer than expected to run. + logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut(); + base.LogoExiting(logo); + } + private class MultiplayerWaveContainer : WaveContainer { protected override bool StartHidden => true; diff --git a/osu.Game/Screens/Multi/Screens/Lobby.cs b/osu.Game/Screens/Multi/Screens/Lobby.cs deleted file mode 100644 index dcda40e0d7..0000000000 --- a/osu.Game/Screens/Multi/Screens/Lobby.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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; - -namespace osu.Game.Screens.Multi.Screens -{ - public class Lobby : ScreenWhiteBox - { - protected override IEnumerable PossibleChildren => new[] { - typeof(MatchCreate), - typeof(Match) - }; - } -} diff --git a/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs b/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs new file mode 100644 index 0000000000..421c89479a --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using OpenTK.Graphics; + +namespace osu.Game.Screens.Multi.Screens.Lounge +{ + public class FilterControl : SearchableListFilterControl + { + protected override Color4 BackgroundColour => OsuColour.FromHex(@"362e42"); + protected override LoungeTab DefaultTab => LoungeTab.Public; + + public FilterControl() + { + DisplayStyleControl.Hide(); + } + } + + public enum LoungeTab + { + Public = RoomAvailability.Public, + Private = RoomAvailability.FriendsOnly, + } +} diff --git a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs new file mode 100644 index 0000000000..016babcaa5 --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs @@ -0,0 +1,191 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Framework.Screens; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using osu.Game.Screens.Multi.Components; +using OpenTK; + +namespace osu.Game.Screens.Multi.Screens.Lounge +{ + public class Lounge : MultiplayerScreen + { + private readonly Container content; + private readonly SearchContainer search; + + protected readonly FilterControl Filter; + protected readonly FillFlowContainer RoomsContainer; + protected readonly RoomInspector Inspector; + + public override string Title => "Lounge"; + + protected override Container TransitionContent => content; + + private IEnumerable rooms; + public IEnumerable Rooms + { + get { return rooms; } + set + { + if (Equals(value, rooms)) return; + rooms = value; + + var enumerable = rooms.ToList(); + + RoomsContainer.Children = enumerable.Select(r => new DrawableRoom(r) + { + Action = didSelect, + }).ToList(); + + if (!enumerable.Contains(Inspector.Room)) + Inspector.Room = null; + + filterRooms(); + } + } + + public Lounge() + { + Children = new Drawable[] + { + Filter = new FilterControl(), + content = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Width = 0.55f, + Padding = new MarginPadding + { + Vertical = 35 - DrawableRoom.SELECTION_BORDER_WIDTH, + Right = 20 - DrawableRoom.SELECTION_BORDER_WIDTH + }, + Child = search = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = RoomsContainer = new RoomsFilterContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10 - DrawableRoom.SELECTION_BORDER_WIDTH * 2), + }, + }, + }, + Inspector = new RoomInspector + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.45f, + }, + }, + }, + }; + + Filter.Search.Current.ValueChanged += s => filterRooms(); + Filter.Tabs.Current.ValueChanged += t => filterRooms(); + Filter.Search.Exit += Exit; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + content.Padding = new MarginPadding + { + Top = Filter.DrawHeight, + Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH, + Right = SearchableListOverlay.WIDTH_PADDING, + }; + } + + protected override void OnFocus(InputState state) + { + GetContainingInputManager().ChangeFocus(Filter.Search); + } + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + Filter.Search.HoldFocus = true; + } + + protected override bool OnExiting(Screen next) + { + Filter.Search.HoldFocus = false; + return base.OnExiting(next); + } + + protected override void OnResuming(Screen last) + { + base.OnResuming(last); + Filter.Search.HoldFocus = true; + } + + protected override void OnSuspending(Screen next) + { + base.OnSuspending(next); + Filter.Search.HoldFocus = false; + } + + private void filterRooms() + { + search.SearchTerm = Filter.Search.Current.Value ?? string.Empty; + + foreach (DrawableRoom r in RoomsContainer.Children) + { + r.MatchingFilter = r.MatchingFilter && + r.Room.Availability.Value == (RoomAvailability)Filter.Tabs.Current.Value; + } + } + + private void didSelect(DrawableRoom room) + { + RoomsContainer.Children.ForEach(c => + { + if (c != room) + c.State = SelectionState.NotSelected; + }); + + Inspector.Room = room.Room; + + // open the room if its selected and is clicked again + if (room.State == SelectionState.Selected) + Push(new Match()); + } + + private class RoomsFilterContainer : FillFlowContainer, IHasFilterableChildren + { + public IEnumerable FilterTerms => new string[] { }; + public IEnumerable FilterableChildren => Children; + + public bool MatchingFilter + { + set + { + if (value) + InvalidateLayout(); + } + } + + public RoomsFilterContainer() + { + LayoutDuration = 200; + LayoutEasing = Easing.OutQuint; + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs b/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs new file mode 100644 index 0000000000..fa9b40684c --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs @@ -0,0 +1,57 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Multi.Screens +{ + public abstract class MultiplayerScreen : OsuScreen + { + private const Easing in_easing = Easing.OutQuint; + private const Easing out_easing = Easing.InSine; + + protected virtual Container TransitionContent => Content; + + /// + /// The type to display in the title of the . + /// + public virtual string Type => Title; + + protected override void OnEntering(Screen last) + { + base.OnEntering(last); + + TransitionContent.MoveToX(200); + + TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, in_easing); + TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing); + } + + protected override bool OnExiting(Screen next) + { + Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing); + TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, out_easing); + + return base.OnExiting(next); + } + + protected override void OnResuming(Screen last) + { + base.OnResuming(last); + + Content.FadeIn(WaveContainer.APPEAR_DURATION, in_easing); + TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, in_easing); + } + + protected override void OnSuspending(Screen next) + { + base.OnSuspending(next); + + Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, out_easing); + TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, out_easing); + } + } +} diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 4b1562291b..61018f9e08 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -17,13 +18,22 @@ using osu.Game.Rulesets; using osu.Game.Screens.Menu; using OpenTK; using OpenTK.Input; +using osu.Game.Overlays; +using osu.Framework.Graphics.Containers; namespace osu.Game.Screens { - public abstract class OsuScreen : Screen, IKeyBindingHandler + public abstract class OsuScreen : Screen, IKeyBindingHandler, IHasDescription { public BackgroundScreen Background { get; private set; } + /// + /// A user-facing title for this screen. + /// + public virtual string Title => GetType().ShortDisplayName(); + + public string Description => Title; + protected virtual bool AllowBackButton => true; /// @@ -32,19 +42,19 @@ namespace osu.Game.Screens /// protected virtual BackgroundScreen CreateBackground() => null; - private readonly BindableBool hideOverlaysOnEnter = new BindableBool(); + private Action updateOverlayStates; /// - /// Whether overlays should be hidden when this screen is entered or resumed. + /// Whether all overlays should be hidden when this screen is entered or resumed. /// - protected virtual bool HideOverlaysOnEnter => hideOverlaysOnEnter; + protected virtual bool HideOverlaysOnEnter => false; - private readonly BindableBool allowOpeningOverlays = new BindableBool(); + protected readonly Bindable OverlayActivationMode = new Bindable(); /// - /// Whether overlays should be able to be opened while this screen is active. + /// Whether overlays should be able to be opened once this screen is entered or resumed. /// - protected virtual bool AllowOpeningOverlays => allowOpeningOverlays; + protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; /// /// Whether this allows the cursor to be displayed. @@ -67,36 +77,28 @@ namespace osu.Game.Screens private ParallaxContainer backgroundParallaxContainer; - public WorkingBeatmap InitialBeatmap - { - set - { - if (IsLoaded) throw new InvalidOperationException($"Cannot set {nameof(InitialBeatmap)} post-load."); - Beatmap.Value = value; - } - } - protected readonly Bindable Ruleset = new Bindable(); private SampleChannel sampleExit; - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuGameBase game, OsuGame osuGame, AudioManager audio) + [BackgroundDependencyLoader(true)] + private void load(BindableBeatmap beatmap, OsuGame osuGame, AudioManager audio) { - if (game != null) - { - //if we were given a beatmap at ctor time, we want to pass this on to the game-wide beatmap. - var localMap = Beatmap.Value; - Beatmap.BindTo(game.Beatmap); - if (localMap != null) - Beatmap.Value = localMap; - } + if (beatmap != null) + Beatmap.BindTo(beatmap); if (osuGame != null) { Ruleset.BindTo(osuGame.Ruleset); - hideOverlaysOnEnter.BindTo(osuGame.HideOverlaysOnEnter); - allowOpeningOverlays.BindTo(osuGame.AllowOpeningOverlays); + OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); + + updateOverlayStates = () => + { + if (HideOverlaysOnEnter) + osuGame.CloseAllOverlays(); + else + osuGame.Toolbar.State = Visibility.Visible; + }; } sampleExit = audio.Sample.Get(@"UI/screen-back"); @@ -217,19 +219,24 @@ namespace osu.Game.Screens logo.Anchor = Anchor.TopLeft; logo.Origin = Anchor.Centre; logo.RelativePositionAxes = Axes.None; + logo.BeatMatching = true; logo.Triangles = true; logo.Ripple = true; } private void applyArrivingDefaults(bool isResuming) { - logo.AppendAnimatingAction(() => LogoArriving(logo, isResuming), true); + logo.AppendAnimatingAction(() => + { + if (IsCurrentScreen) LogoArriving(logo, isResuming); + }, true); if (backgroundParallaxContainer != null) backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount; - hideOverlaysOnEnter.Value = HideOverlaysOnEnter; - allowOpeningOverlays.Value = AllowOpeningOverlays; + OverlayActivationMode.Value = InitialOverlayActivationMode; + + updateOverlayStates?.Invoke(); } private void onExitingLogo() diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs index bd334ad2e2..6262f71ddc 100644 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ b/osu.Game/Screens/Play/PauseContainer.cs @@ -44,13 +44,18 @@ namespace osu.Game.Screens.Play public Action OnResume; public Action OnPause; - private readonly IAdjustableClock adjustableClock; private readonly FramedClock framedClock; + private readonly DecoupleableInterpolatingFramedClock decoupledClock; - public PauseContainer(FramedClock framedClock, IAdjustableClock adjustableClock) + /// + /// Creates a new . + /// + /// The gameplay clock. This is the clock that will process frames. + /// The seekable clock. This is the clock that will be paused and resumed. + public PauseContainer(FramedClock framedClock, DecoupleableInterpolatingFramedClock decoupledClock) { this.framedClock = framedClock; - this.adjustableClock = adjustableClock; + this.decoupledClock = decoupledClock; RelativeSizeAxes = Axes.Both; @@ -80,7 +85,7 @@ namespace osu.Game.Screens.Play if (IsPaused) return; // stop the seekable clock (stops the audio eventually) - adjustableClock.Stop(); + decoupledClock.Stop(); IsPaused = true; OnPause?.Invoke(); @@ -97,10 +102,10 @@ namespace osu.Game.Screens.Play IsResuming = false; lastPauseActionTime = Time.Current; - // seek back to the time of the framed clock. - // this accounts for the audio clock potentially taking time to enter a completely stopped state. - adjustableClock.Seek(framedClock.CurrentTime); - adjustableClock.Start(); + // Seeking the decoupled clock to its current time ensures that its source clock will be seeked to the same time + // This accounts for the audio clock source potentially taking time to enter a completely stopped state + decoupledClock.Seek(decoupledClock.CurrentTime); + decoupledClock.Start(); OnResume?.Invoke(); pauseOverlay.Hide(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0150d76251..fe77f85a50 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -21,6 +22,7 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -37,6 +39,8 @@ namespace osu.Game.Screens.Play protected override bool HideOverlaysOnEnter => true; + protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + public Action RestartRequested; public bool HasFailed { get; private set; } @@ -70,7 +74,7 @@ namespace osu.Game.Screens.Play private SampleChannel sampleRestart; - private ScoreProcessor scoreProcessor; + protected ScoreProcessor ScoreProcessor; protected RulesetContainer RulesetContainer; private HUDOverlay hudOverlay; @@ -117,7 +121,7 @@ namespace osu.Game.Screens.Play // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap); + RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap.Value); } if (!RulesetContainer.Objects.Any()) @@ -143,13 +147,17 @@ namespace osu.Game.Screens.Play adjustableClock.ProcessFrame(); + // Lazer's audio timings in general doesn't match stable. This is the result of user testing, albeit limited. + // This only seems to be required on windows. We need to eventually figure out why, with a bit of luck. + var platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 }; + // the final usable gameplay clock with user-set offsets applied. - var offsetClock = new FramedOffsetClock(adjustableClock); + var offsetClock = new FramedOffsetClock(platformOffsetClock); userAudioOffset.ValueChanged += v => offsetClock.Offset = v; userAudioOffset.TriggerChange(); - scoreProcessor = RulesetContainer.CreateScoreProcessor(); + ScoreProcessor = RulesetContainer.CreateScoreProcessor(); Children = new Drawable[] { @@ -176,7 +184,7 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Child = RulesetContainer }, - new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor) + new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -184,7 +192,7 @@ namespace osu.Game.Screens.Play Breaks = beatmap.Breaks }, RulesetContainer.Cursor?.CreateProxy() ?? new Container(), - hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) + hudOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { Clock = Clock, // hud overlay doesn't want to use the audio clock directly ProcessCustomClock = false, @@ -225,11 +233,11 @@ namespace osu.Game.Screens.Play initializeStoryboard(false); // Bind ScoreProcessor to ourselves - scoreProcessor.AllJudged += onCompletion; - scoreProcessor.Failed += onFail; + ScoreProcessor.AllJudged += onCompletion; + ScoreProcessor.Failed += onFail; foreach (var mod in Beatmap.Value.Mods.Value.OfType()) - mod.ApplyToScoreProcessor(scoreProcessor); + mod.ApplyToScoreProcessor(ScoreProcessor); } private void applyRateFromMods() @@ -254,7 +262,7 @@ namespace osu.Game.Screens.Play private void onCompletion() { // Only show the completion screen if the player hasn't failed - if (scoreProcessor.HasFailed || onCompletionEvent != null) + if (ScoreProcessor.HasFailed || onCompletionEvent != null) return; ValidForResume = false; @@ -272,7 +280,7 @@ namespace osu.Game.Screens.Play Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = ruleset }; - scoreProcessor.PopulateScore(score); + ScoreProcessor.PopulateScore(score); score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value; Push(new Results(score)); }); @@ -364,7 +372,7 @@ namespace osu.Game.Screens.Play Background?.FadeTo(1f, fade_out_duration); } - protected override bool OnWheel(InputState state) => mouseWheelDisabled.Value && !pauseContainer.IsPaused; + protected override bool OnScroll(InputState state) => mouseWheelDisabled.Value && !pauseContainer.IsPaused; private void initializeStoryboard(bool asyncLoad) { diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 734837a4f1..6e1c3d24f0 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -7,15 +7,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; +using osu.Framework.Localisation; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using OpenTK; -using osu.Framework.Localisation; -using osu.Framework.Threading; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; +using OpenTK; namespace osu.Game.Screens.Play { @@ -44,18 +44,26 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load() { - Add(info = new BeatmapMetadataDisplay(Beatmap) + Add(info = new BeatmapMetadataDisplay(Beatmap.Value) { Alpha = 0, Anchor = Anchor.Centre, Origin = Anchor.Centre, }); - Add(new VisualSettings + Add(new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding(25) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] + { + new VisualSettings(), + new InputSettings() + } }); loadTask = LoadComponentAsync(player); @@ -201,8 +209,11 @@ namespace osu.Game.Screens.Play { base.Dispose(isDisposing); - // if the player never got pushed, we should explicitly dispose it. - loadTask?.ContinueWith(_ => player.Dispose()); + if (isDisposing) + { + // if the player never got pushed, we should explicitly dispose it. + loadTask?.ContinueWith(_ => player.Dispose()); + } } private class BeatmapMetadataDisplay : Container diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs new file mode 100644 index 0000000000..755ba468cc --- /dev/null +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -0,0 +1,30 @@ +// 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.Configuration; + +namespace osu.Game.Screens.Play.PlayerSettings +{ + public class InputSettings : PlayerSettingsGroup + { + protected override string Title => "Input settings"; + + private readonly PlayerCheckbox mouseButtonsCheckbox; + + public InputSettings() + { + Children = new Drawable[] + { + mouseButtonsCheckbox = new PlayerCheckbox + { + LabelText = "Disable mouse buttons" + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) => mouseButtonsCheckbox.Bindable = config.GetBindable(OsuSetting.MouseDisableButtons); + } +} diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 0ffcdf94a6..cff3eca895 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -50,11 +50,11 @@ namespace osu.Game.Screens.Play.PlayerSettings content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); } - button.FadeColour(expanded ? buttonActiveColour : Color4.White, 200, Easing.OutQuint); + updateExpanded(); } } - private Color4 buttonActiveColour; + private Color4 expandedColour; protected PlayerSettingsGroup() { @@ -130,9 +130,13 @@ namespace osu.Game.Screens.Play.PlayerSettings [BackgroundDependencyLoader] private void load(OsuColour colours) { - button.Colour = buttonActiveColour = colours.Yellow; + expandedColour = colours.Yellow; + + updateExpanded(); } + private void updateExpanded() => button.FadeColour(expanded ? expandedColour : Color4.White, 200, Easing.InOutQuint); + protected override Container Content => content; protected override bool OnHover(InputState state) => true; diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 1ccc5e2fe8..7f18305b1c 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Play { public abstract class ScreenWithBeatmapBackground : OsuScreen { - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); public override bool AllowBeatmapRulesetChange => false; diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs index b79c212ade..3156a646db 100644 --- a/osu.Game/Screens/Play/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/SongProgressInfo.cs @@ -92,6 +92,6 @@ namespace osu.Game.Screens.Play } } - private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{timeSpan.Duration().TotalMinutes:N0}:{timeSpan.Duration().Seconds:D2}"; + private string formatTime(TimeSpan timeSpan) => $"{(timeSpan < TimeSpan.Zero ? "-" : "")}{Math.Floor(timeSpan.Duration().TotalMinutes)}:{timeSpan.Duration().Seconds:D2}"; } } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 32161a0b8e..56a270f559 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Ranking private static readonly Vector2 background_blur = new Vector2(20); - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); private const float overscan = 1.3f; @@ -57,6 +57,7 @@ namespace osu.Game.Screens.Ranking { base.OnEntering(last); (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint); + Background.ScaleTo(1.1f, transition_time, Easing.OutQuint); allCircles.ForEach(c => { @@ -102,6 +103,8 @@ namespace osu.Game.Screens.Ranking c.ScaleTo(0, transition_time, Easing.OutSine); }); + Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint); + Content.FadeOut(transition_time / 4); return base.OnExiting(next); @@ -160,7 +163,6 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both, ParallaxAmount = 0.01f, - Scale = new Vector2(1 / circle_outer_scale / overscan), Anchor = Anchor.Centre, Origin = Anchor.Centre, Children = new Drawable[] @@ -272,10 +274,10 @@ namespace osu.Game.Screens.Ranking switch (mode) { case ResultMode.Summary: - currentPage = new ResultsPageScore(score, Beatmap); + currentPage = new ResultsPageScore(score, Beatmap.Value); break; case ResultMode.Ranking: - currentPage = new ResultsPageRanking(score, Beatmap); + currentPage = new ResultsPageRanking(score, Beatmap.Value); break; } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index ca36f94eda..f1bd2b945f 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -6,7 +6,6 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; @@ -120,14 +119,8 @@ namespace osu.Game.Screens.Select Margin = new MarginPadding { Top = spacing * 2 }, Children = new[] { - description = new MetadataSection("Description") - { - TextColour = Color4.White.Opacity(0.75f), - }, - source = new MetadataSection("Source") - { - TextColour = Color4.White.Opacity(0.75f), - }, + description = new MetadataSection("Description"), + source = new MetadataSection("Source"), tags = new MetadataSection("Tags"), }, }, @@ -164,10 +157,9 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api) + private void load(APIAccess api) { this.api = api; - tags.TextColour = colours.Yellow; } protected override void UpdateAfterChildren() @@ -364,7 +356,7 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Colour = textFlow.Colour, + Colour = Color4.White.Opacity(0.75f), Text = text }, loaded => { @@ -375,12 +367,6 @@ namespace osu.Game.Screens.Select this.FadeIn(transition_duration); }); } - - public Color4 TextColour - { - get { return textFlow.Colour; } - set { textFlow.Colour = value; } - } } private class DimmedLoadingAnimation : VisibilityContainer diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 97f6371cb2..7950018554 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select { if (osuGame != null) ruleset.BindTo(osuGame.Ruleset); - ruleset.ValueChanged += updateRuleset; + ruleset.ValueChanged += _ => updateDisplay(); } protected override bool BlockPassThroughMouse => false; @@ -78,66 +78,76 @@ namespace osu.Game.Screens.Select private WorkingBeatmap beatmap; - public void UpdateBeatmap(WorkingBeatmap beatmap) + public WorkingBeatmap Beatmap { - this.beatmap = beatmap; - loadBeatmap(); + get => beatmap; + set + { + if (beatmap == value) return; + + beatmap = value; + updateDisplay(); + } } - private void updateRuleset(RulesetInfo ruleset) => loadBeatmap(); + private BufferedWedgeInfo loadingInfo; - private void loadBeatmap() + private void updateDisplay() { - void updateState() + void removeOldInfo() { State = beatmap == null ? Visibility.Hidden : Visibility.Visible; Info?.FadeOut(250); Info?.Expire(); + Info = null; } if (beatmap == null) { - updateState(); + removeOldInfo(); return; } - LoadComponentAsync(new BufferedWedgeInfo(beatmap, ruleset.Value) + LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value) { Shear = -Shear, - Depth = Info?.Depth + 1 ?? 0, - }, newInfo => + Depth = Info?.Depth + 1 ?? 0 + }, loaded => { - updateState(); - Add(Info = newInfo); + // ensure we are the most recent loaded wedge. + if (loaded != loadingInfo) return; + + removeOldInfo(); + Add(Info = loaded); }); } public class BufferedWedgeInfo : BufferedContainer { - private readonly WorkingBeatmap working; public OsuSpriteText VersionLabel { get; private set; } public OsuSpriteText TitleLabel { get; private set; } public OsuSpriteText ArtistLabel { get; private set; } public FillFlowContainer MapperContainer { get; private set; } public FillFlowContainer InfoLabelContainer { get; private set; } + private UnicodeBindableString titleBinding; private UnicodeBindableString artistBinding; + private readonly WorkingBeatmap beatmap; private readonly RulesetInfo ruleset; - public BufferedWedgeInfo(WorkingBeatmap working, RulesetInfo userRuleset) + public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset) { - this.working = working; - - ruleset = userRuleset ?? working.BeatmapInfo.Ruleset; + this.beatmap = beatmap; + ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; } [BackgroundDependencyLoader] private void load(LocalisationEngine localisation) { - var beatmapInfo = working.BeatmapInfo; - var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + var beatmapInfo = beatmap.BeatmapInfo; + var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); PixelSnapping = true; CacheDrawnFrameBuffer = true; @@ -165,7 +175,7 @@ namespace osu.Game.Screens.Select Children = new[] { // Zoomed-in and cropped beatmap background - new BeatmapBackgroundSprite(working) + new BeatmapBackgroundSprite(beatmap) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -248,27 +258,27 @@ namespace osu.Game.Screens.Select private InfoLabel[] getInfoLabels() { - var beatmap = working.Beatmap; + var b = beatmap.Beatmap; List labels = new List(); - if (beatmap?.HitObjects?.Any() == true) + if (b?.HitObjects?.Any() == true) { - HitObject lastObject = beatmap.HitObjects.LastOrDefault(); + HitObject lastObject = b.HitObjects.LastOrDefault(); double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", Icon = FontAwesome.fa_clock_o, - Content = TimeSpan.FromMilliseconds(endTime - beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"), + Content = TimeSpan.FromMilliseconds(endTime - b.HitObjects.First().StartTime).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", Icon = FontAwesome.fa_circle, - Content = getBPMRange(beatmap), + Content = getBPMRange(b), })); IBeatmap playableBeatmap; @@ -276,12 +286,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = working.GetPlayableBeatmap(ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = working.GetPlayableBeatmap(working.BeatmapInfo.Ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset); } labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index f39952dc31..c5996327b9 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Select.Carousel if (songSelect != null) { - startRequested = songSelect.FinaliseSelection; + startRequested = b => songSelect.FinaliseSelection(b); editRequested = songSelect.Edit; } diff --git a/osu.Game/Screens/Select/EditSongSelect.cs b/osu.Game/Screens/Select/EditSongSelect.cs index bca009e2c1..e1d71fdd05 100644 --- a/osu.Game/Screens/Select/EditSongSelect.cs +++ b/osu.Game/Screens/Select/EditSongSelect.cs @@ -7,7 +7,7 @@ namespace osu.Game.Screens.Select { protected override bool ShowFooter => false; - protected override bool OnSelectionFinalised() + protected override bool OnStart() { Exit(); return true; diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs index baab973ae5..19732107c7 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs @@ -8,6 +8,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Game.Graphics; @@ -142,6 +143,8 @@ namespace osu.Game.Screens.Select.Leaderboards { flagBadgeContainer = new Container { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, Size = new Vector2(87f, 20f), Masking = true, Children = new Drawable[] @@ -155,14 +158,16 @@ namespace osu.Game.Screens.Select.Leaderboards }, new FillFlowContainer { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), - Margin = new MarginPadding { Left = edge_margin, }, + Margin = new MarginPadding { Left = edge_margin }, Children = new Drawable[] { - maxCombo = new ScoreComponentLabel(FontAwesome.fa_link, Score.MaxCombo.ToString()), - accuracy = new ScoreComponentLabel(FontAwesome.fa_crosshairs, string.Format(Score.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", Score.Accuracy)), + maxCombo = new ScoreComponentLabel(FontAwesome.fa_link, Score.MaxCombo.ToString(), "Max Combo"), + accuracy = new ScoreComponentLabel(FontAwesome.fa_crosshairs, string.Format(Score.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", Score.Accuracy), "Accuracy"), }, }, }, @@ -305,37 +310,61 @@ namespace osu.Game.Screens.Select.Leaderboards } } - private class ScoreComponentLabel : Container + private class ScoreComponentLabel : Container, IHasTooltip { - public ScoreComponentLabel(FontAwesome icon, string value) - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - Size = new Vector2(60f, 20f); - Padding = new MarginPadding { Top = 10f, }; + private const float icon_size = 20; - Children = new Drawable[] + private readonly string name; + private readonly FillFlowContainer content; + + public override bool Contains(Vector2 screenSpacePos) => content.Contains(screenSpacePos); + + public string TooltipText => name; + + public ScoreComponentLabel(FontAwesome icon, string value, string name) + { + this.name = name; + AutoSizeAxes = Axes.Y; + Width = 60; + + Child = content = new FillFlowContainer { - new SpriteIcon + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] { - Origin = Anchor.Centre, - Icon = FontAwesome.fa_square, - Colour = OsuColour.FromHex(@"3087ac"), - Rotation = 45, - Size = new Vector2(20), - Shadow = true, - }, - new SpriteIcon - { - Origin = Anchor.Centre, - Icon = icon, - Colour = OsuColour.FromHex(@"a4edff"), - Size = new Vector2(14), - }, - new GlowingSpriteText(value, @"Exo2.0-Bold", 17, Color4.White, OsuColour.FromHex(@"83ccfa")) - { - Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Left = 15, }, + new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Children = new[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(icon_size), + Rotation = 45, + Colour = OsuColour.FromHex(@"3087ac"), + Icon = FontAwesome.fa_square, + Shadow = true, + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(icon_size - 6), + Colour = OsuColour.FromHex(@"a4edff"), + Icon = icon, + }, + }, + }, + new GlowingSpriteText(value, @"Exo2.0-Bold", 17, Color4.White, OsuColour.FromHex(@"83ccfa")) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, }, }; } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 3ffac591f3..a0c96d0cee 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -5,7 +5,7 @@ namespace osu.Game.Screens.Select { public class MatchSongSelect : SongSelect { - protected override bool OnSelectionFinalised() + protected override bool OnStart() { Schedule(() => { diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 7992930c45..8ce40fcfa0 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Select return false; } - protected override bool OnSelectionFinalised() + protected override bool OnStart() { if (player != null) return false; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 4ffa9e2a90..70b473bcd9 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Threading; using OpenTK; using OpenTK.Input; using osu.Framework.Allocation; @@ -63,10 +62,9 @@ namespace osu.Game.Screens.Select private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; - private CancellationTokenSource initialAddSetsTask; - private DependencyContainer dependencies; - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); protected SongSelect() { @@ -178,7 +176,7 @@ namespace osu.Game.Screens.Select } } - [BackgroundDependencyLoader(permitNulls: true)] + [BackgroundDependencyLoader(true)] private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) { dependencies.CacheAs(this); @@ -207,19 +205,15 @@ namespace osu.Game.Screens.Select sampleChangeDifficulty = audio.Sample.Get(@"SongSelect/select-difficulty"); sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand"); - initialAddSetsTask = new CancellationTokenSource(); + Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSetsEnumerable(); - Carousel.BeatmapSets = this.beatmaps.GetAllUsableBeatmapSets(); - - Beatmap.DisabledChanged += disabled => Carousel.AllowSelection = !disabled; - Beatmap.TriggerChange(); - - Beatmap.ValueChanged += workingBeatmapChanged; + Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); + Beatmap.BindValueChanged(workingBeatmapChanged); } public void Edit(BeatmapInfo beatmap) { - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); Push(new Editor()); } @@ -227,7 +221,8 @@ namespace osu.Game.Screens.Select /// Call to make a selection and perform the default action for this SongSelect. /// /// An optional beatmap to override the current carousel selection. - public void FinaliseSelection(BeatmapInfo beatmap = null) + /// Whether to trigger . + public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true) { // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). @@ -243,14 +238,15 @@ namespace osu.Game.Screens.Select selectionChangedDebounce = null; } - OnSelectionFinalised(); + if (performStartAction) + OnStart(); } /// /// Called when a selection is made. /// /// If a resultant action occurred that takes the user away from SongSelect. - protected abstract bool OnSelectionFinalised(); + protected abstract bool OnStart(); private ScheduledDelegate selectionChangedDebounce; @@ -283,7 +279,7 @@ namespace osu.Game.Screens.Select { bool preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); ensurePlayingSelected(preview); } @@ -370,7 +366,7 @@ namespace osu.Game.Screens.Select { if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { - UpdateBeatmap(Beatmap); + UpdateBeatmap(Beatmap.Value); ensurePlayingSelected(); } @@ -395,7 +391,7 @@ namespace osu.Game.Screens.Select protected override bool OnExiting(Screen next) { - FinaliseSelection(); + FinaliseSelection(performStartAction: false); beatmapInfoWedge.State = Visibility.Hidden; @@ -417,8 +413,6 @@ namespace osu.Game.Screens.Select beatmaps.BeatmapHidden -= onBeatmapHidden; beatmaps.BeatmapRestored -= onBeatmapRestored; } - - initialAddSetsTask?.Cancel(); } /// @@ -435,7 +429,7 @@ namespace osu.Game.Screens.Select backgroundModeBeatmap.FadeTo(1, 250); } - beatmapInfoWedge.UpdateBeatmap(beatmap); + beatmapInfoWedge.Beatmap = beatmap; } private void ensurePlayingSelected(bool preview = false) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 135b5e2b1e..d15f3053a3 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; using System.Linq; +using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables { @@ -63,14 +64,14 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(OsuGameBase game, TextureStore textureStore) + private void load(IBindableBeatmap beatmap, TextureStore textureStore) { var basePath = Animation.Path.ToLowerInvariant(); for (var frame = 0; frame < Animation.FrameCount; frame++) { var framePath = basePath.Replace(".", frame + "."); - var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; if (path == null) continue; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 9f22bebcc2..efbb3de253 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using System.Linq; +using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables { @@ -62,10 +63,10 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(OsuGameBase game, TextureStore textureStore) + private void load(IBindableBeatmap beatmap, TextureStore textureStore) { var spritePath = Sprite.Path.ToLowerInvariant(); - var path = game.Beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestCase.cs index 43b20f7021..08dc6a3bbd 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestCase.cs @@ -20,29 +20,19 @@ namespace osu.Game.Tests.Visual protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); protected readonly EditorClock Clock; - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) - => dependencies = new DependencyContainer(parent); - - private OsuGameBase osuGame; - protected EditorClockTestCase() { - Clock = new EditorClock(new ControlPointInfo(), BeatDivisor) { IsCoupled = false }; + Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - this.osuGame = osuGame; + Dependencies.Cache(BeatDivisor); + Dependencies.CacheAs(Clock); + Dependencies.CacheAs(Clock); - dependencies.Cache(BeatDivisor); - dependencies.CacheAs(Clock); - dependencies.CacheAs(Clock); - - osuGame.Beatmap.ValueChanged += beatmapChanged; - beatmapChanged(osuGame.Beatmap.Value); + Beatmap.BindValueChanged(beatmapChanged, true); } private void beatmapChanged(WorkingBeatmap working) @@ -59,21 +49,14 @@ namespace osu.Game.Tests.Visual Clock.ProcessFrame(); } - protected override bool OnWheel(InputState state) + protected override bool OnScroll(InputState state) { - if (state.Mouse.WheelDelta > 0) + if (state.Mouse.ScrollDelta.Y > 0) Clock.SeekBackward(true); else Clock.SeekForward(true); return true; } - - protected override void Dispose(bool isDisposing) - { - osuGame.Beatmap.ValueChanged -= beatmapChanged; - - base.Dispose(isDisposing); - } } } diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestCase.cs index c0b72f56a4..2ab121fcc9 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestCase.cs @@ -23,9 +23,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load() { - osuGame.Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo); + Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo); LoadComponentAsync(new Editor(), LoadScreen); } diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs index 2b677f1f42..1740658da6 100644 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ b/osu.Game/Tests/Visual/OsuTestCase.cs @@ -1,20 +1,51 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.IO; -using System.Reflection; +using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Testing; +using osu.Game.Beatmaps; namespace osu.Game.Tests.Visual { public abstract class OsuTestCase : TestCase { + private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap()); + protected BindableBeatmap Beatmap => beatmap; + + protected DependencyContainer Dependencies { get; private set; } + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) + { + Dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); + + Dependencies.CacheAs(beatmap); + Dependencies.CacheAs(beatmap); + + return Dependencies; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audioManager) + { + beatmap.SetAudioManager(audioManager); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmap != null) + { + beatmap.Disabled = true; + beatmap.Value.Track.Stop(); + } + } + protected override ITestCaseTestRunner CreateRunner() => new OsuTestCaseTestRunner(); public class OsuTestCaseTestRunner : OsuGameBase, ITestCaseTestRunner { - protected override string MainResourceFile => File.Exists(base.MainResourceFile) ? base.MainResourceFile : Assembly.GetExecutingAssembly().Location; - private TestCaseTestRunner.TestRunner runner; protected override void LoadAsyncComplete() @@ -27,5 +58,22 @@ namespace osu.Game.Tests.Visual public void RunTestBlocking(TestCase test) => runner.RunTestBlocking(test); } + + private class OsuTestBeatmap : BindableBeatmap + { + public OsuTestBeatmap(WorkingBeatmap defaultValue) + : base(defaultValue) + { + } + + public void SetAudioManager(AudioManager audioManager) => RegisterAudioManager(audioManager); + + public override BindableBeatmap GetBoundCopy() + { + var copy = new OsuTestBeatmap(Default); + copy.BindTo(this); + return copy; + } + } } } diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index 51460ecb6d..dfae8fbc1d 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual new ScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new BeatmapList(ruleset) + Child = new BeatmapList(ruleset, Beatmap) } } }, @@ -108,10 +108,12 @@ namespace osu.Game.Tests.Visual { private readonly Container beatmapDisplays; private readonly Ruleset ruleset; + private readonly BindableBeatmap beatmapBindable; - public BeatmapList(Ruleset ruleset) + public BeatmapList(Ruleset ruleset, BindableBeatmap beatmapBindable) { this.ruleset = ruleset; + this.beatmapBindable = beatmapBindable; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -130,7 +132,7 @@ namespace osu.Game.Tests.Visual var sets = beatmaps.GetAllUsableBeatmapSets(); var allBeatmaps = sets.SelectMany(s => s.Beatmaps).Where(b => ruleset.LegacyID == null || b.RulesetID == ruleset.LegacyID); - allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b))); + allBeatmaps.ForEach(b => beatmapDisplays.Add(new BeatmapDisplay(b, beatmapBindable))); } private class BeatmapDisplay : CompositeDrawable, IHasTooltip @@ -138,27 +140,47 @@ namespace osu.Game.Tests.Visual private readonly OsuSpriteText text; private readonly BeatmapInfo beatmap; + private readonly BindableBeatmap beatmapBindable; + private BeatmapManager beatmaps; - private OsuGameBase osuGame; private bool isSelected; public string TooltipText => text.Text; - public BeatmapDisplay(BeatmapInfo beatmap) + public BeatmapDisplay(BeatmapInfo beatmap, BindableBeatmap beatmapBindable) { this.beatmap = beatmap; + this.beatmapBindable = beatmapBindable; AutoSizeAxes = Axes.Both; InternalChild = text = new OsuSpriteText(); + + this.beatmapBindable.ValueChanged += beatmapChanged; + } + + [BackgroundDependencyLoader] + private void load(BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + + var working = beatmaps.GetWorkingBeatmap(beatmap); + text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]"; + } + + private void beatmapChanged(WorkingBeatmap newBeatmap) + { + if (isSelected) + this.FadeColour(Color4.White, 100); + isSelected = false; } protected override bool OnClick(InputState state) { - if (osuGame.Beatmap.Value.BeatmapInfo.ID == beatmap.ID) + if (beatmapBindable.Value.BeatmapInfo.ID == beatmap.ID) return false; - osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); + beatmapBindable.Value = beatmaps.GetWorkingBeatmap(beatmap); isSelected = true; return true; } @@ -177,25 +199,6 @@ namespace osu.Game.Tests.Visual return; this.FadeColour(Color4.White, 100); } - - [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, BeatmapManager beatmaps) - { - this.osuGame = osuGame; - this.beatmaps = beatmaps; - - var working = beatmaps.GetWorkingBeatmap(beatmap); - text.Text = $"{working.Metadata.Artist} - {working.Metadata.Title} ({working.Metadata.AuthorString}) [{working.BeatmapInfo.Version}]"; - - osuGame.Beatmap.ValueChanged += beatmapChanged; - } - - private void beatmapChanged(WorkingBeatmap newBeatmap) - { - if (isSelected) - this.FadeColour(Color4.White, 100); - isSelected = false; - } } } @@ -204,7 +207,7 @@ namespace osu.Game.Tests.Visual private readonly FillFlowContainer scores; private APIAccess api; - private readonly Bindable currentBeatmap = new Bindable(); + private readonly IBindable currentBeatmap = new Bindable(); public PerformanceList() { @@ -220,7 +223,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame, APIAccess api) + private void load(IBindableBeatmap beatmap, APIAccess api) { this.api = api; @@ -235,7 +238,7 @@ namespace osu.Game.Tests.Visual } currentBeatmap.ValueChanged += beatmapChanged; - currentBeatmap.BindTo(osuGame.Beatmap); + currentBeatmap.BindTo(beatmap); } private GetScoresRequest lastRequest; @@ -333,9 +336,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(OsuGameBase osuGame) + private void load(IBindableBeatmap beatmap) { - osuGame.Beatmap.ValueChanged += beatmapChanged; + beatmap.ValueChanged += beatmapChanged; } private Cached informationCache = new Cached(); diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs index bda438d906..3cdc496ee1 100644 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ b/osu.Game/Tests/Visual/TestCasePlayer.cs @@ -19,8 +19,6 @@ namespace osu.Game.Tests.Visual protected Player Player; - private TestWorkingBeatmap working; - protected TestCasePlayer(Ruleset ruleset) { this.ruleset = ruleset; @@ -44,7 +42,7 @@ namespace osu.Game.Tests.Visual { Player p = null; AddStep(ruleset.RulesetInfo.Name, () => p = loadPlayerFor(ruleset)); - AddUntilStep(() => p.IsLoaded); + AddUntilStep(() => ContinueCondition(p)); } else { @@ -52,11 +50,13 @@ namespace osu.Game.Tests.Visual { Player p = null; AddStep(r.Name, () => p = loadPlayerFor(r)); - AddUntilStep(() => p.IsLoaded); + AddUntilStep(() => ContinueCondition(p)); } } } + protected virtual bool ContinueCondition(Player player) => player.IsLoaded; + protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); private Player loadPlayerFor(RulesetInfo ri) => loadPlayerFor(ri.CreateInstance()); @@ -65,13 +65,13 @@ namespace osu.Game.Tests.Visual { var beatmap = CreateBeatmap(r); - working = new TestWorkingBeatmap(beatmap); - working.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; + Beatmap.Value = new TestWorkingBeatmap(beatmap); + Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; if (Player != null) Remove(Player); - var player = CreatePlayer(working, r); + var player = CreatePlayer(r); LoadComponentAsync(player, LoadScreen); @@ -82,14 +82,12 @@ namespace osu.Game.Tests.Visual { base.Update(); - if (working != null) - // note that this will override any mod rate application - working.Track.Rate = Clock.Rate; + // note that this will override any mod rate application + Beatmap.Value.Track.Rate = Clock.Rate; } - protected virtual Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) => new Player + protected virtual Player CreatePlayer(Ruleset ruleset) => new Player { - InitialBeatmap = beatmap, AllowPause = false, AllowLeadIn = false, AllowResults = false, diff --git a/osu.Game/Utils/DebugUtils.cs b/osu.Game/Utils/DebugUtils.cs new file mode 100644 index 0000000000..191662c690 --- /dev/null +++ b/osu.Game/Utils/DebugUtils.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Utils +{ + public static class DebugUtils + { + public static bool IsDebug + { + get + { + // ReSharper disable once RedundantAssignment + bool isDebug = false; + // Debug.Assert conditions are only evaluated in debug mode + System.Diagnostics.Debug.Assert(isDebug = true); + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + return isDebug; + } + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1a75f1979a..363fc7d7bb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -11,16 +11,16 @@ - - - + + + - + - \ No newline at end of file + diff --git a/osu.TestProject.props b/osu.TestProject.props index afdf895eac..cf8276eee1 100644 --- a/osu.TestProject.props +++ b/osu.TestProject.props @@ -7,18 +7,17 @@ - - + - + VisualTestRunner.cs @@ -26,4 +25,4 @@ false - \ No newline at end of file + diff --git a/osu.sln b/osu.sln index 5c4b644489..bf1b6d60e1 100644 --- a/osu.sln +++ b/osu.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 15.0.27004.2006 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Framework", "osu-framework\osu.Framework\osu.Framework.csproj", "{C76BF5B3-985E-4D39-95FE-97C9C879B83A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Resources", "osu-resources\osu.Game.Resources\osu.Game.Resources.csproj", "{D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" @@ -17,8 +15,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Desktop.Deploy", "osu.Desktop.Deploy\osu.Desktop.Deploy.csproj", "{BAEA2F74-0315-4667-84E0-ACAC0B4BF785}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Tests", "osu.Game.Tests\osu.Game.Tests.csproj", "{54377672-20B1-40AF-8087-5CF73BF3953A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Desktop", "osu.Desktop\osu.Desktop.csproj", "{419659FD-72EA-4678-9EB8-B22A746CED70}" @@ -41,10 +37,6 @@ Global {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|Any CPU.Build.0 = Release|Any CPU - {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C76BF5B3-985E-4D39-95FE-97C9C879B83A}.Release|Any CPU.Build.0 = Release|Any CPU {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|Any CPU.Build.0 = Debug|Any CPU {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -65,8 +57,6 @@ Global {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.Build.0 = Debug|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.ActiveCfg = Release|Any CPU {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|Any CPU.Build.0 = Release|Any CPU - {BAEA2F74-0315-4667-84E0-ACAC0B4BF785}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BAEA2F74-0315-4667-84E0-ACAC0B4BF785}.Release|Any CPU.ActiveCfg = Release|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Debug|Any CPU.Build.0 = Debug|Any CPU {54377672-20B1-40AF-8087-5CF73BF3953A}.Release|Any CPU.ActiveCfg = Release|Any CPU