diff --git a/.gitattributes b/.gitattributes index e3ffd343db..c61ca25cd2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -15,6 +15,7 @@ App.config text eol=crlf *.cmd text eol=crlf *.snippet text eol=crlf *.manifest text eol=crlf +*.licenseheader text eol=crlf # Check out with lf (UNIX) line endings *.sh text eol=lf diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..0c6b80e97e --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +custom: https://osu.ppy.sh/home/support diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/bug-issues.md index 8d85c92fec..c8c41e5a78 100644 --- a/.github/ISSUE_TEMPLATE/bug-issues.md +++ b/.github/ISSUE_TEMPLATE/bug-issues.md @@ -1,14 +1,11 @@ --- name: Bug Report -about: For issues regarding encountered game bugs +about: Issues regarding encountered bugs. --- - - - -**Describe your problem:** +**Describe the bug:** **Screenshots or videos showing encountered issue:** -**osu!lazer version:** +**osu!lazer version:** -**Logs:** \ No newline at end of file +**Logs:** diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/crash-issues.md index 849f042c1f..8ad27e9e31 100644 --- a/.github/ISSUE_TEMPLATE/crash-issues.md +++ b/.github/ISSUE_TEMPLATE/crash-issues.md @@ -1,16 +1,13 @@ --- name: Crash Report -about: For issues regarding game crashes or permanent freezes +about: Issues regarding crashes or permanent freezes. --- - - - -**Describe your problem:** +**Describe the crash:** **Screenshots or videos showing encountered issue:** -**osu!lazer version:** +**osu!lazer version:** -**Logs:** +**Logs:** -**Computer Specifications:** \ No newline at end of file +**Computer Specifications:** diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/feature-request-issues.md index 73c4f37a3e..54c4ff94e5 100644 --- a/.github/ISSUE_TEMPLATE/feature-request-issues.md +++ b/.github/ISSUE_TEMPLATE/feature-request-issues.md @@ -1,10 +1,7 @@ --- name: Feature Request -about: Let us know what you would like to see in the game! +about: Features you would like to see in the game! --- +**Describe the new feature:** - - -**Describe the feature:** - -**Proposal designs of the feature:** +**Proposal designs of the feature:** diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md index ae3cf20a8c..5822da9c65 100644 --- a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md +++ b/.github/ISSUE_TEMPLATE/missing-for-live-issues.md @@ -1,10 +1,7 @@ --- name: Missing for Live -about: Let us know the features you need which are available in osu-stable but not lazer +about: Features which are available in osu!stable but not yet in osu!lazer. --- +**Describe the missing feature:** - - -**Describe the feature:** - -**Designs:** +**Proposal designs of the feature:** diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index 221e4746cb..0000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,8 +0,0 @@ -Add any details pertaining to developers above the break. - -- [ ] Depends on #PR -- Closes #ISSUE - ---- - -Add a sentence or two describing this change in plain english. This will be displayed on the [changelog](https://osu.ppy.sh/home/changelog). A single screenshot or short gif is also welcomed. \ No newline at end of file diff --git a/.gitignore b/.gitignore index f95a04e517..0e2850a01c 100644 --- a/.gitignore +++ b/.gitignore @@ -11,8 +11,10 @@ *.userprefs ### Cake ### -tools/* -!tools/cakebuild.csproj +tools/** +build/tools/** + +fastlane/report.xml # Build results bin/[Dd]ebug/ diff --git a/.gitmodules b/.gitmodules index f1c4f5d172..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "osu-resources"] - path = 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/CatchRuleset__Tests_.xml similarity index 67% rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml rename to .idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml index 1c988fe6fd..6463dd6ea5 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__catch_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/CatchRuleset__Tests_.xml @@ -1,18 +1,21 @@ - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml b/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml similarity index 67% rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml rename to .idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml index d7bb0f90f1..0b63b2d966 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__mania_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/ManiaRuleset__Tests_.xml @@ -1,18 +1,21 @@ - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml b/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml similarity index 67% rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml rename to .idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml index 997ac6b078..750ece648b 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__osu__.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/OsuRuleset__Tests_.xml @@ -1,18 +1,21 @@ - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml b/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml similarity index 67% rename from .idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml rename to .idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml index b7a070174c..7b359a1ca0 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/RulesetTests__taiko_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/TaikoRuleset__Tests_.xml @@ -1,18 +1,21 @@ - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml new file mode 100644 index 0000000000..3722f3dc04 --- /dev/null +++ b/.idea/.idea.osu/.idea/runConfigurations/Tournament.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml new file mode 100644 index 0000000000..e2628a1bb4 --- /dev/null +++ b/.idea/.idea.osu/.idea/runConfigurations/Tournament__Tests_.xml @@ -0,0 +1,21 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml index 344301d4a7..7ac6bb745d 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/osu_.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/osu_.xml @@ -1,17 +1,20 @@ - - \ No newline at end of file diff --git a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml b/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml similarity index 66% rename from .idea/.idea.osu/.idea/runConfigurations/VisualTests.xml rename to .idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml index bf5a1f64e0..7fcb7c15ea 100644 --- a/.idea/.idea.osu/.idea/runConfigurations/VisualTests.xml +++ b/.idea/.idea.osu/.idea/runConfigurations/osu___Tests_.xml @@ -1,17 +1,20 @@ - - \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index e9b8d6f397..57ff3e6b43 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,53 +1,18 @@ { "version": "0.2.0", - "configurations": [ - { - "name": "VisualTests (Debug)", - "type": "coreclr", - "request": "launch", - "program": "dotnet", - "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll" - ], - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build tests (Debug)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}" - } - }, - "console": "internalConsole" - }, - { - "name": "VisualTests (Release)", - "type": "coreclr", - "request": "launch", - "program": "dotnet", - "args": [ - "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1/osu.Game.Tests.dll" - ], - "cwd": "${workspaceRoot}", - "preLaunchTask": "Build tests (Release)", - "linux": { - "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}" - } - }, - "console": "internalConsole" - }, - { + "configurations": [{ "name": "osu! (Debug)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Debug)", "linux": { "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1:${env:LD_LIBRARY_PATH}" + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" } }, "console": "internalConsole" @@ -58,16 +23,135 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll" + "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build osu! (Release)", "linux": { "env": { - "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1:${env:LD_LIBRARY_PATH}" + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}" } }, "console": "internalConsole" + }, + { + "name": "osu! (Tests, Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build tests (Debug)", + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" + } + }, + "console": "internalConsole" + }, { + "name": "osu! (Tests, Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build tests (Release)", + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}" + } + }, + "console": "internalConsole" + }, + { + "name": "Tournament (Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll", + "--tournament" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build osu! (Debug)", + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" + } + }, + "console": "internalConsole" + }, + { + "name": "Tournament (Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll", + "--tournament" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build osu! (Release)", + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}" + } + }, + "console": "internalConsole" + }, + { + "name": "Tournament (Tests, Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll", + "--tournament" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build tournament tests (Debug)", + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" + } + }, + "console": "internalConsole" + }, + { + "name": "Tournament (Tests, Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll", + "--tournament" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build tournament tests (Release)", + "linux": { + "env": { + "LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}" + } + }, + "console": "internalConsole" + }, + { + "name": "Cake: Debug Script", + "type": "coreclr", + "request": "launch", + "program": "${workspaceRoot}/build/tools/Cake.CoreCLR/0.30.0/Cake.dll", + "args": [ + "${workspaceRoot}/build/build.cake", + "--debug", + "--verbosity=diagnostic" + ], + "cwd": "${workspaceRoot}/build", + "stopAtEntry": true, + "externalConsole": false } ] -} +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 188f20b69f..aba590f466 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,8 +2,7 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format "version": "2.0.0", - "tasks": [ - { + "tasks": [{ "label": "Build osu! (Debug)", "type": "shell", "command": "dotnet", @@ -11,7 +10,6 @@ "build", "--no-restore", "osu.Desktop", - "/p:TargetFramework=netcoreapp2.1", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -27,7 +25,6 @@ "build", "--no-restore", "osu.Desktop", - "/p:TargetFramework=netcoreapp2.1", "/p:Configuration=Release", "/p:GenerateFullPaths=true", "/m", @@ -44,7 +41,6 @@ "build", "--no-restore", "osu.Game.Tests", - "/p:TargetFramework=netcoreapp2.1", "/p:GenerateFullPaths=true", "/m", "/verbosity:m" @@ -60,7 +56,6 @@ "build", "--no-restore", "osu.Game.Tests", - "/p:TargetFramework=netcoreapp2.1", "/p:Configuration=Release", "/p:GenerateFullPaths=true", "/m", @@ -70,11 +65,42 @@ "problemMatcher": "$msCompile" }, { - "label": "Restore (netcoreapp2.1)", + "label": "Build tournament tests (Debug)", "type": "shell", "command": "dotnet", "args": [ - "restore" + "build", + "--no-restore", + "osu.Game.Tournament.Tests", + "/p:GenerateFullPaths=true", + "/m", + "/verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, { + "label": "Build tournament tests (Release)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Tournament.Tests", + "/p:Configuration=Release", + "/p:GenerateFullPaths=true", + "/m", + "/verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Restore (netcoreapp2.2)", + "type": "shell", + "command": "dotnet", + "args": [ + "restore", + "osu.sln" ], "problemMatcher": [] } diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000000..cdd3a6b349 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source "https://rubygems.org" + +gem "fastlane" + +plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') +eval_gemfile(plugins_path) if File.exist?(plugins_path) diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000000..17c0db12e7 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,173 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.0) + addressable (2.6.0) + public_suffix (>= 2.0.2, < 4.0) + atomos (0.1.3) + babosa (1.0.2) + claide (1.0.2) + colored (1.2) + colored2 (3.1.2) + commander-fastlane (4.4.6) + highline (~> 1.7.2) + declarative (0.0.10) + declarative-option (0.1.0) + digest-crc (0.4.1) + domain_name (0.5.20180417) + unf (>= 0.0.5, < 1.0.0) + dotenv (2.7.1) + emoji_regex (1.0.1) + excon (0.62.0) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) + faraday-cookie_jar (0.0.6) + faraday (>= 0.7.4) + http-cookie (~> 1.0.0) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.5) + fastlane (2.117.0) + CFPropertyList (>= 2.3, < 4.0.0) + addressable (>= 2.3, < 3.0.0) + babosa (>= 1.0.2, < 2.0.0) + bundler (>= 1.12.0, < 3.0.0) + colored + commander-fastlane (>= 4.4.6, < 5.0.0) + dotenv (>= 2.1.1, < 3.0.0) + emoji_regex (>= 0.1, < 2.0) + excon (>= 0.45.0, < 1.0.0) + faraday (~> 0.9) + faraday-cookie_jar (~> 0.0.6) + faraday_middleware (~> 0.9) + fastimage (>= 2.1.0, < 3.0.0) + gh_inspector (>= 1.1.2, < 2.0.0) + google-api-client (>= 0.21.2, < 0.24.0) + google-cloud-storage (>= 1.15.0, < 2.0.0) + highline (>= 1.7.2, < 2.0.0) + json (< 3.0.0) + mini_magick (~> 4.5.1) + multi_json + multi_xml (~> 0.5) + multipart-post (~> 2.0.0) + plist (>= 3.1.0, < 4.0.0) + public_suffix (~> 2.0.0) + rubyzip (>= 1.2.2, < 2.0.0) + security (= 0.1.3) + simctl (~> 1.6.3) + slack-notifier (>= 2.0.0, < 3.0.0) + terminal-notifier (>= 1.6.2, < 2.0.0) + terminal-table (>= 1.4.5, < 2.0.0) + tty-screen (>= 0.6.3, < 1.0.0) + tty-spinner (>= 0.8.0, < 1.0.0) + word_wrap (~> 1.0.0) + xcodeproj (>= 1.6.0, < 2.0.0) + xcpretty (~> 0.3.0) + xcpretty-travis-formatter (>= 0.0.3) + fastlane-plugin-clean_testflight_testers (0.2.0) + fastlane-plugin-souyuz (0.8.1) + souyuz (>= 0.8.1) + fastlane-plugin-xamarin (0.6.3) + gh_inspector (1.1.3) + google-api-client (0.23.9) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.5, < 0.7.0) + httpclient (>= 2.8.1, < 3.0) + mime-types (~> 3.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.9) + google-cloud-core (1.3.0) + google-cloud-env (~> 1.0) + google-cloud-env (1.0.5) + faraday (~> 0.11) + google-cloud-storage (1.16.0) + digest-crc (~> 0.4) + google-api-client (~> 0.23) + google-cloud-core (~> 1.2) + googleauth (>= 0.6.2, < 0.10.0) + googleauth (0.6.7) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.7) + highline (1.7.10) + http-cookie (1.0.3) + domain_name (~> 0.5) + httpclient (2.8.3) + json (2.2.0) + jwt (2.1.0) + memoist (0.16.0) + mime-types (3.2.2) + mime-types-data (~> 3.2015) + mime-types-data (3.2018.0812) + mini_magick (4.5.1) + mini_portile2 (2.4.0) + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + nanaimo (0.2.6) + naturally (2.2.0) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + os (1.0.0) + plist (3.5.0) + public_suffix (2.0.5) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + retriable (3.1.2) + rouge (2.0.7) + rubyzip (1.2.2) + security (0.1.3) + signet (0.11.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simctl (1.6.5) + CFPropertyList + naturally + slack-notifier (2.3.2) + souyuz (0.8.1) + fastlane (>= 2.29.0) + highline (~> 1.7) + nokogiri (~> 1.7) + terminal-notifier (1.8.0) + terminal-table (1.8.0) + unicode-display_width (~> 1.1, >= 1.1.1) + tty-cursor (0.6.1) + tty-screen (0.6.5) + tty-spinner (0.9.0) + tty-cursor (~> 0.6.0) + uber (0.1.0) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.5) + unicode-display_width (1.4.1) + word_wrap (1.0.0) + xcodeproj (1.8.1) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + xcpretty (0.3.0) + rouge (~> 2.0.7) + xcpretty-travis-formatter (1.0.0) + xcpretty (~> 0.2, >= 0.0.7) + +PLATFORMS + ruby + +DEPENDENCIES + fastlane + fastlane-plugin-clean_testflight_testers + fastlane-plugin-souyuz + fastlane-plugin-xamarin + +BUNDLED WITH + 2.0.1 diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index 2bff304fba..0000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,11 +0,0 @@ -osu!lazer is currently still under heavy development! - -Please ensure that you are making an issue for one of the following: - -- A bug with currently implemented features (not features that don't exist) -- A feature you are considering adding, so we can collaborate on feedback and design. -- Discussions about technical design decisions - -If your issue qualifies, replace this text with a detailed description of your issue with as much relevant information as you can provide. - -Screenshots and log files are highly welcomed. \ No newline at end of file diff --git a/LICENCE b/LICENCE index a11a7ce75b..21c6a7090f 100644 --- a/LICENCE +++ b/LICENCE @@ -1,4 +1,4 @@ -Copyright (c) 2007-2018 ppy Pty Ltd . +Copyright (c) 2019 ppy Pty Ltd . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index baaba22726..55f2eebec9 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,94 @@ -# osu! [![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) +# osu! + +[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu) [![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu) [![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy) Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Commonly known by the codename "osu!lazer". Pew pew. -# Status +## Status This project is still heavily under development, but is in a state where users are encouraged to try it out and keep it installed alongside the stable osu! client. It will continue to evolve over the coming months and hopefully bring some new unique features to the table. We are accepting bug reports (please report with as much detail as possible). Feature requests are welcome as long as you read and understand the contribution guidelines listed below. -# Requirements +Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh/home/changelog). -- A desktop platform with the [.NET Core SDK 2.1](https://www.microsoft.com/net/learn/get-started) or higher installed. -- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio Community Edition](https://www.visualstudio.com/) (Windows), [Visual Studio Code](https://code.visualstudio.com/) (with the C# plugin installed) or [Jetbrains Rider](https://www.jetbrains.com/rider/) (commercial). +## Requirements -# Building and running +- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed. +- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/). +- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date. -If you are not interested in developing the game, please head over to the [releases](https://github.com/ppy/osu/releases) to download a precompiled build with automatic updating enabled (download and run the install executable for your platform). +## Running osu! -Clone the repository including submodules +### Releases -`git clone --recurse-submodules https://github.com/ppy/osu` +![](https://puu.sh/DCmvA/f6a74f5fbb.png) -Build and run +If you are not interested in developing the game, you can still consume our [binary releases](https://github.com/ppy/osu/releases). -- Using Visual Studio 2017, Rider or Visual Studio Code (configurations are included) -- From command line using `dotnet run --project osu.Desktop`. When building for non-development purposes, add `-c Release` to gain higher performance. -- To run with code analysis, instead use `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternative, you can install resharper or use rider to get inline support in your IDE of choice. +**Latest build:** -Note: If you run from command line under linux, you will need to prefix the output folder to your `LD_LIBRARY_PATH`. See `.vscode/launch.json` for an example +| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | +| ------------- | ------------- | -If you run into issues building you may need to restore nuget packages (commonly via `dotnet restore`). Visual Studio Code users must run `Restore` task from debug tab before attempt to build. +- **Linux** users are recommended to self-compile until we have official deployment in place. +- **iOS** users can join the [TestFlight beta program](https://t.co/PasE1zrHhw) (note that due to high demand this is regularly full). +- **Android** users can self-compile, and expect a public beta soon. -# Contributing +If your platform is not listed above, there is still a chance you can manually build it by following the instructions below. + +### Downloading the source code + +Clone the repository: + +```shell +git clone https://github.com/ppy/osu +cd osu +``` + +To update the source code to the latest commit, run the following command inside the `osu` directory: + +```shell +git pull +``` + +### Building + +Build configurations for the recommended IDEs (listed above) are included. You should use the provided Build/Run functionality of your IDE to get things going. When testing or building new components, it's highly encouraged you use the `VisualTests` project/configuration. More information on this provided [below](#contributing). + +> Visual Studio Code users must run the `Restore` task before any build attempt. + +You can also build and run osu! from the command-line with a single command: + +```shell +dotnet run --project osu.Desktop +``` + +If you are not interested in debugging osu!, you can add `-c Release` to gain performance. In this case, you must replace `Debug` with `Release` in any commands mentioned in this document. + +If the build fails, try to restore nuget packages with `dotnet restore`. + +#### A note for Linux users + +On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build directory, located at `osu.Desktop/bin/Debug/$NETCORE_VERSION`. + +`$NETCORE_VERSION` is the version of the targeted .NET Core SDK. You can check it by running `grep TargetFramework osu.Desktop/osu.Desktop.csproj | sed -r 's/.*>(.*)<\/.*/\1/'`. + +For example, you can run osu! with the following command: + +```shell +LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop +``` + +### Testing with resource/framework modifications + +Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages. + +### Code analysis + +Code analysis can be run with `powershell ./build.ps1` or `build.sh`. This is currently only supported under windows due to [resharper cli shortcomings](https://youtrack.jetbrains.com/issue/RSRP-410004). Alternatively, you can install resharper or use rider to get inline support in your IDE of choice. + +## Contributing We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention on having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time, to ensure no effort is wasted. @@ -41,7 +98,7 @@ Contributions can be made via pull requests to this repository. We hope to credi Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured; with any libraries we are using; with any processes involved with contributing, *please* bring it up. I welcome all feedback so we can make contributing to this project as pain-free as possible. -# Licence +## Licence The osu! client code and framework are licensed under the [MIT licence](https://opensource.org/licenses/MIT). Please see [the licence file](LICENCE) for more information. [tl;dr](https://tldrlegal.com/license/mit-license) you can do whatever you want as long as you include the original copyright and license notice in any copy of the software/source. diff --git a/appveyor.yml b/appveyor.yml index 1f485485da..4dcaa7b45e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2017 +image: Previous Visual Studio 2017 test: off install: - cmd: git submodule update --init --recursive --depth=5 diff --git a/build.ps1 b/build.ps1 index 9968673c90..c6a0bf6d4a 100644 --- a/build.ps1 +++ b/build.ps1 @@ -41,27 +41,28 @@ Param( [switch]$ShowDescription, [Alias("WhatIf", "Noop")] [switch]$DryRun, - [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [Parameter(Position = 0, Mandatory = $false, ValueFromRemainingArguments = $true)] [string[]]$ScriptArgs ) Write-Host "Preparing to run build script..." # Determine the script root for resolving other paths. -if(!$PSScriptRoot){ +if(!$PSScriptRoot) { $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent } # Resolve the paths for resources used for debugging. -$TOOLS_DIR = Join-Path $PSScriptRoot "tools" -$CAKE_CSPROJ = Join-Path $TOOLS_DIR "cakebuild.csproj" +$BUILD_DIR = Join-Path $PSScriptRoot "build" +$TOOLS_DIR = Join-Path $BUILD_DIR "tools" +$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj" # Install the required tools locally. Write-Host "Restoring cake tools..." Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null # Find the Cake executable -$CAKE_EXECUTABLE = (Get-ChildItem -Path ./tools/cake.coreclr/ -Filter Cake.dll -Recurse).FullName +$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName # Build Cake arguments $cakeArguments = @("$Script"); @@ -75,5 +76,7 @@ $cakeArguments += $ScriptArgs # Start Cake Write-Host "Running build script..." +Push-Location -Path $BUILD_DIR Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments" +Pop-Location exit $LASTEXITCODE diff --git a/build.sh b/build.sh index caf1702f41..8f1ef5b455 100755 --- a/build.sh +++ b/build.sh @@ -6,12 +6,13 @@ echo "Preparing to run build script..." +cd build SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) TOOLS_DIR=$SCRIPT_DIR/tools CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr" SCRIPT="build.cake" -CAKE_CSPROJ=$TOOLS_DIR/"cakebuild.csproj" +CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj" # Parse arguments. CAKE_ARGUMENTS=() diff --git a/build.cake b/build/build.cake similarity index 68% rename from build.cake rename to build/build.cake index bc7dfafb8c..1d2588de49 100644 --- a/build.cake +++ b/build/build.cake @@ -1,6 +1,7 @@ #addin "nuget:?package=CodeFileSanity&version=0.0.21" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2018.2.2" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" +var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); /////////////////////////////////////////////////////////////////////////////// // ARGUMENTS @@ -9,30 +10,24 @@ var target = Argument("target", "Build"); var configuration = Argument("configuration", "Release"); -var osuSolution = new FilePath("./osu.sln"); +var rootDirectory = new DirectoryPath(".."); +var solution = rootDirectory.CombineWithFilePath("osu.sln"); /////////////////////////////////////////////////////////////////////////////// // TASKS /////////////////////////////////////////////////////////////////////////////// -Task("Restore") - .Does(() => { - DotNetCoreRestore(osuSolution.FullPath); - }); - Task("Compile") - .IsDependentOn("Restore") .Does(() => { - DotNetCoreBuild(osuSolution.FullPath, new DotNetCoreBuildSettings { + DotNetCoreBuild(solution.FullPath, new DotNetCoreBuildSettings { Configuration = configuration, - NoRestore = true, }); }); Task("Test") .IsDependentOn("Compile") .Does(() => { - var testAssemblies = GetFiles("**/*.Tests/bin/**/*.Tests.dll"); + var testAssemblies = GetFiles(rootDirectory + "/**/*.Tests/bin/**/*.Tests.dll"); DotNetCoreVSTest(testAssemblies, new DotNetCoreVSTestSettings { Logger = AppVeyor.IsRunningOnAppVeyor ? "Appveyor" : $"trx", @@ -46,20 +41,20 @@ Task("InspectCode") .WithCriteria(IsRunningOnWindows()) .IsDependentOn("Compile") .Does(() => { - var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); - - InspectCode(osuSolution, new InspectCodeSettings { + InspectCode(solution, new InspectCodeSettings { CachesHome = "inspectcode", OutputFile = "inspectcodereport.xml", }); - StartProcess(nVikaToolPath, @"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors"); + if (returnCode != 0) + throw new Exception($"inspectcode failed with return code {returnCode}"); }); Task("CodeFileSanity") .Does(() => { ValidateCodeSanity(new ValidateCodeSanitySettings { - RootDirectory = ".", + RootDirectory = rootDirectory.FullPath, IsAppveyorBuild = AppVeyor.IsRunningOnAppVeyor }); }); diff --git a/tools/cakebuild.csproj b/build/cakebuild.csproj similarity index 100% rename from tools/cakebuild.csproj rename to build/cakebuild.csproj diff --git a/fastlane/Appfile b/fastlane/Appfile new file mode 100644 index 0000000000..083de66985 --- /dev/null +++ b/fastlane/Appfile @@ -0,0 +1,2 @@ +app_identifier("sh.ppy.osulazer") # The bundle identifier of your app +apple_id("apple-dev@ppy.sh") # Your Apple email address diff --git a/fastlane/Fastfile b/fastlane/Fastfile new file mode 100644 index 0000000000..48c16caf0f --- /dev/null +++ b/fastlane/Fastfile @@ -0,0 +1,65 @@ +update_fastlane + +default_platform(:ios) + +platform :ios do + lane :testflight_prune_dry do + clean_testflight_testers(days_of_inactivity:45, dry_run: true) + end + + # Specify a custom number for what's "inactive" + lane :testflight_prune do + clean_testflight_testers(days_of_inactivity: 45) # 120 days, so about 4 months + end + + lane :update_version do |options| + options[:plist_path] = '../osu.iOS/Info.plist' + app_version(options) + end + + desc 'Deploy to testflight' + lane :beta do |options| + update_version(options) + + provision( + type: 'appstore' + ) + + build( + build_configuration: 'Release', + build_platform: 'iPhone' + ) + + client = HTTPClient.new + changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw' + changelog.gsub!('$BUILD_ID', options[:build]) + + pilot( + wait_processing_interval: 1800, + changelog: changelog, + ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa' + ) + end + + desc 'Compile the project' + lane :build do + nuget_restore( + project_path: 'osu.iOS.sln' + ) + + souyuz( + platform: "ios", + build_target: "osu_iOS", + plist_path: "../osu.iOS/Info.plist" + ) + end + + desc 'Install provisioning profiles using match' + lane :provision do |options| + if Helper.is_ci? + options[:readonly] = true + end + + match(options) + end +end diff --git a/fastlane/Matchfile b/fastlane/Matchfile new file mode 100644 index 0000000000..40c974b09e --- /dev/null +++ b/fastlane/Matchfile @@ -0,0 +1 @@ +git_url('https://github.com/peppy/apple-certificates') diff --git a/fastlane/Pluginfile b/fastlane/Pluginfile new file mode 100644 index 0000000000..9f4f47f213 --- /dev/null +++ b/fastlane/Pluginfile @@ -0,0 +1,7 @@ +# Autogenerated by fastlane +# +# Ensure this file is checked in to source control! + +gem 'fastlane-plugin-clean_testflight_testers' +gem 'fastlane-plugin-souyuz' +gem 'fastlane-plugin-xamarin' diff --git a/fastlane/README.md b/fastlane/README.md new file mode 100644 index 0000000000..53bbc62cae --- /dev/null +++ b/fastlane/README.md @@ -0,0 +1,54 @@ +fastlane documentation +================ +# Installation + +Make sure you have the latest version of the Xcode command line tools installed: + +``` +xcode-select --install +``` + +Install _fastlane_ using +``` +[sudo] gem install fastlane -NV +``` +or alternatively using `brew cask install fastlane` + +# Available Actions +## iOS +### ios testflight_prune_dry +``` +fastlane ios testflight_prune_dry +``` + +### ios testflight_prune +``` +fastlane ios testflight_prune +``` + +### ios update_version +``` +fastlane ios update_version +``` + +### ios beta +``` +fastlane ios beta +``` +Deploy to testflight +### ios build +``` +fastlane ios build +``` +Compile the project +### ios provision +``` +fastlane ios provision +``` +Install provisioning profiles using match + +---- + +This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. +More information about fastlane can be found on [fastlane.tools](https://fastlane.tools). +The documentation of fastlane can be found on [docs.fastlane.tools](https://docs.fastlane.tools). diff --git a/osu-resources b/osu-resources deleted file mode 160000 index 694cb03f19..0000000000 --- a/osu-resources +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 694cb03f19c93106ed0f2593f3e506e835fb652a diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 93fd3935c6..975b7f9f5a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.IO; @@ -7,20 +7,23 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using osu.Desktop.Overlays; -using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Game; using osuTK.Input; using Microsoft.Win32; using osu.Desktop.Updater; using osu.Framework; +using osu.Framework.Logging; using osu.Framework.Platform.Windows; +using osu.Framework.Screens; +using osu.Game.Screens.Menu; namespace osu.Desktop { internal class OsuGameDesktop : OsuGame { private readonly bool noVersionOverlay; + private VersionManager versionManager; public OsuGameDesktop(string[] args = null) : base(args) @@ -32,12 +35,15 @@ namespace osu.Desktop { try { - return new StableStorage(); + if (Host is DesktopGameHost desktopHost) + return new StableStorage(desktopHost); } - catch + catch (Exception e) { - return null; + Logger.Error(e, "Error while searching for stable install"); } + + return null; } protected override void LoadComplete() @@ -46,10 +52,10 @@ namespace osu.Desktop if (!noVersionOverlay) { - LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => + LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, v => { Add(v); - v.State = Visibility.Visible; + v.Show(); }); if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) @@ -59,11 +65,28 @@ namespace osu.Desktop } } + protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen) + { + base.ScreenChanged(lastScreen, newScreen); + + switch (newScreen) + { + case Intro _: + case MainMenu _: + versionManager?.Show(); + break; + + default: + versionManager?.Hide(); + break; + } + } + public override void SetHost(GameHost host) { base.SetHost(host); - var desktopWindow = host.Window as DesktopGameWindow; - if (desktopWindow != null) + + if (host.Window is DesktopGameWindow desktopWindow) { desktopWindow.CursorState |= CursorState.Hidden; @@ -76,7 +99,7 @@ namespace osu.Desktop private void fileDrop(object sender, FileDropEventArgs e) { - var filePaths = new[] { e.FileName }; + var filePaths = e.FileNames; var firstExtension = Path.GetExtension(filePaths.First()); @@ -119,8 +142,8 @@ namespace osu.Desktop return null; } - public StableStorage() - : base(string.Empty, null) + public StableStorage(DesktopGameHost host) + : base(string.Empty, host) { } } diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs index f31bba1e1e..1f1d2cea5f 100644 --- a/osu.Desktop/Overlays/VersionManager.cs +++ b/osu.Desktop/Overlays/VersionManager.cs @@ -1,20 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Platform; using osu.Game; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -25,15 +23,13 @@ namespace osu.Desktop.Overlays private OsuConfigManager config; private OsuGameBase game; private NotificationOverlay notificationOverlay; - private GameHost host; [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config, GameHost host) + private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) { notificationOverlay = notification; this.config = config; this.game = game; - this.host = host; AutoSizeAxes = Axes.Both; Anchor = Anchor.BottomCentre; @@ -60,12 +56,12 @@ namespace osu.Desktop.Overlays { new OsuSpriteText { - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), Text = game.Name }, new OsuSpriteText { - Colour = DebugUtils.IsDebug ? colours.Red : Color4.White, + Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White, Text = game.Version }, } @@ -74,9 +70,8 @@ namespace osu.Desktop.Overlays { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - TextSize = 12, + Font = OsuFont.Numeric.With(size: 12), Colour = colours.Yellow, - Font = @"Venera", Text = @"Development Build" }, new Sprite @@ -96,43 +91,49 @@ namespace osu.Desktop.Overlays var version = game.Version; var lastVersion = config.Get(OsuSetting.Version); + if (game.IsDeployedBuild && version != lastVersion) { config.Set(OsuSetting.Version, version); // only show a notification if we've previously saved a version to the config file (ie. not the first run). if (!string.IsNullOrEmpty(lastVersion)) - notificationOverlay.Post(new UpdateCompleteNotification(version, host.OpenUrlExternally)); + notificationOverlay.Post(new UpdateCompleteNotification(version)); } } private class UpdateCompleteNotification : SimpleNotification { - public UpdateCompleteNotification(string version, Action openUrl = null) + private readonly string version; + + public UpdateCompleteNotification(string version) { + this.version = version; Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; - Icon = FontAwesome.fa_check_square; - Activated = delegate - { - openUrl?.Invoke($"https://osu.ppy.sh/home/changelog/lazer/{version}"); - return true; - }; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, ChangelogOverlay changelog) { + Icon = FontAwesome.Solid.CheckSquare; IconBackgound.Colour = colours.BlueDark; + + Activated = delegate + { + changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); + return true; + }; } } protected override void PopIn() { - this.FadeIn(1000); + this.FadeIn(1400, Easing.OutQuint); } protected override void PopOut() { + this.FadeOut(500, Easing.OutQuint); } } } diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 257155478f..cb488fea52 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.IO; @@ -11,6 +11,7 @@ using osu.Framework.Development; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.IPC; +using osu.Game.Tournament; namespace osu.Desktop { @@ -31,6 +32,7 @@ namespace osu.Desktop var importer = new ArchiveImportIPCChannel(host); // Restore the cwd so relative paths given at the command line work correctly Directory.SetCurrentDirectory(cwd); + foreach (var file in args) { Console.WriteLine(@"Importing {0}", file); @@ -45,6 +47,10 @@ namespace osu.Desktop default: host.Run(new OsuGameDesktop(args)); break; + + case "--tournament": + host.Run(new TournamentGame()); + break; } } diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index 6c363422f7..5184791de1 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -1,17 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.IO.Network; using osu.Framework.Platform; using osu.Game; -using osu.Game.Graphics; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; @@ -41,24 +40,32 @@ namespace osu.Desktop.Updater private async void checkForUpdateAsync() { - var releases = new JsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest"); - await releases.PerformAsync(); - - var latest = releases.ResponseObject; - - if (latest.TagName != version) + try { - notificationOverlay.Post(new SimpleNotification + var releases = new JsonWebRequest("https://api.github.com/repos/ppy/osu/releases/latest"); + + await releases.PerformAsync(); + + var latest = releases.ResponseObject; + + if (latest.TagName != version) { - Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" - + "Click here to download the new version, which can be installed over the top of your existing installation", - Icon = FontAwesome.fa_upload, - Activated = () => + notificationOverlay.Post(new SimpleNotification { - host.OpenUrlExternally(getBestUrl(latest)); - return true; - } - }); + Text = $"A newer release of osu! has been found ({version} → {latest.TagName}).\n\n" + + "Click here to download the new version, which can be installed over the top of your existing installation", + Icon = FontAwesome.Solid.Upload, + Activated = () => + { + host.OpenUrlExternally(getBestUrl(latest)); + return true; + } + }); + } + } + catch + { + // we shouldn't crash on a web failure. or any failure for the matter. } } @@ -69,10 +76,11 @@ namespace osu.Desktop.Updater switch (RuntimeInfo.OS) { case RuntimeInfo.Platform.Windows: - bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".exe")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe")); break; + case RuntimeInfo.Platform.MacOsx: - bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".app.zip")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; } diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 19383d617f..69064a40cb 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -1,14 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using System.IO; -using System.Reflection; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Logging; using osu.Game; using osu.Game.Graphics; @@ -26,11 +25,7 @@ namespace osu.Desktop.Updater private UpdateManager updateManager; private NotificationOverlay notificationOverlay; - public void PrepareUpdate() - { - // Squirrel returns execution to us after the update process is started, so it's safe to use Wait() here - UpdateManager.RestartAppWhenExited().Wait(); - } + public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited(); [BackgroundDependencyLoader] private void load(NotificationOverlay notification, OsuGameBase game) @@ -47,7 +42,7 @@ namespace osu.Desktop.Updater private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { //should we schedule a retry on completion of this check? - bool scheduleRetry = true; + bool scheduleRecheck = true; try { @@ -87,10 +82,11 @@ namespace osu.Desktop.Updater //could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) //try again without deltas. checkForUpdateAsync(false, notification); - scheduleRetry = false; + scheduleRecheck = false; } else { + notification.State = ProgressNotificationState.Cancelled; Logger.Error(e, @"update failed!"); } } @@ -101,11 +97,8 @@ namespace osu.Desktop.Updater } finally { - if (scheduleRetry) + if (scheduleRecheck) { - if (notification != null) - notification.State = ProgressNotificationState.Cancelled; - //check again in 30 minutes. Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30); } @@ -135,8 +128,8 @@ namespace osu.Desktop.Updater Text = @"Update ready to install. Click to restart!", Activated = () => { - updateManager.PrepareUpdate(); - game.GracefullyExit(); + updateManager.PrepareUpdateAsync() + .ContinueWith(_ => Schedule(() => game.GracefullyExit())); return true; } }; @@ -158,7 +151,7 @@ namespace osu.Desktop.Updater { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_upload, + Icon = FontAwesome.Solid.Upload, Colour = Color4.White, Size = new Vector2(20), } @@ -168,23 +161,19 @@ namespace osu.Desktop.Updater private class SquirrelLogger : Splat.ILogger, IDisposable { - private readonly string path; - private readonly object locker = new object(); public LogLevel Level { get; set; } = LogLevel.Info; - public SquirrelLogger() - { - var file = Path.Combine(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), "SquirrelSetupUpdater.log"); - if (File.Exists(file)) File.Delete(file); - path = file; - } + private Logger logger; public void Write(string message, LogLevel logLevel) { if (logLevel < Level) return; - lock (locker) File.AppendAllText(path, message + "\r\n"); + if (logger == null) + logger = Logger.GetLogger("updater"); + + logger.Add(message); } public void Dispose() diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index 09bfdc67d4..8c9e1f279f 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp2.2 WinExe AnyCPU true @@ -17,19 +17,19 @@ osu.Desktop.Program + - - - - + + + diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec index cdd232a9b2..a26b35fcd5 100644 --- a/osu.Desktop/osu.nuspec +++ b/osu.Desktop/osu.nuspec @@ -12,7 +12,7 @@ click the circles. to the beat. click the circles. testing - Copyright ppy Pty Ltd 2007-2018 + Copyright (c) 2019 ppy Pty Ltd en-AU diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..39fe3dac25 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Catch.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs new file mode 100644 index 0000000000..44817c1304 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Catch.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist new file mode 100644 index 0000000000..5115746cbb --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Catch.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Catch-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj new file mode 100644 index 0000000000..37e7c45a4e --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -0,0 +1,45 @@ + + + + + Debug + iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70} + Exe + osu.Game.Rulesets.Catch.Tests + osu.Game.Rulesets.Catch.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} + osu.Game.Rulesets.Catch + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json index da9344b6a2..5dfaf5ec39 100644 --- a/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Catch.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index 162624da57..e45ed8c6f4 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Catch.Tests [TestCase("basic")] [TestCase("spinner")] [TestCase("spinner-and-circles")] + [TestCase("slider")] public new void Test(string name) { base.Test(name); @@ -33,13 +34,18 @@ namespace osu.Game.Rulesets.Catch.Tests case JuiceStream stream: foreach (var nested in stream.NestedHitObjects) yield return new ConvertValue((CatchHitObject)nested); + break; + case BananaShower shower: foreach (var nested in shower.NestedHitObjects) yield return new ConvertValue((CatchHitObject)nested); + break; + default: yield return new ConvertValue((CatchHitObject)hitObject); + break; } } diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..51fe0b035d --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Difficulty; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class CatchDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; + + [TestCase(4.2058561036909863d, "diffcalc-test")] + public void Test(double expected, string name) + => base.Test(expected, name); + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(new CatchRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new CatchRuleset(); + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs deleted file mode 100644 index a2c886f1f3..0000000000 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchPlayer.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer - { - public TestCaseCatchPlayer() : base(new CatchRuleset()) - { - } - } -} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs deleted file mode 100644 index 14487b2c7f..0000000000 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseHyperDash.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Objects; - -namespace osu.Game.Rulesets.Catch.Tests -{ - [TestFixture] - public class TestCaseHyperDash : Game.Tests.Visual.TestCasePlayer - { - public TestCaseHyperDash() - : base(new CatchRuleset()) - { - } - - protected override IBeatmap CreateBeatmap(Ruleset ruleset) - { - var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; - - - for (int i = 0; i < 512; i++) - if (i % 5 < 3) - beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); - - return beatmap; - } - } -} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs similarity index 74% rename from osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index fc3809fae4..ab3c040b4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Game.Beatmaps; @@ -13,21 +13,21 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestCaseAutoJuiceStream : TestCasePlayer + public class TestSceneAutoJuiceStream : PlayerTestScene { - public TestCaseAutoJuiceStream() + public TestSceneAutoJuiceStream() : base(new CatchRuleset()) { } - protected override IBeatmap CreateBeatmap(Ruleset ruleset) + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, - Ruleset = ruleset.RulesetInfo + Ruleset = ruleset } }; @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests protected override Player CreatePlayer(Ruleset ruleset) { - Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); return base.CreatePlayer(ruleset); } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs similarity index 68% rename from osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs index b5cf0e3d1d..0ad72412fc 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,11 +8,12 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer + public class TestSceneBananaShower : PlayerTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -20,22 +21,22 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableBananaShower), typeof(CatchRuleset), - typeof(CatchRulesetContainer), + typeof(DrawableCatchRuleset), }; - public TestCaseBananaShower() + public TestSceneBananaShower() : base(new CatchRuleset()) { } - protected override IBeatmap CreateBeatmap(Ruleset ruleset) + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, - Ruleset = ruleset.RulesetInfo + Ruleset = ruleset } }; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs new file mode 100644 index 0000000000..9836a7811a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneCatchPlayer : PlayerTestScene + { + public TestSceneCatchPlayer() + : base(new CatchRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs similarity index 63% rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs index 8a90b48180..9ce46ad6ba 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs @@ -1,32 +1,32 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer + public class TestSceneCatchStacker : PlayerTestScene { - public TestCaseCatchStacker() + public TestSceneCatchStacker() : base(new CatchRuleset()) { } - protected override IBeatmap CreateBeatmap(Ruleset ruleset) + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap { BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, - Ruleset = ruleset.RulesetInfo + Ruleset = ruleset } }; - for (int i = 0; i < 512; i++) beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs new file mode 100644 index 0000000000..33f93cdb4a --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Tests.Visual; +using System; +using System.Collections.Generic; +using osu.Game.Skinning; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osuTK.Graphics; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneCatcher : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(CatcherSprite), + }; + + private readonly Container container; + + public TestSceneCatcher() + { + Child = container = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + AddStep("show default catcher implementation", () => { container.Child = new CatcherSprite(); }); + + AddStep("show custom catcher implementation", () => + { + container.Child = new CatchCustomSkinSourceContainer + { + Child = new CatcherSprite() + }; + }); + } + + private class CatcherCustomSkin : Container + { + public CatcherCustomSkin() + { + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue + }, + new SpriteText + { + Text = "custom" + } + }; + } + } + + [Cached(typeof(ISkinSource))] + private class CatchCustomSkinSourceContainer : Container, ISkinSource + { + public event Action SourceChanged + { + add { } + remove { } + } + + public Drawable GetDrawableComponent(string componentName) + { + switch (componentName) + { + case "Play/Catch/fruit-catcher-idle": + return new CatcherCustomSkin(); + } + + return null; + } + + public SampleChannel GetSample(string sampleName) => + throw new NotImplementedException(); + + public Texture GetTexture(string componentName) => + throw new NotImplementedException(); + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => + throw new NotImplementedException(); + } + } +} diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs similarity index 86% rename from osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 25f7ca108d..3ae6886c31 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -13,7 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseCatcherArea : OsuTestCase + public class TestSceneCatcherArea : OsuTestScene { private RulesetInfo catchRuleset; private TestCatcherArea catcherArea; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatcherArea), }; - public TestCaseCatcherArea() + public TestSceneCatcherArea() { AddSliderStep("CircleSize", 0, 8, 5, createCatcher); AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t)); diff --git a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs similarity index 90% rename from osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs rename to osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs index 7b6773ad58..44517382f7 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestCaseFruitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestCaseFruitObjects : OsuTestCase + public class TestSceneFruitObjects : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(Pulp), }; - public TestCaseFruitObjects() + public TestSceneFruitObjects() { Add(new GridContainer { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs new file mode 100644 index 0000000000..a603d96201 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Catch.Tests +{ + [TestFixture] + public class TestSceneHyperDash : PlayerTestScene + { + public TestSceneHyperDash() + : base(new CatchRuleset()) + { + } + + [Test] + public void TestHyperDash() + { + AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = + { + Ruleset = ruleset, + BaseDifficulty = new BeatmapDifficulty { CircleSize = 3.6f } + } + }; + + // Should produce a hyper-dash + beatmap.HitObjects.Add(new Fruit { StartTime = 816, X = 308 / 512f, NewCombo = true }); + beatmap.HitObjects.Add(new Fruit { StartTime = 1008, X = 56 / 512f, }); + + for (int i = 0; i < 512; i++) + if (i % 5 < 3) + beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = 2000 + i * 100, NewCombo = i % 8 == 0 }); + + return beatmap; + } + } +} 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 b76f591239..265ecb7688 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,14 +2,14 @@ - - - + + + WinExe - netcoreapp2.1 + netcoreapp2.2 diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs index 5b4af6ea8a..18cc300ff9 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Objects; namespace osu.Game.Rulesets.Catch.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps { Name = @"Fruit Count", Content = fruits.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Juice Stream Count", Content = juiceStreams.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Banana Shower Count", Content = bananaShowers.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index c3dc9499c2..0d9a663b9f 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 22c1180c09..645cb5701a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -31,6 +31,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps initialiseHyperDash((List)Beatmap.HitObjects); int index = 0; + foreach (var obj in Beatmap.HitObjects.OfType()) { obj.IndexInBeatmap = index++; @@ -56,7 +57,9 @@ namespace osu.Game.Rulesets.Catch.Beatmaps rng.Next(); // osu!stable retrieved a random banana rotation rng.Next(); // osu!stable retrieved a random banana colour } + break; + case JuiceStream juiceStream: foreach (var nested in juiceStream.NestedHitObjects) { @@ -67,6 +70,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps rng.Next(); // osu!stable retrieved a random droplet rotation hitObject.X = MathHelper.Clamp(hitObject.X, 0, 1); } + break; } } @@ -98,9 +102,10 @@ namespace osu.Game.Rulesets.Catch.Beatmaps CatchHitObject nextObject = objectWithDroplets[i + 1]; int thisDirection = nextObject.X > currentObject.X ? 1 : -1; - double timeToNext = nextObject.StartTime - currentObject.StartTime; + double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); float distanceToHyper = (float)(timeToNext * CatcherArea.Catcher.BASE_SPEED - distanceToNext); + if (distanceToHyper < 0) { currentObject.HyperDashTarget = nextObject; diff --git a/osu.Game.Rulesets.Catch/CatchInputManager.cs b/osu.Game.Rulesets.Catch/CatchInputManager.cs index 4f976bb7b2..021d7a7efe 100644 --- a/osu.Game.Rulesets.Catch/CatchInputManager.cs +++ b/osu.Game.Rulesets.Catch/CatchInputManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; using osu.Framework.Input.Bindings; @@ -19,8 +19,10 @@ namespace osu.Game.Rulesets.Catch { [Description("Move left")] MoveLeft, + [Description("Move right")] MoveRight, + [Description("Engage dash")] Dash, } diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index 1f1d2475f6..ea9f225cc1 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -9,19 +9,22 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Difficulty; using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Catch { public class CatchRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new CatchRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableCatchRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new CatchBeatmapProcessor(beatmap); @@ -84,6 +87,7 @@ namespace osu.Game.Rulesets.Catch new CatchModNoFail(), new MultiMod(new CatchModHalfTime(), new CatchModDaycore()) }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -93,12 +97,20 @@ namespace osu.Game.Rulesets.Catch new CatchModHidden(), new CatchModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new CatchModAutoplay(), new ModCinema()), new CatchModRelax(), }; + + case ModType.Fun: + return new Mod[] + { + new MultiMod(new ModWindUp(), new ModWindDown()) + }; + default: return new Mod[] { }; } @@ -108,10 +120,12 @@ namespace osu.Game.Rulesets.Catch public override string ShortName => "fruits"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new CatchPerformanceCalculator(this, beatmap, score); + public override int? LegacyID => 2; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs index f6535380c8..75f5b18607 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyAttributes.cs @@ -1,8 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { @@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Catch.Difficulty { public double ApproachRate; public int MaxCombo; - - public CatchDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) - { - } } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index a763989750..44e1a8e5cc 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -1,149 +1,91 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.Difficulty.Skills; +using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Difficulty { public class CatchDifficultyCalculator : DifficultyCalculator { - - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 750; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.94; - private const double star_scaling_factor = 0.145; + protected override int SectionLength => 750; + public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - if (!beatmap.HitObjects.Any()) - return new CatchDifficultyAttributes(mods, 0); - - var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty); - float halfCatchWidth = catcher.CatchWidth * 0.5f; - - var difficultyHitObjects = new List(); - - foreach (var hitObject in beatmap.HitObjects) - { - switch (hitObject) - { - // We want to only consider fruits that contribute to the combo. Droplets are addressed as accuracy and spinners are not relevant for "skill" calculations. - case Fruit fruit: - difficultyHitObjects.Add(new CatchDifficultyHitObject(fruit, halfCatchWidth)); - break; - case JuiceStream _: - difficultyHitObjects.AddRange(hitObject.NestedHitObjects.OfType().Where(o => !(o is TinyDroplet)).Select(o => new CatchDifficultyHitObject(o, halfCatchWidth))); - break; - } - } - - difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - - if (!calculateStrainValues(difficultyHitObjects, timeRate)) - return new CatchDifficultyAttributes(mods, 0); + if (beatmap.HitObjects.Count == 0) + return new CatchDifficultyAttributes { Mods = mods, Skills = skills }; // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / timeRate; - double starRating = Math.Sqrt(calculateDifficulty(difficultyHitObjects, timeRate)) * star_scaling_factor; + double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; - return new CatchDifficultyAttributes(mods, starRating) + return new CatchDifficultyAttributes { + StarRating = Math.Sqrt(skills[0].DifficultyValue()) * star_scaling_factor, + Mods = mods, ApproachRate = preempt > 1200.0 ? -(preempt - 1800.0) / 120.0 : -(preempt - 1200.0) / 150.0 + 5.0, - MaxCombo = difficultyHitObjects.Count + MaxCombo = beatmap.HitObjects.Count(h => h is Fruit) + beatmap.HitObjects.OfType().SelectMany(j => j.NestedHitObjects).Count(h => !(h is TinyDroplet)), + Skills = skills }; } - private bool calculateStrainValues(List objects, double timeRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - CatchDifficultyHitObject lastObject = null; + float halfCatchWidth; - if (!objects.Any()) return false; - - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - foreach (var currentObject in objects) + using (var catcher = new CatcherArea.Catcher(beatmap.BeatmapInfo.BaseDifficulty)) { + halfCatchWidth = catcher.CatchWidth * 0.5f; + halfCatchWidth *= 0.8f; // We're only using 80% of the catcher's width to simulate imperfect gameplay. + } + + CatchHitObject lastObject = null; + + // In 2B beatmaps, it is possible that a normal Fruit is placed in the middle of a JuiceStream. + foreach (var hitObject in beatmap.HitObjects + .SelectMany(obj => obj is JuiceStream stream ? stream.NestedHitObjects : new[] { obj }) + .Cast() + .OrderBy(x => x.StartTime)) + { + // We want to only consider fruits that contribute to the combo. + if (hitObject is BananaShower || hitObject is TinyDroplet) + continue; + if (lastObject != null) - currentObject.CalculateStrains(lastObject, timeRate); + yield return new CatchDifficultyHitObject(hitObject, lastObject, clockRate, halfCatchWidth); - lastObject = currentObject; + lastObject = hitObject; } - - return true; } - private double calculateDifficulty(List objects, double timeRate) + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { - // The strain step needs to be adjusted for the algorithm to be considered equal with speed changing mods - double actualStrainStep = strain_step * timeRate; + new Movement(), + }; - // Find the highest strain value within each strain step - var highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - CatchDifficultyHitObject previousHitObject = null; - foreach (CatchDifficultyHitObject hitObject in objects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double decay = Math.Pow(CatchDifficultyHitObject.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.Strain * decay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - maximumStrain = Math.Max(hitObject.Strain, maximumStrain); - - previousHitObject = hitObject; - } - - // Build the weighted sum over the highest strains for each interval - double difficulty = 0; - double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - foreach (double strain in highestStrains) - { - difficulty += weight * strain; - weight *= decay_weight; - } - - return difficulty; - } + protected override Mod[] DifficultyAdjustmentMods => new Mod[] + { + new CatchModDoubleTime(), + new CatchModHalfTime(), + new CatchModHardRock(), + new CatchModEasy(), + }; } } diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs deleted file mode 100644 index bd7796ed91..0000000000 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyHitObject.cs +++ /dev/null @@ -1,130 +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 osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; -using osuTK; - -namespace osu.Game.Rulesets.Catch.Difficulty -{ - public class CatchDifficultyHitObject - { - internal static readonly double DECAY_BASE = 0.20; - private const float normalized_hitobject_radius = 41.0f; - private const float absolute_player_positioning_error = 16f; - private readonly float playerPositioningError; - - internal CatchHitObject BaseHitObject; - - /// - /// Measures jump difficulty. CtB doesn't have something like button pressing speed or accuracy - /// - internal double Strain = 1; - - /// - /// This is required to keep track of lazy player movement (always moving only as far as necessary) - /// Without this quick repeat sliders / weirdly shaped streams might become ridiculously overrated - /// - internal float PlayerPositionOffset; - internal float LastMovement; - - internal float NormalizedPosition; - internal float ActualNormalizedPosition => NormalizedPosition + PlayerPositionOffset; - - internal CatchDifficultyHitObject(CatchHitObject baseHitObject, float catcherWidthHalf) - { - BaseHitObject = baseHitObject; - - // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. - float scalingFactor = normalized_hitobject_radius / catcherWidthHalf; - - playerPositioningError = absolute_player_positioning_error; // * scalingFactor; - NormalizedPosition = baseHitObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; - } - - private const double direction_change_bonus = 12.5; - internal void CalculateStrains(CatchDifficultyHitObject previousHitObject, double timeRate) - { - // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. - // See Taiko feedback thread. - double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); - - // Update new position with lazy movement. - PlayerPositionOffset = - MathHelper.Clamp( - previousHitObject.ActualNormalizedPosition, - NormalizedPosition - (normalized_hitobject_radius - playerPositioningError), - NormalizedPosition + (normalized_hitobject_radius - playerPositioningError)) // Obtain new lazy position, but be stricter by allowing for an error of a certain degree of the player. - - NormalizedPosition; // Subtract HitObject position to obtain offset - - LastMovement = DistanceTo(previousHitObject); - double addition = spacingWeight(LastMovement); - - if (NormalizedPosition < previousHitObject.NormalizedPosition) - { - LastMovement = -LastMovement; - } - - CatchHitObject previousHitCircle = previousHitObject.BaseHitObject; - - double additionBonus = 0; - double sqrtTime = Math.Sqrt(Math.Max(timeElapsed, 25)); - - // Direction changes give an extra point! - if (Math.Abs(LastMovement) > 0.1) - { - if (Math.Abs(previousHitObject.LastMovement) > 0.1 && Math.Sign(LastMovement) != Math.Sign(previousHitObject.LastMovement)) - { - double bonus = direction_change_bonus / sqrtTime; - - // Weight bonus by how - double bonusFactor = Math.Min(playerPositioningError, Math.Abs(LastMovement)) / playerPositioningError; - - // We want time to play a role twice here! - addition += bonus * bonusFactor; - - // Bonus for tougher direction switches and "almost" hyperdashes at this point - if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) - { - additionBonus += 0.3 * bonusFactor; - } - } - - // Base bonus for every movement, giving some weight to streams. - addition += 7.5 * Math.Min(Math.Abs(LastMovement), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtTime; - } - - // Bonus for "almost" hyperdashes at corner points - if (previousHitCircle != null && previousHitCircle.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) - { - if (!previousHitCircle.HyperDash) - { - additionBonus += 1.0; - } - else - { - // After a hyperdash we ARE in the correct position. Always! - PlayerPositionOffset = 0; - } - - addition *= 1.0 + additionBonus * ((10 - previousHitCircle.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); - } - - addition *= 850.0 / Math.Max(timeElapsed, 25); - - Strain = previousHitObject.Strain * decay + addition; - } - - private static double spacingWeight(float distance) - { - return Math.Pow(distance, 1.3) / 500; - } - - internal float DistanceTo(CatchDifficultyHitObject other) - { - return Math.Abs(ActualNormalizedPosition - other.ActualNormalizedPosition); - } - } -} diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs new file mode 100644 index 0000000000..5a640f6d1a --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Difficulty +{ + public class CatchPerformanceCalculator : PerformanceCalculator + { + protected new CatchDifficultyAttributes Attributes => (CatchDifficultyAttributes)base.Attributes; + + private Mod[] mods; + + private int fruitsHit; + private int ticksHit; + private int tinyTicksHit; + private int tinyTicksMissed; + private int misses; + + public CatchPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) + : base(ruleset, beatmap, score) + { + } + + public override double Calculate(Dictionary categoryDifficulty = null) + { + mods = Score.Mods; + + var legacyScore = Score as LegacyScoreInfo; + + fruitsHit = legacyScore?.Count300 ?? Score.Statistics[HitResult.Perfect]; + ticksHit = legacyScore?.Count100 ?? 0; + tinyTicksHit = legacyScore?.Count50 ?? 0; + tinyTicksMissed = legacyScore?.CountKatu ?? 0; + misses = Score.Statistics[HitResult.Miss]; + + // Don't count scores made with supposedly unranked mods + if (mods.Any(m => !m.Ranked)) + return 0; + + // We are heavily relying on aim in catch the beat + double value = Math.Pow(5.0f * Math.Max(1.0f, Attributes.StarRating / 0.0049f) - 4.0f, 2.0f) / 100000.0f; + + // Longer maps are worth more. "Longer" means how many hits there are which can contribute to combo + int numTotalHits = totalComboHits(); + + // Longer maps are worth more + float lengthBonus = + 0.95f + 0.4f * Math.Min(1.0f, numTotalHits / 3000.0f) + + (numTotalHits > 3000 ? (float)Math.Log10(numTotalHits / 3000.0f) * 0.5f : 0.0f); + + // Longer maps are worth more + value *= lengthBonus; + + // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available + value *= Math.Pow(0.97f, misses); + + // Combo scaling + float beatmapMaxCombo = Attributes.MaxCombo; + if (beatmapMaxCombo > 0) + value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + + float approachRate = (float)Attributes.ApproachRate; + float approachRateFactor = 1.0f; + if (approachRate > 9.0f) + approachRateFactor += 0.1f * (approachRate - 9.0f); // 10% for each AR above 9 + else if (approachRate < 8.0f) + approachRateFactor += 0.025f * (8.0f - approachRate); // 2.5% for each AR below 8 + + value *= approachRateFactor; + + if (mods.Any(m => m is ModHidden)) + // Hiddens gives nothing on max approach rate, and more the lower it is + value *= 1.05f + 0.075f * (10.0f - Math.Min(10.0f, approachRate)); // 7.5% for each AR below 10 + + if (mods.Any(m => m is ModFlashlight)) + // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. + value *= 1.35f * lengthBonus; + + // Scale the aim value with accuracy _slightly_ + value *= Math.Pow(accuracy(), 5.5f); + + // Custom multipliers for NoFail. SpunOut is not applicable. + if (mods.Any(m => m is ModNoFail)) + value *= 0.90f; + + return value; + } + + private float accuracy() => totalHits() == 0 ? 0 : MathHelper.Clamp((float)totalSuccessfulHits() / totalHits(), 0f, 1f); + private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed; + private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit; + private int totalComboHits() => misses + ticksHit + fruitsHit; + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs new file mode 100644 index 0000000000..24e526ed19 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing +{ + public class CatchDifficultyHitObject : DifficultyHitObject + { + private const float normalized_hitobject_radius = 41.0f; + + public new CatchHitObject BaseObject => (CatchHitObject)base.BaseObject; + + public new CatchHitObject LastObject => (CatchHitObject)base.LastObject; + + public readonly float NormalizedPosition; + public readonly float LastNormalizedPosition; + + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// + public readonly double StrainTime; + + public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth) + : base(hitObject, lastObject, clockRate) + { + // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. + var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; + + NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; + LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; + + // Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure + StrainTime = Math.Max(25, DeltaTime); + } + } +} diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs new file mode 100644 index 0000000000..d146153294 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Difficulty.Skills +{ + public class Movement : Skill + { + private const float absolute_player_positioning_error = 16f; + private const float normalized_hitobject_radius = 41.0f; + private const double direction_change_bonus = 12.5; + + protected override double SkillMultiplier => 850; + protected override double StrainDecayBase => 0.2; + + protected override double DecayWeight => 0.94; + + private float? lastPlayerPosition; + private float lastDistanceMoved; + + protected override double StrainValueOf(DifficultyHitObject current) + { + var catchCurrent = (CatchDifficultyHitObject)current; + + if (lastPlayerPosition == null) + lastPlayerPosition = catchCurrent.LastNormalizedPosition; + + float playerPosition = MathHelper.Clamp( + lastPlayerPosition.Value, + catchCurrent.NormalizedPosition - (normalized_hitobject_radius - absolute_player_positioning_error), + catchCurrent.NormalizedPosition + (normalized_hitobject_radius - absolute_player_positioning_error) + ); + + float distanceMoved = playerPosition - lastPlayerPosition.Value; + + double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500; + double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime); + + double bonus = 0; + + // Direction changes give an extra point! + if (Math.Abs(distanceMoved) > 0.1) + { + if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved)) + { + double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error; + + distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor; + + // Bonus for tougher direction switches and "almost" hyperdashes at this point + if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH) + bonus = 0.3 * bonusFactor; + } + + // Base bonus for every movement, giving some weight to streams. + distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain; + } + + // Bonus for "almost" hyperdashes at corner points + if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH) + { + if (!catchCurrent.LastObject.HyperDash) + bonus += 1.0; + else + { + // After a hyperdash we ARE in the correct position. Always! + playerPosition = catchCurrent.NormalizedPosition; + } + + distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10); + } + + lastPlayerPosition = playerPosition; + lastDistanceMoved = distanceMoved; + + return distanceAddition / catchCurrent.StrainTime; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index f38009263f..374dd50c11 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -16,19 +16,21 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 1100; } } - protected override float HealthIncreaseFor(HitResult result) + protected override double HealthIncreaseFor(HitResult result) { switch (result) { default: return 0; + case HitResult.Perfect: - return 8; + return 0.008; } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs index 0df2305339..f1399bb5c0 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchDropletJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; @@ -13,19 +13,21 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 30; } } - protected override float HealthIncreaseFor(HitResult result) + protected override double HealthIncreaseFor(HitResult result) { switch (result) { default: - return 0; + return base.HealthIncreaseFor(result); + case HitResult.Perfect: - return 7; + return 0.007; } } } diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs index 8a51867899..8fd9ac92ba 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -17,34 +17,24 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 300; } } - /// - /// Retrieves the numeric health increase of a . - /// - /// The to find the numeric health increase for. - /// The numeric health increase of . - protected virtual float HealthIncreaseFor(HitResult result) + protected override double HealthIncreaseFor(HitResult result) { switch (result) { default: - return 0; + return -0.02; + case HitResult.Perfect: - return 10.2f; + return 0.01; } } - /// - /// Retrieves the numeric health increase of a . - /// - /// The to find the numeric health increase for. - /// The numeric health increase of . - public float HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type); - /// /// Whether fruit on the platter should explode or drop. /// Note that this is only checked if the owning object is also diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs index 8b77351027..3829b5e94f 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchTinyDropletJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; @@ -15,19 +15,21 @@ namespace osu.Game.Rulesets.Catch.Judgements { default: return 0; + case HitResult.Perfect: return 10; } } - protected override float HealthIncreaseFor(HitResult result) + protected override double HealthIncreaseFor(HitResult result) { switch (result) { default: return 0; + case HitResult.Perfect: - return 4; + return 0.004; } } } diff --git a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs index 5b3835755a..b3605b013b 100644 --- a/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs +++ b/osu.Game.Rulesets.Catch/MathUtils/FastRandom.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; @@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils /// The random value. public uint NextUInt() { - uint t = _x ^ _x << 11; + uint t = _x ^ (_x << 11); _x = _y; _y = _z; _z = _w; - return _w = _w ^ _w >> 19 ^ t ^ t >> 8; + return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8); } /// diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index 5ed563614a..692e63fa69 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -1,24 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Mods { public class CatchModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "osu!salad!" }, - Replay = new CatchAutoGenerator(beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + Replay = new CatchAutoGenerator(beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs index 6d4caef8d2..cae19e9468 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs index dcf417c405..178909387f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index d20a2ec727..a82d0af102 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 9cfba0236a..71268d899d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; @@ -20,10 +21,10 @@ namespace osu.Game.Rulesets.Catch.Mods private CatchPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (CatchPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (CatchPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class CatchFlashlight : Flashlight @@ -55,9 +56,9 @@ namespace osu.Game.Rulesets.Catch.Mods return default_flashlight_size; } - protected override void OnComboChange(int newCombo) + protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs index 4e48de454f..ce06b841aa 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 8e19c0614a..060e51e31d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.MathUtils; using osu.Game.Rulesets.Catch.Objects; @@ -15,77 +15,107 @@ namespace osu.Game.Rulesets.Catch.Mods public override double ScoreMultiplier => 1.12; public override bool Ranked => true; - private float lastStartX; - private int lastStartTime; + private float? lastPosition; + private double lastStartTime; public void ApplyToHitObject(HitObject hitObject) { + if (hitObject is JuiceStream stream) + { + lastPosition = stream.EndX; + lastStartTime = stream.EndTime; + return; + } + + if (!(hitObject is Fruit)) + return; + var catchObject = (CatchHitObject)hitObject; float position = catchObject.X; - int startTime = (int)hitObject.StartTime; + double startTime = hitObject.StartTime; - if (lastStartX == 0) + if (lastPosition == null) { - lastStartX = position; + lastPosition = position; lastStartTime = startTime; + return; } - float diff = lastStartX - position; - int timeDiff = startTime - lastStartTime; + float positionDiff = position - lastPosition.Value; + double timeDiff = startTime - lastStartTime; if (timeDiff > 1000) { - lastStartX = position; + lastPosition = position; lastStartTime = startTime; return; } - if (diff == 0) + if (positionDiff == 0) { - bool right = RNG.NextBool(); - - float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH; - - if (right) - { - if (position + rand <= 1) - position += rand; - else - position -= rand; - } - else - { - if (position - rand >= 0) - position -= rand; - else - position += rand; - } - + applyRandomOffset(ref position, timeDiff / 4d); catchObject.X = position; - return; } - if (Math.Abs(diff) < timeDiff / 3d) - { - if (diff > 0) - { - if (position - diff > 0) - position -= diff; - } - else - { - if (position - diff < 1) - position -= diff; - } - } + if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3d) + applyOffset(ref position, positionDiff); catchObject.X = position; - lastStartX = position; + lastPosition = position; lastStartTime = startTime; } + + /// + /// Applies a random offset in a random direction to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The maximum offset, cannot exceed 20px. + private void applyRandomOffset(ref float position, double maxOffset) + { + bool right = RNG.NextBool(); + float rand = Math.Min(20, (float)RNG.NextDouble(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH; + + if (right) + { + // Clamp to the right bound + if (position + rand <= 1) + position += rand; + else + position -= rand; + } + else + { + // Clamp to the left bound + if (position - rand >= 0) + position -= rand; + else + position += rand; + } + } + + /// + /// Applies an offset to a position, ensuring that the final position remains within the boundary of the playfield. + /// + /// The position which the offset should be applied to. + /// The amount to offset by. + private void applyOffset(ref float position, float amount) + { + if (amount > 0) + { + // Clamp to the right bound + if (position + amount < 1) + position += amount; + } + else + { + // Clamp to the left bound + if (position + amount > 0) + position += amount; + } + } } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index f2716f351e..9990b01427 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs index 687db172cf..da2edcee44 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs index 914419438d..3c02646e99 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs index de00ff31ce..fb92399102 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 8bf9f32572..0454bc969d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs index 71461cfc40..68e01391ce 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Objects/Banana.cs b/osu.Game.Rulesets.Catch/Objects/Banana.cs index e1af4c1075..0b3d1d23e0 100644 --- a/osu.Game.Rulesets.Catch/Objects/Banana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Banana.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 25af7e4bdf..6d44e4660e 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 0c50dae9fb..2153b8dc85 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs new file mode 100644 index 0000000000..837662f5fe --- /dev/null +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitWindows.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Catch.Objects +{ + public class CatchHitWindows : HitWindows + { + public override bool IsHitResultAllowed(HitResult result) + { + switch (result) + { + case HitResult.Perfect: + case HitResult.Miss: + return true; + } + + return false; + } + } +} diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs index 8756a5178f..5afdb14888 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBanana.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Catch.Objects.Drawable { diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs index 697fab85c9..42646851d7 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableBananaShower.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -13,17 +13,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container bananaContainer; - public DrawableBananaShower(BananaShower s, Func> getVisualRepresentation = null) + public DrawableBananaShower(BananaShower s, Func> createDrawableRepresentation = null) : base(s) { RelativeSizeAxes = Axes.X; Origin = Anchor.BottomLeft; X = 0; - InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; + AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both }); foreach (var b in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(b)); + AddNested(createDrawableRepresentation?.Invoke(b)); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs index 2db252ebc6..2f8ccec48b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableCatchHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable base.SkinChanged(skin, allowFallback); if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; } private const float preempt = 1000; @@ -84,6 +84,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable case ArmedState.Miss: this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); break; + case ArmedState.Hit: this.FadeOut().Expire(); break; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs index a896d13132..9cabdc3dd9 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableDroplet.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -26,15 +26,12 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable [BackgroundDependencyLoader] private void load() { - InternalChild = pulp = new Pulp - { - Size = Size - }; + AddInternal(pulp = new Pulp { Size = Size }); } public override Color4 AccentColour { - get { return base.AccentColour; } + get => base.AccentColour; set { base.AccentColour = value; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs index 4bd50f29f6..77407def54 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableFruit.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable // todo: this should come from the skin. AccentColour = colourForRepresentation(HitObject.VisualRepresentation); - InternalChildren = new[] + AddRangeInternal(new[] { createPulp(HitObject.VisualRepresentation), border = new Circle @@ -70,7 +71,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable } } }, - }; + }); if (HitObject.HyperDash) { @@ -105,6 +106,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { default: return new Container(); + case FruitVisualRepresentation.Raspberry: return new Container { @@ -143,6 +145,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pineapple: return new Container { @@ -181,6 +184,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Pear: return new Container { @@ -213,6 +217,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Grape: return new Container { @@ -245,6 +250,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable }, } }; + case FruitVisualRepresentation.Banana: return new Container { @@ -282,19 +288,25 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable default: case FruitVisualRepresentation.Pear: return new Color4(17, 136, 170, 255); + case FruitVisualRepresentation.Grape: return new Color4(204, 102, 0, 255); + case FruitVisualRepresentation.Raspberry: return new Color4(121, 9, 13, 255); + case FruitVisualRepresentation.Pineapple: return new Color4(102, 136, 0, 255); + case FruitVisualRepresentation.Banana: switch (RNG.Next(0, 3)) { default: return new Color4(255, 240, 0, 255); + case 1: return new Color4(255, 192, 0, 255); + case 2: return new Color4(214, 221, 28, 255); } diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs index e66852c5c2..9e5e9f6a04 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableJuiceStream.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -13,17 +13,17 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable { private readonly Container dropletContainer; - public DrawableJuiceStream(JuiceStream s, Func> getVisualRepresentation = null) + public DrawableJuiceStream(JuiceStream s, Func> createDrawableRepresentation = null) : base(s) { RelativeSizeAxes = Axes.Both; Origin = Anchor.BottomLeft; X = 0; - InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; + AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }); foreach (var o in s.NestedHitObjects.Cast()) - AddNested(getVisualRepresentation?.Invoke(o)); + AddNested(createDrawableRepresentation?.Invoke(o)); } protected override void AddNested(DrawableHitObject h) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs index 2f167121aa..d41aea1e7b 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/DrawableTinyDroplet.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs index f21c14f076..b9b6d5b924 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawable/Pieces/Pulp.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osuTK.Graphics; @@ -23,9 +23,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces } private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; diff --git a/osu.Game.Rulesets.Catch/Objects/Droplet.cs b/osu.Game.Rulesets.Catch/Objects/Droplet.cs index 8b54922959..7b0bb3f0ae 100644 --- a/osu.Game.Rulesets.Catch/Objects/Droplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/Droplet.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; diff --git a/osu.Game.Rulesets.Catch/Objects/Fruit.cs b/osu.Game.Rulesets.Catch/Objects/Fruit.cs index 2c2cd013c3..6f0423b420 100644 --- a/osu.Game.Rulesets.Catch/Objects/Fruit.cs +++ b/osu.Game.Rulesets.Catch/Objects/Fruit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d8bd3e0edc..0952e8981a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Audio; @@ -25,6 +24,11 @@ namespace osu.Game.Rulesets.Catch.Objects public double Velocity; public double TickDistance; + /// + /// The length of one span of this . + /// + public double SpanDuration => Duration / this.SpanCount(); + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -41,93 +45,68 @@ namespace osu.Game.Rulesets.Catch.Objects protected override void CreateNestedHitObjects() { base.CreateNestedHitObjects(); - createTicks(); - } - private void createTicks() - { - if (TickDistance == 0) - return; - - var length = Path.Distance; - var tickDistance = Math.Min(TickDistance, length); - var spanDuration = length / Velocity; - - var minDistanceFromEnd = Velocity * 0.01; - - AddNested(new Fruit + var tickSamples = Samples.Select(s => new HitSampleInfo { - Samples = Samples, - StartTime = StartTime, - X = X - }); + Bank = s.Bank, + Name = @"slidertick", + Volume = s.Volume + }).ToList(); - double lastDropletTime = StartTime; + SliderEventDescriptor? lastEvent = null; - for (int span = 0; span < this.SpanCount(); span++) + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - var spanStartTime = StartTime + span * spanDuration; - var reversed = span % 2 == 1; - - for (double d = 0; d <= length; d += tickDistance) + // generate tiny droplets since the last point + if (lastEvent != null) { - var timeProgress = d / length; - var distanceProgress = reversed ? 1 - timeProgress : timeProgress; + double sinceLastTick = e.Time - lastEvent.Value.Time; - double time = spanStartTime + timeProgress * spanDuration; - - if (LegacyLastTickOffset != null) + if (sinceLastTick > 80) { - // If we're the last tick, apply the legacy offset - if (span == this.SpanCount() - 1 && d + tickDistance > length) - time = Math.Max(StartTime + Duration / 2, time - LegacyLastTickOffset.Value); - } + double timeBetweenTiny = sinceLastTick; + while (timeBetweenTiny > 100) + timeBetweenTiny /= 2; - double tinyTickInterval = time - lastDropletTime; - while (tinyTickInterval > 100) - tinyTickInterval /= 2; - - for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval) - { - double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration; - - AddNested(new TinyDroplet + for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { - StartTime = t, - X = X + Path.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, - Samples = new List(Samples.Select(s => new SampleInfo + AddNested(new TinyDroplet { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); + Samples = tickSamples, + StartTime = t + lastEvent.Value.Time, + X = X + Path.PositionAt( + lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH, + }); + } } - - if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd) - { - AddNested(new Droplet - { - StartTime = time, - X = X + Path.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, - Samples = new List(Samples.Select(s => new SampleInfo - { - Bank = s.Bank, - Name = @"slidertick", - Volume = s.Volume - })) - }); - } - - lastDropletTime = time; } - AddNested(new Fruit + // this also includes LegacyLastTick and this is used for TinyDroplet generation above. + // this means that the final segment of TinyDroplets are increasingly mistimed where LegacyLastTickOffset is being applied. + lastEvent = e; + + switch (e.Type) { - Samples = Samples, - StartTime = spanStartTime + spanDuration, - X = X + Path.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH - }); + case SliderEventType.Tick: + AddNested(new Droplet + { + Samples = tickSamples, + StartTime = e.Time, + X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + }); + break; + + case SliderEventType.Head: + case SliderEventType.Tail: + case SliderEventType.Repeat: + AddNested(new Fruit + { + Samples = Samples, + StartTime = e.Time, + X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + }); + break; + } } } @@ -147,7 +126,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double Distance => Path.Distance; - public List> NodeSamples { get; set; } = new List>(); + public List> NodeSamples { get; set; } = new List>(); public double? LegacyLastTickOffset { get; set; } } diff --git a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs index 39f1cadad5..1bf160b5a6 100644 --- a/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs +++ b/osu.Game.Rulesets.Catch/Objects/TinyDroplet.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Judgements; diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs index 045d0824c6..dba76eef49 100644 --- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Runtime.CompilerServices; @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.iOS")] diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 23b620248f..8dd00756f2 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -1,25 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework.MathUtils; using osu.Game.Beatmaps; +using osu.Game.Replays; +using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Catch.Replays { - internal class CatchAutoGenerator : AutoGenerator + internal class CatchAutoGenerator : AutoGenerator { public const double RELEASE_DELAY = 20; - public CatchAutoGenerator(Beatmap beatmap) + public new CatchBeatmap Beatmap => (CatchBeatmap)base.Beatmap; + + public CatchAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; + Replay = new Replay(); } protected Replay Replay; @@ -40,10 +43,13 @@ namespace osu.Game.Rulesets.Catch.Replays float positionChange = Math.Abs(lastPosition - h.X); double timeAvailable = h.StartTime - lastTime; - //So we can either make it there without a dash or not. - double speedRequired = positionChange / timeAvailable; + // So we can either make it there without a dash or not. + // If positionChange is 0, we don't need to move, so speedRequired should also be 0 (could be NaN if timeAvailable is 0 too) + // The case where positionChange > 0 and timeAvailable == 0 results in PositiveInfinity which provides expected beheaviour. + double speedRequired = positionChange == 0 ? 0 : positionChange / timeAvailable; - bool dashRequired = speedRequired > movement_speed && h.StartTime != 0; + bool dashRequired = speedRequired > movement_speed; + bool impossibleJump = speedRequired > movement_speed * 2; // todo: get correct catcher size, based on difficulty CS. const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f; @@ -56,9 +62,8 @@ namespace osu.Game.Rulesets.Catch.Replays return; } - if (h is Banana) + if (impossibleJump) { - // auto bananas unrealistically warp to catch 100% combo. Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); } else if (h.HyperDash) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs index f05eb31454..103aa6c3f1 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchFramedReplayInputHandler.cs @@ -1,9 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Catch.Replays @@ -21,10 +23,14 @@ namespace osu.Game.Rulesets.Catch.Replays { get { - if (!HasFrames) + var frame = CurrentFrame; + + if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + Debug.Assert(CurrentTime != null); + + return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index d5c5eb844a..1e88b35c3b 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Catch.Replays diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..ebad654404 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,138 @@ +osu file format v14 + +[General] +StackLeniency: 0.3 +Mode: 2 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +34500,-50,4,2,1,50,0,0 + +[HitObjects] +// fruits spaced 1/1 beat apart +32,128,0,5,0,0:0:0:0: +96,128,500,1,0,0:0:0:0: +160,128,1000,1,0,0:0:0:0: +224,128,1500,1,0,0:0:0:0: +288,128,2000,1,0,0:0:0:0: +352,128,2500,1,0,0:0:0:0: +416,128,3000,1,0,0:0:0:0: +480,128,3500,1,0,0:0:0:0: + +// fruits spaced 1/2 beat apart +32,160,4500,1,0,0:0:0:0: +64,160,4750,1,0,0:0:0:0: +96,160,5000,1,0,0:0:0:0: +128,160,5250,1,0,0:0:0:0: +160,160,5500,1,0,0:0:0:0: +192,160,5750,1,0,0:0:0:0: +224,160,6000,1,0,0:0:0:0: +256,160,6250,1,0,0:0:0:0: +288,160,6500,1,0,0:0:0:0: + +// fruits spaced 1/4 beat apart +96,128,7500,1,0,0:0:0:0: +128,128,7625,1,0,0:0:0:0: +160,128,7750,1,0,0:0:0:0: +192,128,7875,1,0,0:0:0:0: +224,128,8000,1,0,0:0:0:0: +256,128,8125,1,0,0:0:0:0: +288,128,8250,1,0,0:0:0:0: +320,128,8375,1,0,0:0:0:0: +352,128,8500,1,0,0:0:0:0: + +// fruit hyperdashes, spaced 1/2 beat apart +32,160,9500,1,0,0:0:0:0: +480,160,9750,1,0,0:0:0:0: +32,160,10000,1,0,0:0:0:0: +480,160,10250,1,0,0:0:0:0: +32,160,10500,1,0,0:0:0:0: +480,160,10750,1,0,0:0:0:0: +32,160,11000,1,0,0:0:0:0: + +// fruit hyperdashes, spaced 1/4 beat apart +32,192,12000,1,0,0:0:0:0: +480,192,12125,1,0,0:0:0:0: +32,192,12250,1,0,0:0:0:0: +480,192,12375,1,0,0:0:0:0: +32,192,12500,1,0,0:0:0:0: +480,192,12625,1,0,0:0:0:0: +32,192,12750,1,0,0:0:0:0: +480,192,12875,1,0,0:0:0:0: +32,192,13000,1,0,0:0:0:0: + +// stream + hyperdash + stream, spaced 1/4 beat apart +32,192,14000,1,0,0:0:0:0: +64,192,14125,1,0,0:0:0:0: +96,192,14250,1,0,0:0:0:0: +128,192,14375,1,0,0:0:0:0: +480,192,14500,1,0,0:0:0:0: +448,192,14625,1,0,0:0:0:0: +416,192,14750,1,0,0:0:0:0: +384,192,14875,1,0,0:0:0:0: +32,192,15000,1,0,0:0:0:0: + +// basic sliders +32,192,16000,2,0,L|192:192,1,160 +224,192,17000,2,0,L|384:192,1,160 +416,192,17875,2,0,L|480:192,1,40 + +// slider hyperdashes, spaced 1/4 beat apart +32,192,19000,2,0,L|128:192,1,80 +480,192,19375,2,0,L|384:192,1,80 +352,192,19750,2,0,L|256:192,1,80 +0,192,20125,2,0,L|128:192,1,120 + +// stream + slider hyperdashes, spaced 1/4 beat apart +32,192,21500,1,0,0:0:0:0: +64,192,21625,1,0,0:0:0:0: +96,192,21750,1,0,0:0:0:0: +512,192,21875,2,0,L|320:192,1,160 +320,192,22500,1,0,0:0:0:0: +288,192,22625,1,0,0:0:0:0: +256,192,22750,1,0,0:0:0:0: +0,192,22875,2,0,L|64:192,1,40 + +// streams, spaced 1/4 beat apart +64,192,24000,1,0,0:0:0:0: +160,192,24125,1,0,0:0:0:0: +64,192,24250,1,0,0:0:0:0: +160,192,24375,1,0,0:0:0:0: +64,192,24500,1,0,0:0:0:0: +160,192,24625,1,0,0:0:0:0: +64,192,24750,1,0,0:0:0:0: +160,192,24875,1,0,0:0:0:0: +64,192,25000,1,0,0:0:0:0: +160,192,25125,1,0,0:0:0:0: +64,192,25250,1,0,0:0:0:0: +160,192,25375,1,0,0:0:0:0: +64,192,25500,1,0,0:0:0:0: + +// stream + spinner combo, spaced 1/4 beat apart +256,192,26500,12,0,27000,0:0:0:0: +128,192,27250,5,0,0:0:0:0: +128,192,27375,1,0,0:0:0:0: +160,192,27500,1,0,0:0:0:0: +192,192,27625,1,0,0:0:0:0: +256,192,27750,12,0,28500,0:0:0:0: +192,192,28625,5,0,0:0:0:0: +224,192,28750,1,0,0:0:0:0: +256,192,28875,1,0,0:0:0:0: +256,192,29000,1,0,0:0:0:0: +256,192,29125,12,0,29500,0:0:0:0: + +// long slow slider +0,192,30500,6,0,B|480:192|480:192|0:192,2,960 + +// long fast slider +0,192,37500,6,0,B|480:192|480:192|0:192,2,960 + +// long hyperdash slider +0,192,41500,2,0,P|544:192|544:192,5,480 diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json new file mode 100644 index 0000000000..58c52b6867 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"StartTime":19184.0,"Objects":[{"StartTime":19184.0,"Position":320.0},{"StartTime":19263.0,"Position":311.730255},{"StartTime":19343.0,"Position":324.6205},{"StartTime":19423.0,"Position":343.0907},{"StartTime":19503.0,"Position":372.2917},{"StartTime":19582.0,"Position":385.194733},{"StartTime":19662.0,"Position":379.0426},{"StartTime":19742.0,"Position":385.1066},{"StartTime":19822.0,"Position":391.624664},{"StartTime":19919.0,"Position":386.27832},{"StartTime":20016.0,"Position":380.117035},{"StartTime":20113.0,"Position":381.664154},{"StartTime":20247.0,"Position":370.872864}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu new file mode 100644 index 0000000000..d48b2d7769 --- /dev/null +++ b/osu.Game.Rulesets.Catch/Resources/Testing/Beatmaps/slider.osu @@ -0,0 +1,18 @@ +osu file format v14 + +[General] +Mode: 2 + +[Difficulty] +HPDrainRate:3 +CircleSize:2 +OverallDifficulty:4 +ApproachRate:4 +SliderMultiplier:0.9 +SliderTickRate:1 + +[TimingPoints] +35.4473684210527,638.298947368422,4,2,1,60,1,0 + +[HitObjects] +320,176,19184,2,8,P|384:168|368:232,1,150 diff --git a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs index 403cedde8c..99b22b2d56 100644 --- a/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs +++ b/osu.Game.Rulesets.Catch/Scoring/CatchScoreProcessor.cs @@ -1,11 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -13,8 +12,8 @@ namespace osu.Game.Rulesets.Catch.Scoring { public class CatchScoreProcessor : ScoreProcessor { - public CatchScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public CatchScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } @@ -27,21 +26,18 @@ namespace osu.Game.Rulesets.Catch.Scoring hpDrainRate = beatmap.BeatmapInfo.BaseDifficulty.DrainRate; } - private const double harshness = 0.01; - - protected override void ApplyResult(JudgementResult result) + protected override double HealthAdjustmentFactorFor(JudgementResult result) { - base.ApplyResult(result); - - if (result.Type == HitResult.Miss) + switch (result.Type) { - if (!result.Judgement.IsBonus) - Health.Value -= hpDrainRate * (harshness * 2); - return; - } + case HitResult.Miss: + return hpDrainRate; - if (result.Judgement is CatchJudgement catchJudgement) - Health.Value += Math.Max(catchJudgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness; + default: + return 10.2 - hpDrainRate; // Award less HP as drain rate is increased + } } + + public override HitWindows CreateHitWindows() => new CatchHitWindows(); } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 0697a72325..b6d8cf9cbe 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; namespace osu.Game.Rulesets.Catch.UI { @@ -20,33 +19,24 @@ namespace osu.Game.Rulesets.Catch.UI internal readonly CatcherArea CatcherArea; - public CatchPlayfield(BeatmapDifficulty difficulty, Func> getVisualRepresentation) + public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { Container explodingFruitContainer; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate - - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + explodingFruitContainer = new Container { - explodingFruitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - CatcherArea = new CatcherArea(difficulty) - { - GetVisualRepresentation = getVisualRepresentation, - ExplodingFruitTarget = explodingFruitContainer, - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - }, - HitObjectContainer - } + RelativeSizeAxes = Axes.Both, + }, + CatcherArea = new CatcherArea(difficulty) + { + CreateDrawableRepresentation = createDrawableRepresentation, + ExplodingFruitTarget = explodingFruitContainer, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + }, + HitObjectContainer }; } diff --git a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs similarity index 69% rename from osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index fa1a7ee868..b8d3dc9017 100644 --- a/osu.Game.Rulesets.Catch/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -1,19 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Catch.UI { - public class PlayfieldAdjustmentContainer : Container + public class CatchPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public CatchPlayfieldAdjustmentContainer() { + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Size = new Vector2(0.86f); // matches stable's vertical offset for catcher plate + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 3637fe0c36..0b06e958e6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -1,13 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.MathUtils; using osu.Game.Beatmaps; @@ -25,15 +23,15 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatcherArea : Container { - public const float CATCHER_SIZE = 100; + public const float CATCHER_SIZE = 106.75f; protected internal readonly Catcher MovableCatcher; - public Func> GetVisualRepresentation; + public Func> CreateDrawableRepresentation; public Container ExplodingFruitTarget { - set { MovableCatcher.ExplodingFruitTarget = value; } + set => MovableCatcher.ExplodingFruitTarget = value; } public CatcherArea(BeatmapDifficulty difficulty = null) @@ -60,12 +58,12 @@ namespace osu.Game.Rulesets.Catch.UI if (lastPlateableFruit.IsLoaded) action(); else - lastPlateableFruit.OnLoadComplete = _ => action(); + lastPlateableFruit.OnLoadComplete += _ => action(); } if (result.IsHit && fruit.CanBePlated) { - var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); + var caughtFruit = (DrawableCatchHitObject)CreateDrawableRepresentation?.Invoke(fruit.HitObject); if (caughtFruit == null) return; @@ -141,7 +139,7 @@ namespace osu.Game.Rulesets.Catch.UI [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Children = new[] { caughtFruit = new Container { @@ -158,7 +156,7 @@ namespace osu.Game.Rulesets.Catch.UI protected bool Dashing { - get { return dashing; } + get => dashing; set { if (value == dashing) return; @@ -176,7 +174,7 @@ namespace osu.Game.Rulesets.Catch.UI /// protected bool Trail { - get { return trail; } + get => trail; set { if (value == trail) return; @@ -212,7 +210,7 @@ namespace osu.Game.Rulesets.Catch.UI Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); } - private Sprite createCatcherSprite() => new CatcherSprite(); + private Drawable createCatcherSprite() => new CatcherSprite(); /// /// Add a caught fruit to the catcher's stack. @@ -292,6 +290,7 @@ namespace osu.Game.Rulesets.Catch.UI const float hyper_dash_transition_length = 180; bool previouslyHyperDashing = HyperDashing; + if (modifier <= 1 || X == targetPosition) { hyperDashModifier = 1; @@ -325,9 +324,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection--; return true; + case CatchAction.MoveRight: currentDirection++; return true; + case CatchAction.Dash: Dashing = true; return true; @@ -343,9 +344,11 @@ namespace osu.Game.Rulesets.Catch.UI case CatchAction.MoveLeft: currentDirection++; return true; + case CatchAction.MoveRight: currentDirection--; return true; + case CatchAction.Dash: Dashing = false; return true; @@ -374,8 +377,8 @@ namespace osu.Game.Rulesets.Catch.UI X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * speed, 0, 1); // Correct overshooting. - if (hyperDashDirection > 0 && hyperDashTargetPosition < X || - hyperDashDirection < 0 && hyperDashTargetPosition > X) + if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) || + (hyperDashDirection < 0 && hyperDashTargetPosition > X)) { X = hyperDashTargetPosition; SetHyperDashState(); @@ -439,23 +442,6 @@ namespace osu.Game.Rulesets.Catch.UI fruit.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.Catch/UI/CatcherSprite.cs b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs new file mode 100644 index 0000000000..c0c1952064 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatcherSprite.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Catch.UI +{ + public class CatcherSprite : CompositeDrawable + { + public CatcherSprite() + { + Size = new Vector2(CatcherArea.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) * CatcherArea.CATCHER_SIZE; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new SkinnableSprite(@"Play/Catch/fruit-catcher-idle") + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; + } + } +} diff --git a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs similarity index 61% rename from osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs rename to osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ef1bb7767f..f48b84e344 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRulesetContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -1,30 +1,32 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Input; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Catch.UI { - public class CatchRulesetContainer : ScrollingRulesetContainer + public class DrawableCatchRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant; protected override bool UserScrollSpeedAdjustment => false; - public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableCatchRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); @@ -34,24 +36,31 @@ namespace osu.Game.Rulesets.Catch.UI protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, CreateDrawableRepresentation); - public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); - public override DrawableHitObject GetVisualRepresentation(CatchHitObject h) + protected override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); + + public override DrawableHitObject CreateDrawableRepresentation(CatchHitObject h) { switch (h) { case Banana banana: return new DrawableBanana(banana); + case Fruit fruit: return new DrawableFruit(fruit); + case JuiceStream stream: - return new DrawableJuiceStream(stream, GetVisualRepresentation); + return new DrawableJuiceStream(stream, CreateDrawableRepresentation); + case BananaShower shower: - return new DrawableBananaShower(shower, GetVisualRepresentation); + return new DrawableBananaShower(shower, CreateDrawableRepresentation); + case TinyDroplet tiny: return new DrawableTinyDroplet(tiny); + case Droplet droplet: return new DrawableDroplet(droplet); } diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..9cd1e47023 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Mania.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs new file mode 100644 index 0000000000..d47ac4643f --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Mania.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist new file mode 100644 index 0000000000..8780204d5b --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Mania.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Mania-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj new file mode 100644 index 0000000000..24abccb19d --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -0,0 +1,45 @@ + + + + + Debug + iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C} + Exe + osu.Game.Rulesets.Mania.Tests + osu.Game.Rulesets.Mania.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {48F4582B-7687-4621-9CBE-5C24197CB536} + osu.Game.Rulesets.Mania + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json index c781b0e64e..492f894484 100644 --- a/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Mania.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Mania.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 58f2ab7747..6b95975059 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -40,29 +40,29 @@ namespace osu.Game.Rulesets.Mania.Tests protected override Ruleset CreateRuleset() => new ManiaRuleset(); } - public class ManiaConvertMapping : ConvertMapping, IEquatable - { - public uint RandomW; - public uint RandomX; - public uint RandomY; - public uint RandomZ; + public class ManiaConvertMapping : ConvertMapping, IEquatable + { + public uint RandomW; + public uint RandomX; + public uint RandomY; + public uint RandomZ; - public ManiaConvertMapping() - { - } + public ManiaConvertMapping() + { + } - public ManiaConvertMapping(IBeatmapConverter converter) - { - var maniaConverter = (ManiaBeatmapConverter)converter; - RandomW = maniaConverter.Random.W; - RandomX = maniaConverter.Random.X; - RandomY = maniaConverter.Random.Y; - RandomZ = maniaConverter.Random.Z; - } + public ManiaConvertMapping(IBeatmapConverter converter) + { + var maniaConverter = (ManiaBeatmapConverter)converter; + RandomW = maniaConverter.Random.W; + RandomX = maniaConverter.Random.X; + RandomY = maniaConverter.Random.Y; + RandomZ = maniaConverter.Random.Z; + } - public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ; - public override bool Equals(ConvertMapping other) => base.Equals(other) && Equals(other as ManiaConvertMapping); - } + public bool Equals(ManiaConvertMapping other) => other != null && RandomW == other.RandomW && RandomX == other.RandomX && RandomY == other.RandomY && RandomZ == other.RandomZ; + public override bool Equals(ConvertMapping other) => base.Equals(other) && Equals(other as ManiaConvertMapping); + } public struct ConvertValue : IEquatable { diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..2c36e81190 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaDifficultyCalculatorTest.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mania.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class ManiaDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + + [TestCase(2.3683365342338796d, "diffcalc-test")] + public void Test(double expected, string name) + => base.Test(expected, name); + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(new ManiaRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs similarity index 83% rename from osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs rename to osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs index 75c8fc7e79..909d0d45c6 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaInputTestCase.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaInputTestScene.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,12 +8,12 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { - public abstract class ManiaInputTestCase : OsuTestCase + public abstract class ManiaInputTestScene : OsuTestScene { private readonly Container content; protected override Container Content => content ?? base.Content; - protected ManiaInputTestCase(int keys) + protected ManiaInputTestScene(int keys) { base.Content.Add(content = new LocalInputManager(keys)); } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs new file mode 100644 index 0000000000..4b3786c30a --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [Cached(Type = typeof(IManiaHitObjectComposer))] + public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer + { + private readonly Column column; + + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + + protected ManiaPlacementBlueprintTestScene() + { + Add(column = new Column(0) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.OrangeRed, + Clock = new FramedClock(new StopwatchClock()), // No scroll + }); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo); + + return dependencies; + } + + protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; + + protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject); + + public Column ColumnAt(Vector2 screenSpacePosition) => column; + + public int TotalColumns => 1; + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs new file mode 100644 index 0000000000..b598893e8c --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [Cached(Type = typeof(IManiaHitObjectComposer))] + public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer + { + [Cached(Type = typeof(IAdjustableClock))] + private readonly IAdjustableClock clock = new StopwatchClock(); + + private readonly Column column; + + protected ManiaSelectionBlueprintTestScene() + { + Add(column = new Column(0) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AccentColour = Color4.OrangeRed, + Clock = new FramedClock(new StopwatchClock()), // No scroll + }); + } + + public Column ColumnAt(Vector2 screenSpacePosition) => column; + + public int TotalColumns => 1; + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs similarity index 97% rename from osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs index bab3a4db32..20ac5eaa39 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseAutoGeneration.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneAutoGeneration.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using NUnit.Framework; @@ -12,7 +12,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseAutoGeneration : OsuTestCase + public class TestSceneAutoGeneration : OsuTestScene { [Test] public void TestSingleNote() diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs similarity index 85% rename from osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs index 9bdb5ec1b2..d94a986dae 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseColumn.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneColumn.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI.Components; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -21,7 +22,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseColumn : ManiaInputTestCase + public class TestSceneColumn : ManiaInputTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -31,9 +32,12 @@ namespace osu.Game.Rulesets.Mania.Tests typeof(ColumnHitObjectArea) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List columns = new List(); - public TestCaseColumn() + public TestSceneColumn() : base(2) { } @@ -49,8 +53,8 @@ namespace osu.Game.Rulesets.Mania.Tests Spacing = new Vector2(20, 0), Children = new[] { - createColumn(ScrollingDirection.Up, ManiaAction.Key1), - createColumn(ScrollingDirection.Down, ManiaAction.Key2) + createColumn(ScrollingDirection.Up, ManiaAction.Key1, 0), + createColumn(ScrollingDirection.Down, ManiaAction.Key2, 1) } }; } @@ -85,9 +89,9 @@ namespace osu.Game.Rulesets.Mania.Tests } } - private Drawable createColumn(ScrollingDirection direction, ManiaAction action) + private Drawable createColumn(ScrollingDirection direction, ManiaAction action, int index) { - var column = new Column + var column = new Column(index) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs similarity index 61% rename from osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs index 6c0f931cda..7ed886be49 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseEditor.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; @@ -11,11 +11,11 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseEditor : EditorTestCase + public class TestSceneEditor : EditorTestScene { private readonly Bindable direction = new Bindable(); - public TestCaseEditor() + public TestSceneEditor() : base(new ManiaRuleset()) { AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up); @@ -25,8 +25,8 @@ namespace osu.Game.Rulesets.Mania.Tests [BackgroundDependencyLoader] private void load(RulesetConfigCache configCache) { - var config = (ManiaConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); - config.BindWith(ManiaSetting.ScrollDirection, direction); + var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance()); + config.BindWith(ManiaRulesetSetting.ScrollDirection, direction); } } } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs new file mode 100644 index 0000000000..b4332264b9 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNotePlacementBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneHoldNotePlacementBlueprint : ManiaPlacementBlueprintTestScene + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHoldNote((HoldNote)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new HoldNotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs similarity index 86% rename from osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs index 993f7520e8..04c5724f93 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseHoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,14 +15,14 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseHoldNoteSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneHoldNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { private readonly DrawableHoldNote drawableObject; protected override Container Content => content ?? base.Content; private readonly Container content; - public TestCaseHoldNoteSelectionBlueprint() + public TestSceneHoldNoteSelectionBlueprint() { var holdNote = new HoldNote { Column = 0, Duration = 1000 }; holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs new file mode 100644 index 0000000000..d7b539a2a0 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotePlacementBlueprint.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene + { + protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); + protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs similarity index 80% rename from osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs index 67370be66c..6bb344f977 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNoteSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,14 +15,14 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Tests { - public class TestCaseNoteSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene { private readonly DrawableNote drawableObject; protected override Container Content => content ?? base.Content; private readonly Container content; - public TestCaseNoteSelectionBlueprint() + public TestSceneNoteSelectionBlueprint() { var note = new Note { Column = 0 }; note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs similarity index 94% rename from osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index 6344aee6bd..b2613a59d5 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -1,19 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; @@ -26,7 +27,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseNotes : OsuTestCase + public class TestSceneNotes : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -137,11 +138,11 @@ namespace osu.Game.Rulesets.Mania.Tests content = new Container { RelativeSizeAxes = Axes.Both } } }, - new SpriteText + new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - TextSize = 14, + Font = OsuFont.GetFont(size: 14), Text = description } } @@ -167,11 +168,13 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var nested in obj.NestedHitObjects) { double finalPosition = (nested.HitObject.StartTime - obj.HitObject.StartTime) / endTime.Duration; + switch (direction) { case ScrollingDirection.Up: nested.Y = (float)(finalPosition * content.DrawHeight); break; + case ScrollingDirection.Down: nested.Y = (float)(-finalPosition * content.DrawHeight); break; diff --git a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs similarity index 91% rename from osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs rename to osu.Game.Rulesets.Mania.Tests/TestSceneStage.cs index 7acc37cb67..395e6daf0a 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestCaseStage.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneStage.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -13,6 +14,7 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; using osuTK; @@ -20,15 +22,18 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Tests { [TestFixture] - public class TestCaseStage : ManiaInputTestCase + public class TestSceneStage : ManiaInputTestScene { private const int columns = 4; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly List stages = new List(); private FillFlowContainer fill; - public TestCaseStage() + public TestSceneStage() : base(columns) { } 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 98ad086c66..dbade6ff8d 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,14 +2,14 @@ - - - + + + WinExe - netcoreapp2.1 + netcoreapp2.2 diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs index ad5f8e447d..dc24a344e9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmap.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.UI; @@ -42,13 +42,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { Name = @"Note Count", Content = notes.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Hold Note Count", Content = holdnotes.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, }; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 2770a6ff5b..e10602312e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mania.Objects; using System; @@ -27,6 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override IEnumerable ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; public int TargetColumns; + public bool Dual; public readonly bool IsForCurrentRuleset; // Internal for testing purposes @@ -45,7 +46,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); if (IsForCurrentRuleset) + { TargetColumns = (int)Math.Max(1, roundedCircleSize); + + if (TargetColumns >= 10) + { + TargetColumns = TargetColumns / 2; + Dual = true; + } + } else { float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; @@ -70,14 +79,22 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return base.ConvertBeatmap(original); } - protected override Beatmap CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); + protected override Beatmap CreateBeatmap() + { + beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); + + if (Dual) + beatmap.Stages.Add(new StageDefinition { Columns = TargetColumns }); + + return beatmap; + } protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap) { - var maniaOriginal = original as ManiaHitObject; - if (maniaOriginal != null) + if (original is ManiaHitObject maniaOriginal) { yield return maniaOriginal; + yield break; } @@ -92,6 +109,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps private readonly List prevNoteTimes = new List(max_notes_for_density); private double density = int.MaxValue; + private void computeDensity(double newNoteTime) { if (prevNoteTimes.Count == max_notes_for_density) @@ -104,6 +122,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps private double lastTime; private Vector2 lastPosition; private PatternType lastStair = PatternType.Stair; + private void recordNote(double time, Vector2 position) { lastTime = time; @@ -180,7 +199,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps foreach (var obj in newPattern.HitObjects) yield return obj; - } } @@ -237,7 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps /// /// The time to retrieve the sample info list from. /// - private List sampleInfoListAt(double time) + private List sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 635004d2f6..ea418eedb4 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (originalPattern.HitObjects.Count() == 1) { yield return originalPattern; + yield break; } @@ -135,6 +136,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); + return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); } @@ -142,6 +144,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); + return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); } @@ -149,11 +152,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); + return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); } if (convertType.HasFlag(PatternType.LowProbability)) return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); + return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); } @@ -174,6 +179,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; int nextColumn = GetRandomColumn(); + for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) { // Find available column @@ -212,6 +218,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy nextColumn = FindAvailableColumn(nextColumn, PreviousPattern); int lastColumn = nextColumn; + for (int i = 0; i < noteCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -294,6 +301,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i <= spanCount; i++) { addToPattern(pattern, nextColumn, startTime, startTime); @@ -336,16 +344,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p3 = 0; p4 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; break; + case 4: p2 = Math.Min(p2, 0.3); p3 = Math.Min(p3, 0.04); p4 = 0; break; + case 5: p2 = Math.Min(p2, 0.34); p3 = Math.Min(p3, 0.1); @@ -353,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy break; } - bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP || sample.Name == SampleInfo.HIT_FINISH; + bool isDoubleSample(HitSampleInfo sample) => sample.Name == HitSampleInfo.HIT_CLAP || sample.Name == HitSampleInfo.HIT_FINISH; bool canGenerateTwoNotes = !convertType.HasFlag(PatternType.LowProbability); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); @@ -432,9 +443,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy noteCount = 0; noteCount = Math.Min(TotalColumns - 1, noteCount); - bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); + bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == HitSampleInfo.HIT_WHISTLE || s.Name == HitSampleInfo.HIT_FINISH || s.Name == HitSampleInfo.HIT_CLAP); var rowPattern = new Pattern(); + for (int i = 0; i <= spanCount; i++) { if (!(ignoreHead && startTime == HitObject.StartTime)) @@ -460,7 +472,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private List sampleInfoListAt(double time) + private List sampleInfoListAt(double time) { var curveData = HitObject as IHasCurve; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 775a4145e6..b3be08e1f7 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Rulesets.Mania.MathUtils; @@ -35,12 +35,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy switch (TotalColumns) { - case 8 when HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000: + case 8 when HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000: addToPattern(pattern, 0, generateHold); break; + case 8: addToPattern(pattern, FindAvailableColumn(GetRandomColumn(), PreviousPattern), generateHold); break; + default: if (TotalColumns > 0) addToPattern(pattern, GetRandomColumn(), generateHold); @@ -70,9 +72,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy }; if (hold.Head.Samples == null) - hold.Head.Samples = new List(); + hold.Head.Samples = new List(); - hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); + hold.Head.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL }); hold.Tail.Samples = HitObject.Samples; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 8463e209e4..decd159ee9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (!convertType.HasFlag(PatternType.KeepSingle)) { - if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8) + if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH) && TotalColumns != 8) convertType |= PatternType.Mirror; - else if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)) + else if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)) convertType |= PatternType.Gathered; } } @@ -116,10 +116,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 - // If we convert to 7K + 1, let's not overload the special key - && (TotalColumns != 8 || lastColumn != 0) - // Make sure the last column was not the centre column - && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) + // If we convert to 7K + 1, let's not overload the special key + && (TotalColumns != 8 || lastColumn != 0) + // Make sure the last column was not the centre column + && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) { // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object) int column = RandomStart + TotalColumns - lastColumn - 1; @@ -172,6 +172,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return pattern = generateRandomPatternWithMirrored(0.12, 0.38, 0.12); if (ConversionDifficulty > 4) return pattern = generateRandomPatternWithMirrored(0.12, 0.17, 0); + return pattern = generateRandomPatternWithMirrored(0.12, 0, 0); } @@ -179,6 +180,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { if (convertType.HasFlag(PatternType.LowProbability)) return pattern = generateRandomPattern(0.78, 0.42, 0, 0); + return pattern = generateRandomPattern(1, 0.62, 0, 0); } @@ -186,6 +188,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { if (convertType.HasFlag(PatternType.LowProbability)) return pattern = generateRandomPattern(0.35, 0.08, 0, 0); + return pattern = generateRandomPattern(0.52, 0.15, 0, 0); } @@ -193,6 +196,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { if (convertType.HasFlag(PatternType.LowProbability)) return pattern = generateRandomPattern(0.18, 0, 0, 0); + return pattern = generateRandomPattern(0.45, 0, 0, 0); } @@ -229,6 +233,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); + for (int i = 0; i < noteCount; i++) { nextColumn = allowStacking @@ -250,6 +255,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } else last = GetRandomColumn(); + return last; } } @@ -257,7 +263,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// Whether this hit object can generate a note in the special column. /// - private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH); + private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); /// /// Generates a random pattern. @@ -298,6 +304,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; int nextColumn = GetRandomColumn(upperBound: columnLimit); + for (int i = 0; i < noteCount; i++) { nextColumn = FindAvailableColumn(nextColumn, upperBound: columnLimit, patterns: pattern); @@ -335,18 +342,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p4 = 0; p5 = 0; break; + case 3: p2 = Math.Min(p2, 0.1); p3 = 0; p4 = 0; p5 = 0; break; + case 4: p2 = Math.Min(p2, 0.23); p3 = Math.Min(p3, 0.04); p4 = 0; p5 = 0; break; + case 5: p3 = Math.Min(p3, 0.15); p4 = Math.Min(p4, 0.03); @@ -354,7 +364,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy break; } - if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)) + if (HitObject.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)) p2 = 1; return GetRandomNoteCount(p2, p3, p4, p5); @@ -379,20 +389,24 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy p2 = 0; p3 = 0; break; + case 3: centreProbability = Math.Min(centreProbability, 0.03); p2 = 0; p3 = 0; break; + case 4: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.2); p3 = 0; break; + case 5: centreProbability = Math.Min(centreProbability, 0.03); p3 = 0; break; + case 6: centreProbability = 0; p2 = Math.Min(p2 * 2, 0.5); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index f412e127c5..fba52dfc32 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy return 4; if (val >= 1 - p3) return 3; + return val >= 1 - p2 ? 2 : 1; } @@ -157,6 +158,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy // Ensure that we have at least one free column, so that an endless loop is avoided bool hasValidColumns = false; + for (int i = lowerBound.Value; i < upperBound.Value; i++) { hasValidColumns = isValid(i); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs index 2eba2d5843..a3cd455886 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; @@ -12,51 +12,63 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy internal enum PatternType { None = 0, + /// /// Keep the same as last row. /// ForceStack = 1 << 0, + /// /// Keep different from last row. /// ForceNotStack = 1 << 1, + /// /// Keep as single note at its original position. /// KeepSingle = 1 << 2, + /// /// Use a lower random value. /// LowProbability = 1 << 3, + /// /// Reserved. /// Alternate = 1 << 4, + /// /// Ignore the repeat count. /// ForceSigSlider = 1 << 5, + /// /// Convert slider to circle. /// ForceNotSlider = 1 << 6, + /// /// Notes gathered together. /// Gathered = 1 << 7, Mirror = 1 << 8, + /// /// Change 0 -> 6. /// Reverse = 1 << 9, + /// /// 1 -> 5 -> 1 -> 5 like reverse. /// Cycle = 1 << 10, + /// /// Next note will be at column + 1. /// Stair = 1 << 11, + /// /// Next note will be at column - 1. /// diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs index 32d2d096b2..f095a0ffce 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index a42d57cdd1..71b3f6ecf2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs index afec607a7c..dff7cb72ce 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/StageDefinition.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mania.UI; diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs deleted file mode 100644 index 1c9e1e4c73..0000000000 --- a/osu.Game.Rulesets.Mania/Configuration/ManiaConfigManager.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Configuration.Tracking; -using osu.Game.Configuration; -using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Mania.UI; - -namespace osu.Game.Rulesets.Mania.Configuration -{ - public class ManiaConfigManager : RulesetConfigManager - { - public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) - : base(settings, ruleset, variant) - { - } - - protected override void InitialiseDefaults() - { - base.InitialiseDefaults(); - - Set(ManiaSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0); - Set(ManiaSetting.ScrollDirection, ManiaScrollingDirection.Down); - } - - public override TrackedSettings CreateTrackedSettings() => new TrackedSettings - { - new TrackedSetting(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) - }; - } - - public enum ManiaSetting - { - ScrollTime, - ScrollDirection - } -} diff --git a/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs new file mode 100644 index 0000000000..b591f9da22 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Configuration/ManiaRulesetConfigManager.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Configuration.Tracking; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Mania.UI; + +namespace osu.Game.Rulesets.Mania.Configuration +{ + public class ManiaRulesetConfigManager : RulesetConfigManager + { + public ManiaRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + : base(settings, ruleset, variant) + { + } + + protected override void InitialiseDefaults() + { + base.InitialiseDefaults(); + + Set(ManiaRulesetSetting.ScrollTime, 2250.0, 50.0, 10000.0, 50.0); + Set(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down); + } + + public override TrackedSettings CreateTrackedSettings() => new TrackedSettings + { + new TrackedSetting(ManiaRulesetSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) + }; + } + + public enum ManiaRulesetSetting + { + ScrollTime, + ScrollDirection + } +} diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index c7f6890b93..3ff665d2c8 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -1,18 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Difficulty { public class ManiaDifficultyAttributes : DifficultyAttributes { public double GreatHitWindow; - - public ManiaDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) - { - } } } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 7b4d4b12ed..4a9c22d339 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -1,34 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Difficulty.Skills; using osu.Game.Rulesets.Mania.Mods; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Difficulty { - internal class ManiaDifficultyCalculator : DifficultyCalculator + public class ManiaDifficultyCalculator : DifficultyCalculator { private const double star_scaling_factor = 0.018; - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 400; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.9; - private readonly bool isForCurrentRuleset; public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) @@ -37,109 +27,71 @@ namespace osu.Game.Rulesets.Mania.Difficulty isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - if (!beatmap.HitObjects.Any()) - return new ManiaDifficultyAttributes(mods, 0); + if (beatmap.HitObjects.Count == 0) + return new ManiaDifficultyAttributes { Mods = mods, Skills = skills }; - var difficultyHitObjects = new List(); - - int columnCount = ((ManiaBeatmap)beatmap).TotalColumns; - - // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. - // Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change - difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime)); - - if (!calculateStrainValues(difficultyHitObjects, timeRate)) - return new DifficultyAttributes(mods, 0); - - - double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; - - return new ManiaDifficultyAttributes(mods, starRating) + return new ManiaDifficultyAttributes { - // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate + StarRating = difficultyValue(skills) * star_scaling_factor, + Mods = mods, + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future + GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate, + Skills = skills }; } - private bool calculateStrainValues(List objects, double timeRate) + private double difficultyValue(Skill[] skills) { - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - using (var hitObjectsEnumerator = objects.GetEnumerator()) + // Preprocess the strains to find the maximum overall + individual (aggregate) strain from each section + var overall = skills.OfType().Single(); + var aggregatePeaks = new List(Enumerable.Repeat(0.0, overall.StrainPeaks.Count)); + + foreach (var individual in skills.OfType()) { - if (!hitObjectsEnumerator.MoveNext()) - return false; - - ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current; - - // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. - while (hitObjectsEnumerator.MoveNext()) + for (int i = 0; i < individual.StrainPeaks.Count; i++) { - var next = hitObjectsEnumerator.Current; - next?.CalculateStrains(current, timeRate); - current = next; + double aggregate = individual.StrainPeaks[i] + overall.StrainPeaks[i]; + + if (aggregate > aggregatePeaks[i]) + aggregatePeaks[i] = aggregate; } - - return true; - } - } - - private double calculateDifficulty(List objects, double timeRate) - { - double actualStrainStep = strain_step * timeRate; - - // Find the highest strain value within each strain step - List highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - ManiaHitObjectDifficulty previousHitObject = null; - foreach (var hitObject in objects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - double strain = hitObject.IndividualStrain + hitObject.OverallStrain; - maximumStrain = Math.Max(strain, maximumStrain); - - previousHitObject = hitObject; } - // Build the weighted sum over the highest strains for each interval + aggregatePeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. + double difficulty = 0; double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - foreach (double strain in highestStrains) + // Difficulty is the weighted sum of the highest strains from every section. + foreach (double strain in aggregatePeaks) { - difficulty += weight * strain; - weight *= decay_weight; + difficulty += strain * weight; + weight *= 0.9; } return difficulty; } + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) + { + for (int i = 1; i < beatmap.HitObjects.Count; i++) + yield return new ManiaDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); + } + + protected override Skill[] CreateSkills(IBeatmap beatmap) + { + int columnCount = ((ManiaBeatmap)beatmap).TotalColumns; + + var skills = new List { new Overall(columnCount) }; + + for (int i = 0; i < columnCount; i++) + skills.Add(new Individual(i, columnCount)); + + return skills.ToArray(); + } + protected override Mod[] DifficultyAdjustmentMods { get diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index 1f26bda1b2..b99bddee96 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,6 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania.Difficulty { @@ -27,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty private int countMeh; private int countMiss; - public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { } @@ -113,8 +114,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667) - * strainValue - * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); + * strainValue + * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); // Bonus for many hitcircles - it's harder to keep good accuracy up for longer // accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); diff --git a/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs new file mode 100644 index 0000000000..29ba934e9f --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Preprocessing/ManiaDifficultyHitObject.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Preprocessing +{ + public class ManiaDifficultyHitObject : DifficultyHitObject + { + public new ManiaHitObject BaseObject => (ManiaHitObject)base.BaseObject; + + public ManiaDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) + : base(hitObject, lastObject, clockRate) + { + } + } +} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs new file mode 100644 index 0000000000..059cd39641 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Individual.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Skills +{ + public class Individual : Skill + { + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.125; + + private readonly double[] holdEndTimes; + + private readonly int column; + + public Individual(int column, int columnCount) + { + this.column = column; + + holdEndTimes = new double[columnCount]; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + var maniaCurrent = (ManiaDifficultyHitObject)current; + var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + + try + { + if (maniaCurrent.BaseObject.Column != column) + return 0; + + // We give a slight bonus if something is held meanwhile + return holdEndTimes.Any(t => t > endTime) ? 2.5 : 2; + } + finally + { + holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs new file mode 100644 index 0000000000..ed25173d38 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Overall.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Mania.Difficulty.Preprocessing; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Difficulty.Skills +{ + public class Overall : Skill + { + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.3; + + private readonly double[] holdEndTimes; + + private readonly int columnCount; + + public Overall(int columnCount) + { + this.columnCount = columnCount; + + holdEndTimes = new double[columnCount]; + } + + protected override double StrainValueOf(DifficultyHitObject current) + { + var maniaCurrent = (ManiaDifficultyHitObject)current; + var endTime = (maniaCurrent.BaseObject as HoldNote)?.EndTime ?? maniaCurrent.BaseObject.StartTime; + + double holdFactor = 1.0; // Factor in case something else is held + double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly + + for (int i = 0; i < columnCount; i++) + { + // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... + if (current.BaseObject.StartTime < holdEndTimes[i] && endTime > holdEndTimes[i]) + holdAddition = 1.0; + + // ... this addition only is valid if there is _no_ other note with the same ending. + // Releasing multiple notes at the same time is just as easy as releasing one + if (endTime == holdEndTimes[i]) + holdAddition = 0; + + // We give a slight bonus if something is held meanwhile + if (holdEndTimes[i] > endTime) + holdFactor = 1.25; + } + + holdEndTimes[maniaCurrent.BaseObject.Column] = endTime; + + return (1 + holdAddition) * holdFactor; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs new file mode 100644 index 0000000000..b99a1157f3 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditBodyPiece.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components +{ + public class EditBodyPiece : BodyPiece + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Yellow; + + Background.Alpha = 0.5f; + Foreground.Alpha = 0; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs index 424ff1118c..6f85fd9167 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/Components/EditNotePiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs new file mode 100644 index 0000000000..26115311f7 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class HoldNotePlacementBlueprint : ManiaPlacementBlueprint + { + private readonly EditBodyPiece bodyPiece; + private readonly EditNotePiece headPiece; + private readonly EditNotePiece tailPiece; + + public HoldNotePlacementBlueprint() + : base(new HoldNote()) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + bodyPiece = new EditBodyPiece { Origin = Anchor.TopCentre }, + headPiece = new EditNotePiece { Origin = Anchor.Centre }, + tailPiece = new EditNotePiece { Origin = Anchor.Centre } + }; + } + + protected override void Update() + { + base.Update(); + + if (Column != null) + { + headPiece.Y = PositionAt(HitObject.StartTime); + tailPiece.Y = PositionAt(HitObject.EndTime); + } + + var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); + var bottomPosition = new Vector2(headPiece.DrawPosition.X, Math.Max(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); + + bodyPiece.Position = topPosition; + bodyPiece.Width = headPiece.Width; + bodyPiece.Height = (bottomPosition - topPosition).Y; + } + + private double originalStartTime; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + base.OnMouseMove(e); + + if (PlacementBegun) + { + var endTime = TimeAt(e.ScreenSpaceMousePosition); + + HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; + HitObject.Duration = Math.Abs(endTime - originalStartTime); + } + else + { + headPiece.Width = tailPiece.Width = SnappedWidth; + headPiece.X = tailPiece.X = SnappedMousePosition.X; + + originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); + } + + return true; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index afeb0a585e..d64c5dbc6a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Graphics; @@ -49,7 +49,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.Update(); Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight); - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); // This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do // When scrolling upwards our origin is already at the top of the head note (which is the intended location), diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs new file mode 100644 index 0000000000..d3779e2e18 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public abstract class ManiaPlacementBlueprint : PlacementBlueprint, + IRequireHighFrequencyMousePosition // the playfield could be moving behind us + where T : ManiaHitObject + { + protected new T HitObject => (T)base.HitObject; + + protected Column Column; + + /// + /// The current mouse position, snapped to the closest column. + /// + protected Vector2 SnappedMousePosition { get; private set; } + + /// + /// The width of the closest column to the current mouse position. + /// + protected float SnappedWidth { get; private set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + protected ManiaPlacementBlueprint(T hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (Column == null) + return base.OnMouseDown(e); + + HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition); + HitObject.Column = Column.Index; + + BeginPlacement(); + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + EndPlacement(); + return base.OnMouseUp(e); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (!PlacementBegun) + Column = ColumnAt(e.ScreenSpaceMousePosition); + + if (Column == null) return false; + + SnappedWidth = Column.DrawWidth; + + // Snap to the column + var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); + SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y); + return true; + } + + protected double TimeAt(Vector2 screenSpacePosition) + { + if (Column == null) + return 0; + + var hitObjectContainer = Column.HitObjectContainer; + + // If we're scrolling downwards, a position of 0 is actually further away from the hit target + // so we need to flip the vertical coordinate in the hitobject container's space + var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y; + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos; + + return scrollingInfo.Algorithm.TimeAt(hitObjectPos, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + hitObjectContainer.DrawHeight); + } + + protected float PositionAt(double time) + { + var pos = scrollingInfo.Algorithm.PositionAt(time, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + Column.HitObjectContainer.DrawHeight); + + return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y; + } + + protected Column ColumnAt(Vector2 screenSpacePosition) + => composer.ColumnAt(applyPositionOffset(screenSpacePosition, false)); + + private Vector2 applyPositionOffset(Vector2 position, bool reverse) + { + position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2; + return position; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 53f9dd8752..d3c12b1944 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -1,23 +1,80 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public class ManiaSelectionBlueprint : SelectionBlueprint { + public Vector2 ScreenSpaceDragPosition { get; private set; } + public Vector2 DragPosition { get; private set; } + + protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject; + + protected IClock EditorClock { get; private set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + public ManiaSelectionBlueprint(DrawableHitObject hitObject) : base(hitObject) { RelativeSizeAxes = Axes.None; } - public override void AdjustPosition(DragEvent dragEvent) + [BackgroundDependencyLoader] + private void load(IAdjustableClock clock) { + EditorClock = clock; + } + + protected override void Update() + { + base.Update(); + + Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero)); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; + DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + + return base.OnMouseDown(e); + } + + protected override bool OnDrag(DragEvent e) + { + var result = base.OnDrag(e); + + ScreenSpaceDragPosition = e.ScreenSpaceMousePosition; + DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition); + + return result; + } + + public override void Show() + { + HitObject.AlwaysAlive = true; + base.Show(); + } + + public override void Hide() + { + HitObject.AlwaysAlive = false; + base.Hide(); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs new file mode 100644 index 0000000000..32c6a6fd07 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Edit.Blueprints +{ + public class NotePlacementBlueprint : ManiaPlacementBlueprint + { + public NotePlacementBlueprint() + : base(new Note()) + { + Origin = Anchor.Centre; + + AutoSizeAxes = Axes.Y; + + InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; + } + + protected override void Update() + { + base.Update(); + + Width = SnappedWidth; + Position = SnappedMousePosition; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs index 7df7924c51..d345b14e84 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NoteSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; @@ -20,7 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.Update(); Size = HitObject.DrawSize; - Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs similarity index 55% rename from osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs rename to osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs index f605ad0a22..e5f379f608 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/DrawableManiaEditRuleset.cs @@ -1,21 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Graphics; using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaEditRulesetContainer : ManiaRulesetContainer + public class DrawableManiaEditRuleset : DrawableManiaRuleset { public new IScrollingInfo ScrollingInfo => base.ScrollingInfo; - public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { } diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs new file mode 100644 index 0000000000..295bf417c4 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Edit.Blueprints; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class HoldNoteCompositionTool : HitObjectCompositionTool + { + public HoldNoteCompositionTool() + : base("Hold") + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs new file mode 100644 index 0000000000..f64bab1fae --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mania.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public interface IManiaHitObjectComposer + { + Column ColumnAt(Vector2 screenSpacePosition); + + int TotalColumns { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs b/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs index e7bc526471..a42f793a77 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaEditPlayfield.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.UI; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3531b81e68..2729621ab3 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -11,33 +10,55 @@ using osu.Game.Rulesets.Objects.Drawables; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaHitObjectComposer : HitObjectComposer + [Cached(Type = typeof(IManiaHitObjectComposer))] + public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { + protected new DrawableManiaEditRuleset DrawableRuleset { get; private set; } + public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { } + /// + /// Retrieves the column that intersects a screen-space position. + /// + /// The screen-space position. + /// The column which intersects with . + public Column ColumnAt(Vector2 screenSpacePosition) => DrawableRuleset.GetColumnByPosition(screenSpacePosition); + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) + public int TotalColumns => ((ManiaPlayfield)DrawableRuleset.Playfield).TotalColumns; + + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) { - var rulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap); + DrawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); // This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it - dependencies.CacheAs(rulesetContainer.ScrollingInfo); + dependencies.CacheAs(DrawableRuleset.ScrollingInfo); - return rulesetContainer; + return DrawableRuleset; } - protected override IReadOnlyList CompositionTools => Array.Empty(); + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + { + new NoteCompositionTool(), + new HoldNoteCompositionTool() + }; + + public override SelectionHandler CreateSelectionHandler() => new ManiaSelectionHandler(); public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { @@ -45,6 +66,7 @@ namespace osu.Game.Rulesets.Mania.Edit { case DrawableNote note: return new NoteSelectionBlueprint(note); + case DrawableHoldNote holdNote: return new HoldNoteSelectionBlueprint(holdNote); } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs new file mode 100644 index 0000000000..6f49c7f0c4 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -0,0 +1,125 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit.Compose.Components; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class ManiaSelectionHandler : SelectionHandler + { + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + private IClock editorClock; + + [BackgroundDependencyLoader] + private void load(IAdjustableClock clock) + { + editorClock = clock; + } + + public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + { + adjustOrigins((ManiaSelectionBlueprint)blueprint); + performDragMovement(dragEvent); + performColumnMovement(dragEvent); + + base.HandleDrag(blueprint, dragEvent); + } + + /// + /// Ensures that the position of hitobjects remains centred to the mouse position. + /// E.g. The hitobject position will change if the editor scrolls while a hitobject is dragged. + /// + /// The that received the drag event. + private void adjustOrigins(ManiaSelectionBlueprint reference) + { + var referenceParent = (HitObjectContainer)reference.HitObject.Parent; + + float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y; + float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin; + + // Flip the vertical coordinate space when scrolling downwards + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + targetPosition = targetPosition - referenceParent.DrawHeight; + + float movementDelta = targetPosition - reference.HitObject.Position.Y; + + foreach (var b in SelectedBlueprints.OfType()) + b.HitObject.Y += movementDelta; + } + + private void performDragMovement(DragEvent dragEvent) + { + foreach (var b in SelectedBlueprints) + { + var hitObject = b.HitObject; + + var objectParent = (HitObjectContainer)hitObject.Parent; + + // Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame + // without the position having been updated by the parenting ScrollingHitObjectContainer + hitObject.Y += dragEvent.Delta.Y; + + float targetPosition; + + // If we're scrolling downwards, a position of 0 is actually further away from the hit target + // so we need to flip the vertical coordinate in the hitobject container's space + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + targetPosition = -hitObject.Position.Y; + else + targetPosition = hitObject.Position.Y; + + objectParent.Remove(hitObject); + + hitObject.HitObject.StartTime = scrollingInfo.Algorithm.TimeAt(targetPosition, + editorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + objectParent.DrawHeight); + + objectParent.Add(hitObject); + } + } + + private void performColumnMovement(DragEvent dragEvent) + { + var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition); + var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition); + if (lastColumn == null || currentColumn == null) + return; + + int columnDelta = currentColumn.Index - lastColumn.Index; + if (columnDelta == 0) + return; + + int minColumn = int.MaxValue; + int maxColumn = int.MinValue; + + foreach (var obj in SelectedHitObjects.OfType()) + { + if (obj.Column < minColumn) + minColumn = obj.Column; + if (obj.Column > maxColumn) + maxColumn = obj.Column; + } + + columnDelta = MathHelper.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + + foreach (var obj in SelectedHitObjects.OfType()) + obj.Column += columnDelta; + } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs index 81a2728ad4..30b0f09a94 100644 --- a/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Masks/ManiaSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs new file mode 100644 index 0000000000..50b5f9a8fe --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mania.Edit.Blueprints; +using osu.Game.Rulesets.Mania.Objects; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class NoteCompositionTool : HitObjectCompositionTool + { + public NoteCompositionTool() + : base(nameof(Note)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs index 9055e48a4c..e8b48768a1 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 6eb5a79200..b9c6e3a7f7 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; @@ -10,5 +10,17 @@ namespace osu.Game.Rulesets.Mania.Judgements public override bool AffectsCombo => false; protected override int NumericResultFor(HitResult result) => 20; + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return 0; + + default: + return 0.040; + } + } } } diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index 4e0649c708..0e4c811945 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -14,16 +14,47 @@ namespace osu.Game.Rulesets.Mania.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Ok: return 100; + case HitResult.Good: return 200; + case HitResult.Great: case HitResult.Perfect: return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.125; + + case HitResult.Meh: + return 0.005; + + case HitResult.Ok: + return 0.010; + + case HitResult.Good: + return 0.035; + + case HitResult.Great: + return 0.055; + + case HitResult.Perfect: + return 0.065; + + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Mania/ManiaInputManager.cs b/osu.Game.Rulesets.Mania/ManiaInputManager.cs index 61356d96dc..292990fd7e 100644 --- a/osu.Game.Rulesets.Mania/ManiaInputManager.cs +++ b/osu.Game.Rulesets.Mania/ManiaInputManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; using osu.Framework.Input.Bindings; @@ -19,6 +19,7 @@ namespace osu.Game.Rulesets.Mania { [Description("Special 1")] Special1 = 1, + [Description("Special 2")] Special2, @@ -26,38 +27,55 @@ namespace osu.Game.Rulesets.Mania // above at a later time, without breaking replays/configs. [Description("Key 1")] Key1 = 10, + [Description("Key 2")] Key2, + [Description("Key 3")] Key3, + [Description("Key 4")] Key4, + [Description("Key 5")] Key5, + [Description("Key 6")] Key6, + [Description("Key 7")] Key7, + [Description("Key 8")] Key8, + [Description("Key 9")] Key9, + [Description("Key 10")] Key10, + [Description("Key 11")] Key11, + [Description("Key 12")] Key12, + [Description("Key 13")] Key13, + [Description("Key 14")] Key14, + [Description("Key 15")] Key15, + [Description("Key 16")] Key16, + [Description("Key 17")] Key17, + [Description("Key 18")] Key18, } diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 19e89c8ec5..d83033f9c6 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Beatmaps; @@ -10,8 +10,10 @@ using osu.Game.Rulesets.UI; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; @@ -24,15 +26,15 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Difficulty; using osu.Game.Rulesets.Mania.Edit; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mania { public class ManiaRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableManiaRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); public override HitObjectComposer CreateHitObjectComposer() => new ManiaHitObjectComposer(this); @@ -115,6 +117,7 @@ namespace osu.Game.Rulesets.Mania new ManiaModNoFail(), new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -124,6 +127,7 @@ namespace osu.Game.Rulesets.Mania new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()), new ManiaModFlashlight(), }; + case ModType.Conversion: return new Mod[] { @@ -140,11 +144,19 @@ namespace osu.Game.Rulesets.Mania new ManiaModDualStages(), new ManiaModMirror(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new ManiaModAutoplay(), new ModCinema()), }; + + case ModType.Fun: + return new Mod[] + { + new MultiMod(new ModWindUp(), new ModWindDown()) + }; + default: return new Mod[] { }; } @@ -154,7 +166,7 @@ namespace osu.Game.Rulesets.Mania public override string ShortName => "mania"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetMania }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap); @@ -162,7 +174,7 @@ namespace osu.Game.Rulesets.Mania public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); - public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaRulesetConfigManager(settings, RulesetInfo); public override RulesetSettingsSubsection CreateSettings() => new ManiaSettingsSubsection(this); @@ -207,6 +219,7 @@ namespace osu.Game.Rulesets.Mania SpecialAction = ManiaAction.Special1, NormalActionStart = ManiaAction.Key1, }.GenerateKeyBindingsFor(variant, out _); + case PlayfieldType.Dual: int keys = getDualStageKeyCount(variant); @@ -264,6 +277,7 @@ namespace osu.Game.Rulesets.Mania { default: return $"{variant}K"; + case PlayfieldType.Dual: { var keys = getDualStageKeyCount(variant); @@ -330,12 +344,12 @@ namespace osu.Game.Rulesets.Mania for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); - for (int i = 0; i < columns / 2; i++) - bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); - if (columns % 2 == 1) bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); + for (int i = 0; i < columns / 2; i++) + bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); + nextNormalAction = currentNormalAction; return bindings; } @@ -349,6 +363,7 @@ namespace osu.Game.Rulesets.Mania /// Number of columns in this stage lies at (item - Single). /// Single = 0, + /// /// Columns are grouped into two stages. /// Overall number of columns lies at (item - Dual), further computation is required for diff --git a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs index 783142fadc..2ebfd0cfc1 100644 --- a/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs +++ b/osu.Game.Rulesets.Mania/ManiaSettingsSubsection.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.UI; @@ -21,14 +22,26 @@ namespace osu.Game.Rulesets.Mania [BackgroundDependencyLoader] private void load() { + var config = (ManiaRulesetConfigManager)Config; + Children = new Drawable[] { new SettingsEnumDropdown { LabelText = "Scrolling direction", - Bindable = ((ManiaConfigManager)Config).GetBindable(ManiaSetting.ScrollDirection) - } + Bindable = config.GetBindable(ManiaRulesetSetting.ScrollDirection) + }, + new SettingsSlider + { + LabelText = "Scroll speed", + Bindable = config.GetBindable(ManiaRulesetSetting.ScrollTime) + }, }; } + + private class TimeSlider : OsuSliderBar + { + public override string TooltipText => Current.Value.ToString("N0") + "ms"; + } } } diff --git a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs index 785cd5ab06..a9cd7f2476 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/FastRandom.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; @@ -37,11 +37,11 @@ namespace osu.Game.Rulesets.Mania.MathUtils /// The random value. public uint NextUInt() { - uint t = X ^ X << 11; + uint t = X ^ (X << 11); X = Y; Y = Z; Z = W; - return W = W ^ W >> 19 ^ t ^ t >> 8; + return W = W ^ (W >> 19) ^ t ^ (t >> 8); } /// diff --git a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs index e08c9aa2a8..410386c9d5 100644 --- a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 6d91c03c13..13fdd74113 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter { - public override string ShortenedName => Name; + public override string Acronym => Name; public abstract int KeyCount { get; } public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 89792956bb..c05e979e9a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -1,25 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "osu!topus!" }, - Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs index 506879bb96..bec0a6a1d3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs index bd93f0db38..a302f95966 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index 4790a77cc0..c78bf72979 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -1,18 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter, IApplicableToBeatmap + public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter { public override string Name => "Dual Stages"; - public override string ShortenedName => "DS"; + public override string Acronym => "DS"; public override string Description => @"Double the stages, double the fun!"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; @@ -29,24 +27,7 @@ namespace osu.Game.Rulesets.Mania.Mods if (isForCurrentRuleset) return; - mbc.TargetColumns *= 2; - } - - public void ApplyToBeatmap(Beatmap beatmap) - { - if (isForCurrentRuleset) - return; - - var maniaBeatmap = (ManiaBeatmap)beatmap; - - var newDefinitions = new List(); - foreach (var existing in maniaBeatmap.Stages) - { - newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); - newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); - } - - maniaBeatmap.Stages = newDefinitions; + mbc.Dual = true; } public PlayfieldType PlayfieldType => PlayfieldType.Dual; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index 8c85846ed1..ff77df0ae0 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 73942cbb53..39185e6a57 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; @@ -11,8 +12,8 @@ namespace osu.Game.Rulesets.Mania.Mods public class ManiaModFadeIn : Mod { public override string Name => "Fade In"; - public override string ShortenedName => "FI"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override string Acronym => "FI"; + public override IconUsage Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; public override string Description => @"Keys appear out of nowhere!"; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index cd84483eb9..6893e1e73b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects; @@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Mania.Mods } } - protected override void OnComboChange(int newCombo) + protected override void OnComboChange(ValueChangedEvent e) { } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs index 978554362d..014954dd60 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index 7b766cab85..d9de06a811 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 9bc2502a8f..66b90984b4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Rulesets.Mania.Objects; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index c2a4ed444f..948979505c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 1; public override string Name => "One Key"; - public override string ShortenedName => "1K"; + public override string Acronym => "1K"; public override string Description => @"Play with one key."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 3d78ad449b..de91902ca8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 2; public override string Name => "Two Keys"; - public override string ShortenedName => "2K"; + public override string Acronym => "2K"; public override string Description => @"Play with two keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index a96375a81d..8575a96bde 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 3; public override string Name => "Three Keys"; - public override string ShortenedName => "3K"; + public override string Acronym => "3K"; public override string Description => @"Play with three keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs index 2bd3d08648..54ea3afa07 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 4; public override string Name => "Four Keys"; - public override string ShortenedName => "4K"; + public override string Acronym => "4K"; public override string Description => @"Play with four keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs index e59b2d6e59..e9a9bba5bd 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 5; public override string Name => "Five Keys"; - public override string ShortenedName => "5K"; + public override string Acronym => "5K"; public override string Description => @"Play with five keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs index 6a05317f46..b9606d1cb5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 6; public override string Name => "Six Keys"; - public override string ShortenedName => "6K"; + public override string Acronym => "6K"; public override string Description => @"Play with six keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs index 7280c345b8..b80d794085 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 7; public override string Name => "Seven Keys"; - public override string ShortenedName => "7K"; + public override string Acronym => "7K"; public override string Description => @"Play with seven keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs index dddef0fa9d..3462d634a4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 8; public override string Name => "Eight Keys"; - public override string ShortenedName => "8K"; + public override string Acronym => "8K"; public override string Description => @"Play with eight keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs index 4ef38503a4..83c505c048 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Mods { @@ -7,7 +7,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override int KeyCount => 9; public override string Name => "Nine Keys"; - public override string ShortenedName => "9K"; + public override string Acronym => "9K"; public override string Description => @"Play with nine keys."; } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 847b0037f1..17f4098420 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -1,28 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Beatmaps; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModMirror : Mod, IApplicableToRulesetContainer + public class ManiaModMirror : Mod, IApplicableToBeatmap { public override string Name => "Mirror"; - public override string ShortenedName => "MR"; + public override string Acronym => "MR"; public override ModType Type => ModType.Conversion; public override double ScoreMultiplier => 1; public override bool Ranked => true; - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToBeatmap(Beatmap beatmap) { - var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; + var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; - rulesetContainer.Objects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); + beatmap.HitObjects.OfType().ForEach(h => h.Column = availableColumns - 1 - h.Column); } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs index b3e73be4ee..2d94fb6af5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs index c22549bfc7..e8988be548 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs index abada804fb..2e22e23dbd 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 0915b80742..ba16140644 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs @@ -1,32 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; +using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModRandom : Mod, IApplicableToRulesetContainer + public class ManiaModRandom : Mod, IApplicableToBeatmap { public override string Name => "Random"; - public override string ShortenedName => "RD"; + public override string Acronym => "RD"; public override ModType Type => ModType.Conversion; - public override FontAwesome Icon => FontAwesome.fa_osu_dice; + public override IconUsage Icon => OsuIcon.Dice; public override string Description => @"Shuffle around the keys!"; public override double ScoreMultiplier => 1; - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToBeatmap(Beatmap beatmap) { - var availableColumns = ((ManiaRulesetContainer)rulesetContainer).Beatmap.TotalColumns; + var availableColumns = ((ManiaBeatmap)beatmap).TotalColumns; var shuffledColumns = Enumerable.Range(0, availableColumns).OrderBy(item => RNG.Next()).ToList(); - rulesetContainer.Objects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); + beatmap.HitObjects.OfType().ForEach(h => h.Column = shuffledColumns[h.Column]); } } } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs index 05843b3af8..ecc343ecaa 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Objects/BarLine.cs b/osu.Game.Rulesets.Mania/Objects/BarLine.cs index 0ceb10cc89..4c644a8f09 100644 --- a/osu.Game.Rulesets.Mania/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/BarLine.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs index cf718ebdb0..9c3197504f 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableBarLine.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index 5fcc71a039..9368af987d 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { RelativeSizeAxes = Axes.X; - InternalChildren = new Drawable[] + AddRangeInternal(new Drawable[] { bodyPiece = new BodyPiece { @@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre } - }; + }); foreach (var tick in tickContainer) AddNested(tick); @@ -75,16 +76,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables AddNested(Tail); } - protected override void OnDirectionChanged(ScrollingDirection direction) + protected override void OnDirectionChanged(ValueChangedEvent e) { - base.OnDirectionChanged(direction); + base.OnDirectionChanged(e); - bodyPiece.Anchor = bodyPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; } public override Color4 AccentColour { - get { return base.AccentColour; } + get => base.AccentColour; set { base.AccentColour = value; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs index 3e014ec35c..9a29273282 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNoteTick.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -7,6 +7,7 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Scoring; @@ -33,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables RelativeSizeAxes = Axes.X; Size = new Vector2(1); - InternalChildren = new[] + AddRangeInternal(new[] { glowContainer = new CircularContainer { @@ -51,12 +52,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables } } } - }; + }); } public override Color4 AccentColour { - get { return base.AccentColour; } + get => base.AccentColour; set { base.AccentColour = value; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 8c96c6dfe7..0873f753be 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -12,6 +12,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables { public abstract class DrawableManiaHitObject : DrawableHitObject { + /// + /// Whether this should always remain alive. + /// + internal bool AlwaysAlive; + /// /// The which causes this to be hit. /// @@ -34,9 +39,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables Direction.BindValueChanged(OnDirectionChanged, true); } - protected virtual void OnDirectionChanged(ScrollingDirection direction) + protected override bool ShouldBeAlive => AlwaysAlive || base.ShouldBeAlive; + + protected virtual void OnDirectionChanged(ValueChangedEvent e) { - Anchor = Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; + Anchor = Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } } @@ -58,6 +65,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables 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 423712b026..afd7777861 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableNote.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osuTK.Graphics; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; @@ -28,19 +29,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables CornerRadius = 5; Masking = true; - InternalChild = headPiece = new NotePiece(); + AddInternal(headPiece = new NotePiece()); } - protected override void OnDirectionChanged(ScrollingDirection direction) + protected override void OnDirectionChanged(ValueChangedEvent e) { - base.OnDirectionChanged(direction); + base.OnDirectionChanged(e); - headPiece.Anchor = headPiece.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; + headPiece.Anchor = headPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; } public override Color4 AccentColour { - get { return base.AccentColour; } + get => base.AccentColour; set { base.AccentColour = value; diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs index 4e5bcf64e7..8102718edf 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/BodyPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Caching; @@ -7,6 +7,7 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; @@ -15,12 +16,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces /// /// Represents length-wise portion of a hold note. /// - internal class BodyPiece : Container, IHasAccentColour + public class BodyPiece : Container, IHasAccentColour { private readonly Container subtractionLayer; - private readonly Drawable background; - private readonly BufferedContainer foreground; + protected readonly Drawable Background; + protected readonly BufferedContainer Foreground; private readonly BufferedContainer subtractionContainer; public BodyPiece() @@ -29,8 +30,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Children = new[] { - background = new Box { RelativeSizeAxes = Axes.Both }, - foreground = new BufferedContainer + Background = new Box { RelativeSizeAxes = Axes.Both }, + Foreground = new BufferedContainer { Blending = BlendingMode.Additive, RelativeSizeAxes = Axes.Both, @@ -77,11 +78,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { if (accentColour == value) return; + accentColour = value; updateAccentColour(); @@ -90,7 +92,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces public bool Hitting { - get { return hitting; } + get => hitting; set { hitting = value; @@ -123,7 +125,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces Radius = DrawWidth }; - foreground.ForceRedraw(); + Foreground.ForceRedraw(); subtractionContainer.ForceRedraw(); subtractionCache.Validate(); @@ -137,18 +139,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces if (!IsLoaded) return; - foreground.Colour = AccentColour.Opacity(0.5f); - background.Colour = AccentColour.Opacity(0.7f); + Foreground.Colour = AccentColour.Opacity(0.5f); + Background.Colour = AccentColour.Opacity(0.7f); const float animation_length = 50; - foreground.ClearTransforms(false, nameof(foreground.Colour)); + Foreground.ClearTransforms(false, nameof(Foreground.Colour)); + if (hitting) { // wait for the next sync point double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2); - using (foreground.BeginDelayedSequence(synchronisedOffset)) - foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(foreground.Colour, animation_length).Loop(); + using (Foreground.BeginDelayedSequence(synchronisedOffset)) + Foreground.FadeColour(AccentColour.Lighten(0.2f), animation_length).Then().FadeColour(Foreground.Colour, animation_length).Loop(); } subtractionCache.Invalidate(); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs index 3a524bfc90..1d25a0c966 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/GlowPiece.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osuTK.Graphics; @@ -35,13 +36,15 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces } private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { if (accentColour == value) return; + accentColour = value; updateGlow(); diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs index 8325cb8ac0..9e0307c5c2 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/LaneGlowPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; @@ -78,8 +78,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces public Color4 AccentColour { - get { return Colour; } - set { Colour = value; } + get => Colour; + set => Colour = value; } } } diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs index 26d5a7f188..bb33693783 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/Pieces/NotePiece.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -49,20 +49,22 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces private void load(IScrollingInfo scrollingInfo) { direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(direction => + direction.BindValueChanged(dir => { - colouredBox.Anchor = colouredBox.Origin = direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; + colouredBox.Anchor = colouredBox.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre; }, true); } private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { if (accentColour == value) return; + accentColour = value; colouredBox.Colour = AccentColour.Lighten(0.9f); diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 77562bb4c2..5e9f46d9c7 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -17,9 +17,10 @@ namespace osu.Game.Rulesets.Mania.Objects public double EndTime => StartTime + Duration; private double duration; + public double Duration { - get { return duration; } + get => duration; set { duration = value; @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Objects public override double StartTime { - get { return base.StartTime; } + get => base.StartTime; set { base.StartTime = value; @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Objects public override int Column { - get { return base.Column; } + get => base.Column; set { base.Column = value; diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs index 05959a31c0..c133ee73b1 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNoteTick.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index e183098a51..70720a926b 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Objects; @@ -8,7 +9,13 @@ namespace osu.Game.Rulesets.Mania.Objects { public abstract class ManiaHitObject : HitObject, IHasColumn { - public virtual int Column { get; set; } + public readonly Bindable ColumnBindable = new Bindable(); + + public virtual int Column + { + get => ColumnBindable.Value; + set => ColumnBindable.Value = value; + } protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); } diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs deleted file mode 100644 index 390d2b0218..0000000000 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObjectDifficulty.cs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects.Types; -using System; - -namespace osu.Game.Rulesets.Mania.Objects -{ - internal class ManiaHitObjectDifficulty - { - /// - /// Factor by how much individual / overall strain decays per second. - /// - /// - /// These values are results of tweaking a lot and taking into account general feedback. - /// - internal const double INDIVIDUAL_DECAY_BASE = 0.125; - internal const double OVERALL_DECAY_BASE = 0.30; - - internal ManiaHitObject BaseHitObject; - - private readonly int beatmapColumnCount; - - - private readonly double endTime; - private readonly double[] heldUntil; - - /// - /// Measures jacks or more generally: repeated presses of the same button - /// - private readonly double[] individualStrains; - - internal double IndividualStrain - { - get - { - return individualStrains[BaseHitObject.Column]; - } - - set - { - individualStrains[BaseHitObject.Column] = value; - } - } - - /// - /// Measures note density in a way - /// - internal double OverallStrain = 1; - - public ManiaHitObjectDifficulty(ManiaHitObject baseHitObject, int columnCount) - { - BaseHitObject = baseHitObject; - - endTime = (baseHitObject as IHasEndTime)?.EndTime ?? baseHitObject.StartTime; - - beatmapColumnCount = columnCount; - heldUntil = new double[beatmapColumnCount]; - individualStrains = new double[beatmapColumnCount]; - - for (int i = 0; i < beatmapColumnCount; ++i) - { - individualStrains[i] = 0; - heldUntil[i] = 0; - } - } - - internal void CalculateStrains(ManiaHitObjectDifficulty previousHitObject, double timeRate) - { - // TODO: Factor in holds - double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double individualDecay = Math.Pow(INDIVIDUAL_DECAY_BASE, timeElapsed / 1000); - double overallDecay = Math.Pow(OVERALL_DECAY_BASE, timeElapsed / 1000); - - double holdFactor = 1.0; // Factor to all additional strains in case something else is held - double holdAddition = 0; // Addition to the current note in case it's a hold and has to be released awkwardly - - // Fill up the heldUntil array - for (int i = 0; i < beatmapColumnCount; ++i) - { - heldUntil[i] = previousHitObject.heldUntil[i]; - - // If there is at least one other overlapping end or note, then we get an addition, buuuuuut... - if (BaseHitObject.StartTime < heldUntil[i] && endTime > heldUntil[i]) - { - holdAddition = 1.0; - } - - // ... this addition only is valid if there is _no_ other note with the same ending. Releasing multiple notes at the same time is just as easy as releasing 1 - if (endTime == heldUntil[i]) - { - holdAddition = 0; - } - - // We give a slight bonus to everything if something is held meanwhile - if (heldUntil[i] > endTime) - { - holdFactor = 1.25; - } - - // Decay individual strains - individualStrains[i] = previousHitObject.individualStrains[i] * individualDecay; - } - - heldUntil[BaseHitObject.Column] = endTime; - - // Increase individual strain in own column - IndividualStrain += 2.0 * holdFactor; - - OverallStrain = previousHitObject.OverallStrain * overallDecay + (1.0 + holdAddition) * holdFactor; - } - } -} diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs index 063b626af1..5f2ceab48b 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitWindows.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps; @@ -13,18 +13,17 @@ namespace osu.Game.Rulesets.Mania.Objects private static readonly IReadOnlyDictionary base_ranges = new Dictionary { { HitResult.Perfect, (44.8, 38.8, 27.8) }, - { HitResult.Great, (128, 98, 68 ) }, + { HitResult.Great, (128, 98, 68) }, { HitResult.Good, (194, 164, 134) }, { HitResult.Ok, (254, 224, 194) }, { HitResult.Meh, (302, 272, 242) }, { HitResult.Miss, (376, 346, 316) }, }; + public override bool IsHitResultAllowed(HitResult result) => true; + public override void SetDifficulty(double difficulty) { - AllowsPerfect = true; - AllowsOk = true; - Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]); Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); diff --git a/osu.Game.Rulesets.Mania/Objects/Note.cs b/osu.Game.Rulesets.Mania/Objects/Note.cs index 42877649d2..0035960c63 100644 --- a/osu.Game.Rulesets.Mania/Objects/Note.cs +++ b/osu.Game.Rulesets.Mania/Objects/Note.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; diff --git a/osu.Game.Rulesets.Mania/Objects/TailNote.cs b/osu.Game.Rulesets.Mania/Objects/TailNote.cs index 9de542bcd3..5a30fd6a12 100644 --- a/osu.Game.Rulesets.Mania/Objects/TailNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/TailNote.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.Judgements; diff --git a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs b/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs index 44a79de7db..1ea3138828 100644 --- a/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs +++ b/osu.Game.Rulesets.Mania/Objects/Types/IHasColumn.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mania.Objects.Types { diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs index 76ccfe324b..f3ea6c7b71 100644 --- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Runtime.CompilerServices; @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.iOS")] diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs index 0eef540d97..e5669816fa 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaAutoGenerator.cs @@ -1,17 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; +using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Mania.Replays { - internal class ManiaAutoGenerator : AutoGenerator + internal class ManiaAutoGenerator : AutoGenerator { public const double RELEASE_DELAY = 20; @@ -22,13 +21,14 @@ namespace osu.Game.Rulesets.Mania.Replays public ManiaAutoGenerator(ManiaBeatmap beatmap) : base(beatmap) { - Replay = new Replay { User = new User { Username = @"Autoplay" } }; + Replay = new Replay(); columnActions = new ManiaAction[Beatmap.TotalColumns]; var normalAction = ManiaAction.Key1; var specialAction = ManiaAction.Special1; int totalCounter = 0; + foreach (var stage in Beatmap.Stages) { for (int i = 0; i < stage.Columns; i++) @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Mania.Replays var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time); var actions = new List(); + foreach (var group in pointGroups) { foreach (var point in group) @@ -61,6 +62,7 @@ namespace osu.Game.Rulesets.Mania.Replays case HitPoint _: actions.Add(columnActions[point.Column]); break; + case ReleasePoint _: actions.Remove(columnActions[point.Column]); break; diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs index ea239bf80f..899718b77e 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaFramedReplayInputHandler.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; namespace osu.Game.Rulesets.Mania.Replays @@ -17,6 +18,6 @@ namespace osu.Game.Rulesets.Mania.Replays protected override bool IsImportant(ManiaReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; } } diff --git a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs index bc9fd6e06f..f7277d3669 100644 --- a/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs +++ b/osu.Game.Rulesets.Mania/Replays/ManiaReplayFrame.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Mania.Replays @@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Replays int activeColumns = (int)(legacyFrame.MouseX ?? 0); int counter = 0; + while (activeColumns > 0) { var isSpecial = stage.IsSpecialColumn(counter); diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..4c877c6193 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,180 @@ +osu file format v14 + +[General] +Mode: 3 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +37500,-50,4,2,1,50,0,0 +41500,-25,4,2,1,50,0,0 + +[HitObjects] +// jacks spaced 1/1 beat apart +64,192,0,1,0,0:0:0:0: +64,192,500,1,0,0:0:0:0: +64,192,1000,1,0,0:0:0:0: +64,192,1500,1,0,0:0:0:0: +64,192,2000,1,0,0:0:0:0: +64,192,2500,1,0,0:0:0:0: + +// jacks spaced 1/2 beat apart +64,192,3500,1,0,0:0:0:0: +64,192,3750,1,0,0:0:0:0: +64,192,4000,1,0,0:0:0:0: +64,192,4250,1,0,0:0:0:0: +64,192,4500,1,0,0:0:0:0: +64,192,4750,1,0,0:0:0:0: +64,192,5000,1,0,0:0:0:0: +64,192,6000,1,0,0:0:0:0: + +// doubles jacks spaced 1/2 beat apart +192,192,6000,1,0,0:0:0:0: +64,192,6250,1,0,0:0:0:0: +192,192,6250,1,0,0:0:0:0: +64,192,6500,1,0,0:0:0:0: +192,192,6500,1,0,0:0:0:0: +64,192,6750,1,0,0:0:0:0: +192,192,6750,1,0,0:0:0:0: +64,192,7000,1,0,0:0:0:0: +192,192,7000,1,0,0:0:0:0: +64,192,7250,1,0,0:0:0:0: +192,192,7250,1,0,0:0:0:0: +64,192,7500,1,0,0:0:0:0: +192,192,7500,1,0,0:0:0:0: + +// trill spaced 1/2 beat apart +64,192,8500,1,0,0:0:0:0: +192,192,8750,1,0,0:0:0:0: +64,192,9000,1,0,0:0:0:0: +192,192,9250,1,0,0:0:0:0: +64,192,9500,1,0,0:0:0:0: +192,192,9750,1,0,0:0:0:0: +64,192,10000,1,0,0:0:0:0: +192,192,10250,1,0,0:0:0:0: +64,192,10500,1,0,0:0:0:0: + +// stair spaced 1/4 apart +64,192,11500,1,0,0:0:0:0: +192,192,11625,1,0,0:0:0:0: +320,192,11750,1,0,0:0:0:0: +448,192,11875,1,0,0:0:0:0: +320,192,12000,1,0,0:0:0:0: +192,192,12125,1,0,0:0:0:0: +64,192,12250,1,0,0:0:0:0: +192,192,12375,1,0,0:0:0:0: +320,192,12500,1,0,0:0:0:0: +448,192,12625,1,0,0:0:0:0: + +// jumpstreams? +64,192,13500,1,0,0:0:0:0: +192,192,13625,1,0,0:0:0:0: +320,192,13750,1,0,0:0:0:0: +448,192,13875,1,0,0:0:0:0: +320,192,14000,1,0,0:0:0:0: +192,192,14000,1,0,0:0:0:0: +64,192,14125,1,0,0:0:0:0: +192,192,14250,1,0,0:0:0:0: +320,192,14250,1,0,0:0:0:0: +448,192,14250,1,0,0:0:0:0: +64,192,14375,1,0,0:0:0:0: +64,192,14500,1,0,0:0:0:0: +320,192,14625,1,0,0:0:0:0: +448,192,14625,1,0,0:0:0:0: +192,192,14625,1,0,0:0:0:0: +192,192,14750,1,0,0:0:0:0: +64,192,14875,1,0,0:0:0:0: +192,192,15000,1,0,0:0:0:0: +320,192,15125,1,0,0:0:0:0: +448,192,15125,1,0,0:0:0:0: + +// double... jumps? +64,192,16000,1,0,0:0:0:0: +64,192,16250,1,0,0:0:0:0: +192,192,16250,1,0,0:0:0:0: +192,192,16500,1,0,0:0:0:0: +320,192,16500,1,0,0:0:0:0: +320,192,16750,1,0,0:0:0:0: +448,192,16750,1,0,0:0:0:0: +448,192,17000,1,0,0:0:0:0: + +// notes alongside hold +64,192,18000,128,0,18500:0:0:0:0: +192,192,18000,1,0,0:0:0:0: +192,192,18250,1,0,0:0:0:0: +192,192,18500,1,0,0:0:0:0: + +// notes overlapping hold +64,192,19500,1,0,0:0:0:0: +192,192,19625,128,0,20875:0:0:0:0: +64,192,19750,1,0,0:0:0:0: +64,192,20000,1,0,0:0:0:0: +64,192,20250,1,0,0:0:0:0: +64,192,20500,1,0,0:0:0:0: +64,192,20750,1,0,0:0:0:0: +64,192,21000,1,0,0:0:0:0: + +// simultaneous holds +64,192,22000,128,0,23000:0:0:0:0: +192,192,22000,128,0,23000:0:0:0:0: +320,192,22000,128,0,23000:0:0:0:0: +448,192,22000,128,0,23000:0:0:0:0: + +// hold stairs +64,192,24500,128,0,25500:0:0:0:0: +192,192,24625,128,0,25375:0:0:0:0: +320,192,24750,128,0,25250:0:0:0:0: +448,192,24875,128,0,25125:0:0:0:0: +448,192,25375,128,0,26375:0:0:0:0: +320,192,25500,128,0,26250:0:0:0:0: +192,192,25625,128,0,26125:0:0:0:0: +64,192,25750,128,0,26000:0:0:0:0: + +// quads +64,192,26500,1,0,0:0:0:0: +64,192,27500,1,0,0:0:0:0: +192,192,27500,1,0,0:0:0:0: +320,192,27500,1,0,0:0:0:0: +448,192,27500,1,0,0:0:0:0: +64,192,27750,1,0,0:0:0:0: +192,192,27750,1,0,0:0:0:0: +320,192,27750,1,0,0:0:0:0: +448,192,27750,1,0,0:0:0:0: +64,192,28000,1,0,0:0:0:0: +192,192,28000,1,0,0:0:0:0: +320,192,28000,1,0,0:0:0:0: +448,192,28000,1,0,0:0:0:0: +64,192,28250,1,0,0:0:0:0: +192,192,28250,1,0,0:0:0:0: +320,192,28250,1,0,0:0:0:0: +448,192,28250,1,0,0:0:0:0: +64,192,28500,1,0,0:0:0:0: +192,192,28500,1,0,0:0:0:0: +320,192,28500,1,0,0:0:0:0: +448,192,28500,1,0,0:0:0:0: + +// double-trills +64,192,29500,1,0,0:0:0:0: +192,192,29500,1,0,0:0:0:0: +320,192,29625,1,0,0:0:0:0: +448,192,29625,1,0,0:0:0:0: +64,192,29750,1,0,0:0:0:0: +192,192,29750,1,0,0:0:0:0: +320,192,29875,1,0,0:0:0:0: +448,192,29875,1,0,0:0:0:0: +64,192,30000,1,0,0:0:0:0: +192,192,30000,1,0,0:0:0:0: +320,192,30125,1,0,0:0:0:0: +448,192,30125,1,0,0:0:0:0: +64,192,30250,1,0,0:0:0:0: +192,192,30250,1,0,0:0:0:0: +320,192,30375,1,0,0:0:0:0: +448,192,30375,1,0,0:0:0:0: +64,192,30500,1,0,0:0:0:0: +192,192,30500,1,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 12b32c46ee..5caf08fb1e 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; @@ -27,36 +27,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_max = 1; - /// - /// The default BAD hit HP increase. - /// - private const double hp_increase_bad = 0.005; - - /// - /// The default OK hit HP increase. - /// - private const double hp_increase_ok = 0.010; - - /// - /// The default GOOD hit HP increase. - /// - private const double hp_increase_good = 0.035; - - /// - /// The default tick hit HP increase. - /// - private const double hp_increase_tick = 0.040; - - /// - /// The default GREAT hit HP increase. - /// - private const double hp_increase_great = 0.055; - - /// - /// The default PERFECT hit HP increase. - /// - private const double hp_increase_perfect = 0.065; - /// /// The MISS HP multiplier at OD = 0. /// @@ -72,11 +42,6 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private const double hp_multiplier_miss_max = 1; - /// - /// The default MISS HP increase. - /// - private const double hp_increase_miss = -0.125; - /// /// The MISS HP multiplier. This is multiplied to the miss hp increase. /// @@ -87,12 +52,8 @@ namespace osu.Game.Rulesets.Mania.Scoring /// private double hpMultiplier = 1; - public ManiaScoreProcessor() - { - } - - public ManiaScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public ManiaScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } @@ -121,41 +82,9 @@ namespace osu.Game.Rulesets.Mania.Scoring } } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); + protected override double HealthAdjustmentFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; - bool isTick = result.Judgement is HoldNoteTickJudgement; - - if (isTick) - { - if (result.IsHit) - Health.Value += hpMultiplier * hp_increase_tick; - } - else - { - switch (result.Type) - { - case HitResult.Miss: - Health.Value += hpMissMultiplier * hp_increase_miss; - break; - case HitResult.Meh: - Health.Value += hpMultiplier * hp_increase_bad; - break; - case HitResult.Ok: - Health.Value += hpMultiplier * hp_increase_ok; - break; - case HitResult.Good: - Health.Value += hpMultiplier * hp_increase_good; - break; - case HitResult.Great: - Health.Value += hpMultiplier * hp_increase_great; - break; - case HitResult.Perfect: - Health.Value += hpMultiplier * hp_increase_perfect; - break; - } - } - } + public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 2f456f7479..c59bed4ea7 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osuTK.Graphics; @@ -8,11 +8,12 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mania.UI.Components; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -21,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI private const float column_width = 45; private const float special_column_width = 70; + /// + /// The index of this column as part of the whole playfield. + /// + public readonly int Index; + public readonly Bindable Action = new Bindable(); private readonly ColumnBackground background; @@ -30,8 +36,10 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; private readonly Container explosionContainer; - public Column() + public Column(int index) { + Index = index; + RelativeSizeAxes = Axes.Y; Width = column_width; @@ -74,28 +82,30 @@ namespace osu.Game.Rulesets.Mania.UI TopLevelContainer.Add(explosionContainer.CreateProxy()); - Direction.BindValueChanged(d => + Direction.BindValueChanged(dir => { hitTargetContainer.Padding = new MarginPadding { - Top = d == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0, - Bottom = d == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0, + Top = dir.NewValue == ScrollingDirection.Up ? ManiaStage.HIT_TARGET_POSITION : 0, + Bottom = dir.NewValue == ScrollingDirection.Down ? ManiaStage.HIT_TARGET_POSITION : 0, }; - keyArea.Anchor = keyArea.Origin= d == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + keyArea.Anchor = keyArea.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; }, true); } public override Axes RelativeSizeAxes => Axes.Y; private bool isSpecial; + public bool IsSpecial { - get { return isSpecial; } + get => isSpecial; set { if (isSpecial == value) return; + isSpecial = value; Width = isSpecial ? special_column_width : column_width; @@ -103,13 +113,15 @@ namespace osu.Game.Rulesets.Mania.UI } private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { if (accentColour == value) return; + accentColour = value; background.AccentColour = value; @@ -137,9 +149,18 @@ namespace osu.Game.Rulesets.Mania.UI HitObjectContainer.Add(hitObject); } + public override bool Remove(DrawableHitObject h) + { + if (!base.Remove(h)) + return false; + + h.OnNewResult -= OnNewResult; + return true; + } + internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { - if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements) + if (!result.IsHit || !judgedObject.DisplayResult || !DisplayJudgements.Value) return; explosionContainer.Add(new HitExplosion(judgedObject) @@ -150,7 +171,7 @@ namespace osu.Game.Rulesets.Mania.UI public bool OnPressed(ManiaAction action) { - if (action != Action) + if (action != Action.Value) return false; var nextObject = @@ -165,5 +186,9 @@ namespace osu.Game.Rulesets.Mania.UI } public bool OnReleased(ManiaAction action) => false; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border + => DrawRectangle.Inflate(new Vector2(ManiaStage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); } } diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs index 6aef158205..b4e29ae9f9 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnBackground.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -48,9 +48,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components }; direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(direction => + direction.BindValueChanged(dir => { - backgroundOverlay.Anchor = backgroundOverlay.Origin = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + backgroundOverlay.Anchor = backgroundOverlay.Origin = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; updateColours(); }, true); } @@ -70,6 +70,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { if (accentColour == value) return; + accentColour = value; updateColours(); diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index f158d5be07..a0d713067d 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.UI; @@ -49,9 +50,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components private void load(IScrollingInfo scrollingInfo) { direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(direction => + direction.BindValueChanged(dir => { - Anchor anchor = direction == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; + Anchor anchor = dir.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; hitTargetBar.Anchor = hitTargetBar.Origin = anchor; hitTargetLine.Anchor = hitTargetLine.Origin = anchor; @@ -73,6 +74,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { if (accentColour == value) return; + accentColour = value; updateColours(); diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs index 228e81fef3..85880222d7 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnKeyArea.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Game.Graphics; @@ -64,11 +65,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components }; direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(direction => + direction.BindValueChanged(dir => { gradient.Colour = ColourInfo.GradientVertical( - direction == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0), - direction == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black); + dir.NewValue == ScrollingDirection.Up ? Color4.Black : Color4.Black.Opacity(0), + dir.NewValue == ScrollingDirection.Up ? Color4.Black.Opacity(0) : Color4.Black); }, true); } @@ -87,6 +88,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components { if (accentColour == value) return; + accentColour = value; updateColours(); diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs index f78cfefab8..8797f014df 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -19,25 +19,18 @@ namespace osu.Game.Rulesets.Mania.UI private void load() { if (JudgementText != null) - JudgementText.TextSize = 25; + JudgementText.Font = JudgementText.Font.With(size: 25); } - protected override void LoadComplete() + protected override double FadeInDuration => 50; + + protected override void ApplyHitAnimations() { - base.LoadComplete(); + JudgementBody.ScaleTo(0.8f); + JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - this.FadeInFromZero(50, Easing.OutQuint); - - if (Result.IsHit) - { - JudgementBody.ScaleTo(0.8f); - JudgementBody.ScaleTo(1, 250, Easing.OutElastic); - - JudgementBody.Delay(50).ScaleTo(0.75f, 250); - this.Delay(50).FadeOut(200); - } - - Expire(); + JudgementBody.Delay(FadeInDuration).ScaleTo(0.75f, 250); + this.Delay(FadeInDuration).FadeOut(200); } } } diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs similarity index 61% rename from osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs rename to osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 321dd4e1cb..c8aeda8fe4 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -1,45 +1,47 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Scoring; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; namespace osu.Game.Rulesets.Mania.UI { - public class ManiaRulesetContainer : ScrollingRulesetContainer + public class DrawableManiaRuleset : DrawableScrollingRuleset { + protected new ManiaPlayfield Playfield => (ManiaPlayfield)base.Playfield; + public new ManiaBeatmap Beatmap => (ManiaBeatmap)base.Beatmap; public IEnumerable BarLines; - protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config; + protected new ManiaRulesetConfigManager Config => (ManiaRulesetConfigManager)base.Config; private readonly Bindable configDirection = new Bindable(); - public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableManiaRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { // Generate the bar lines double lastObjectTime = (Objects.LastOrDefault() as IHasEndTime)?.EndTime ?? Objects.LastOrDefault()?.StartTime ?? double.MaxValue; @@ -55,6 +57,7 @@ namespace osu.Game.Rulesets.Mania.UI double endTime = i < timingPoints.Count - 1 ? timingPoints[i + 1].Time - point.BeatLength : lastObjectTime + point.BeatLength * (int)point.TimeSignature; int index = 0; + for (double t = timingPoints[i].Time; Precision.DefinitelyBigger(endTime, t); t += point.BeatLength, index++) { barLines.Add(new BarLine @@ -74,32 +77,39 @@ namespace osu.Game.Rulesets.Mania.UI { BarLines.ForEach(Playfield.Add); - Config.BindWith(ManiaSetting.ScrollDirection, configDirection); - configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true); + Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection); + configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true); - Config.BindWith(ManiaSetting.ScrollTime, TimeRange); + Config.BindWith(ManiaRulesetSetting.ScrollTime, TimeRange); } - protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + /// + /// Retrieves the column that intersects a screen-space position. + /// + /// The screen-space position. + /// The column which intersects with . + public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); + + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); + + protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); - public override int Variant => (int)(Mods.OfType().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single) + Beatmap.TotalColumns; + public override int Variant => (int)(Beatmap.Stages.Count == 1 ? PlayfieldType.Single : PlayfieldType.Dual) + Beatmap.TotalColumns; - public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); + protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant); - public override DrawableHitObject GetVisualRepresentation(ManiaHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(ManiaHitObject h) { switch (h) { case HoldNote holdNote: return new DrawableHoldNote(holdNote); + case Note note: return new DrawableNote(note); + default: return null; } diff --git a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs index 817b60d14a..0ec1fc38d2 100644 --- a/osu.Game.Rulesets.Mania/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Mania/UI/HitExplosion.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; using osu.Game.Rulesets.Mania.Objects.Drawables; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 85d1c0c4b0..5ab07416a6 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -17,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI { private readonly List stages = new List(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); + public ManiaPlayfield(List stageDefinitions) { if (stageDefinitions == null) @@ -25,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); - Size = new Vector2(1, 0.8f); - GridContainer playfieldGrid; AddInternal(playfieldGrid = new GridContainer { @@ -37,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.UI var normalColumnAction = ManiaAction.Key1; var specialColumnAction = ManiaAction.Special1; int firstColumnIndex = 0; + for (int i = 0; i < stageDefinitions.Count; i++) { var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction); @@ -52,11 +54,46 @@ namespace osu.Game.Rulesets.Mania.UI public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h); + public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h); + public void Add(BarLine barline) => stages.ForEach(s => s.Add(barline)); + /// + /// Retrieves a column from a screen-space position. + /// + /// The screen-space position. + /// The column which the lies in. + public Column GetColumnByPosition(Vector2 screenSpacePosition) + { + Column found = null; + + foreach (var stage in stages) + { + foreach (var column in stage.Columns) + { + if (column.ReceivePositionalInputAt(screenSpacePosition)) + { + found = column; + break; + } + } + + if (found != null) + break; + } + + return found; + } + + /// + /// Retrieves the total amount of columns across all stages in this playfield. + /// + public int TotalColumns => stages.Sum(s => s.Columns.Count); + private ManiaStage getStageByColumn(int column) { int sum = 0; + foreach (var stage in stages) { sum = sum + stage.Columns.Count; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..d893a3fdde --- /dev/null +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfieldAdjustmentContainer.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.UI +{ + public class ManiaPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer + { + public ManiaPlayfieldAdjustmentContainer() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(1, 0.8f); + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs index 0fc9c1920a..98165fedeb 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaScrollingDirection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.UI.Scrolling; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs index 73c080ffba..a28de7ea58 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaStage.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaStage.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.UI /// public class ManiaStage : ScrollingPlayfield { + public const float COLUMN_SPACING = 1; + public const float HIT_TARGET_POSITION = 50; public IReadOnlyList Columns => columnFlow.Children; @@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Mania.UI private List normalColumnColours = new List(); private Color4 specialColumnColour; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Columns.Any(c => c.ReceivePositionalInputAt(screenSpacePos)); + private readonly int firstColumnIndex; public ManiaStage(int firstColumnIndex, StageDefinition definition, ref ManiaAction normalColumnStartAction, ref ManiaAction specialColumnStartAction) @@ -84,8 +88,8 @@ namespace osu.Game.Rulesets.Mania.UI RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = 1, Right = 1 }, - Spacing = new Vector2(1, 0) + Padding = new MarginPadding { Left = COLUMN_SPACING, Right = COLUMN_SPACING }, + Spacing = new Vector2(COLUMN_SPACING, 0) }, } }, @@ -123,7 +127,7 @@ namespace osu.Game.Rulesets.Mania.UI for (int i = 0; i < definition.Columns; i++) { var isSpecial = definition.IsSpecialColumn(i); - var column = new Column + var column = new Column(firstColumnIndex + i) { IsSpecial = isSpecial, Action = { Value = isSpecial ? specialColumnStartAction++ : normalColumnStartAction++ } @@ -132,12 +136,12 @@ namespace osu.Game.Rulesets.Mania.UI AddColumn(column); } - Direction.BindValueChanged(d => + Direction.BindValueChanged(dir => { barLineContainer.Padding = new MarginPadding { - Top = d == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0, - Bottom = d == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0, + Top = dir.NewValue == ScrollingDirection.Up ? HIT_TARGET_POSITION : 0, + Bottom = dir.NewValue == ScrollingDirection.Down ? HIT_TARGET_POSITION : 0, }; }, true); } @@ -152,16 +156,36 @@ namespace osu.Game.Rulesets.Mania.UI public override void Add(DrawableHitObject h) { var maniaObject = (ManiaHitObject)h.HitObject; - int columnIndex = maniaObject.Column - firstColumnIndex; - Columns.ElementAt(columnIndex).Add(h); + + int columnIndex = -1; + + maniaObject.ColumnBindable.BindValueChanged(_ => + { + if (columnIndex != -1) + Columns.ElementAt(columnIndex).Remove(h); + + columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Add(h); + }, true); + h.OnNewResult += OnNewResult; } + public override bool Remove(DrawableHitObject h) + { + var maniaObject = (ManiaHitObject)h.HitObject; + int columnIndex = maniaObject.Column - firstColumnIndex; + Columns.ElementAt(columnIndex).Remove(h); + + h.OnNewResult -= OnNewResult; + return true; + } + public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline)); internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { - if (!judgedObject.DisplayResult || !DisplayJudgements) + if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; judgements.Clear(); diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..01e635f09c --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Osu.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs new file mode 100644 index 0000000000..7a0797a909 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Osu.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist new file mode 100644 index 0000000000..f79215cf54 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Osu.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Osu-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj new file mode 100644 index 0000000000..9930a166e3 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -0,0 +1,45 @@ + + + + + Debug + iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96} + Exe + osu.Game.Rulesets.Osu.Tests + osu.Game.Rulesets.Osu.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {C92A607B-1FDD-4954-9F92-03FF547D9080} + osu.Game.Rulesets.Osu + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json index fe3ecbec47..ed03e99b9b 100644 --- a/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Osu.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Osu.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs index 3a551bbbcf..f98d63e6c7 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuBeatmapConversionTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -33,9 +33,12 @@ namespace osu.Game.Rulesets.Osu.Tests case Slider slider: foreach (var nested in slider.NestedHitObjects) yield return createConvertValue(nested); + break; + default: yield return createConvertValue(hitObject); + break; } diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..e55dc1f902 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class OsuDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; + + [TestCase(6.931145117263422, "diffcalc-test")] + public void Test(double expected, string name) + => base.Test(expected, name); + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(new OsuRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new OsuRuleset(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs index 579cb77084..e8b99e86f9 100644 --- a/osu.Game.Rulesets.Osu.Tests/StackingTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/StackingTest.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using System.Linq; using System.Text; using NUnit.Framework; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; using Decoder = osu.Game.Beatmaps.Formats.Decoder; @@ -22,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Tests using (var reader = new StreamReader(stream)) { var beatmap = Decoder.GetDecoder(reader).Decode(reader); - var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo); + var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()); var objects = converted.HitObjects.ToList(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs b/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs deleted file mode 100644 index f2525ad555..0000000000 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Osu.Tests -{ - [TestFixture] - public class TestCaseEditor : EditorTestCase - { - public TestCaseEditor() - : base(new OsuRuleset()) - { - } - } -} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs new file mode 100644 index 0000000000..4aca34bf64 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneEditor : EditorTestScene + { + public TestSceneEditor() + : base(new OsuRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs similarity index 52% rename from osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 472da88e1f..1b1cfa89c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -9,25 +9,26 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor + public class TestSceneGameplayCursor : OsuTestScene, IProvideCursor { - private GameplayCursor cursor; + private GameplayCursorContainer cursorContainer; - public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(CursorTrail) }; - public CursorContainer Cursor => cursor; + public CursorContainer Cursor => cursorContainer; public bool ProvidingUserCursor => true; [BackgroundDependencyLoader] private void load() { - Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); + Add(cursorContainer = new OsuCursorContainer { RelativeSizeAxes = Axes.Both }); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs similarity index 89% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs index 1c1a027411..d44a0cd841 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircle.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircle.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -19,7 +19,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircle : OsuTestCase + public class TestSceneHitCircle : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -30,9 +30,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); - public TestCaseHitCircle() + public TestSceneHitCircle() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); @@ -89,7 +88,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private readonly bool auto; - public TestDrawableHitCircle(HitCircle h, bool auto) : base(h) + public TestDrawableHitCircle(HitCircle h, bool auto) + : base(h) { this.auto = auto; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs similarity index 52% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs index 68c35faca4..55c6b22146 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleHidden.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -10,13 +10,13 @@ using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseHitCircleHidden : TestCaseHitCircle + public class TestSceneHitCircleHidden : TestSceneHitCircle { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestCaseHitCircleHidden() + public TestSceneHitCircleHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs new file mode 100644 index 0000000000..399cf22599 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneHitCircleLongCombo : PlayerTestScene + { + public TestSceneHitCircleLongCombo() + : base(new OsuRuleset()) + { + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, + Ruleset = ruleset + } + }; + + for (int i = 0; i < 512; i++) + beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 }); + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs similarity index 70% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs index 313438a337..4c6abc45f7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCirclePlacementBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseHitCirclePlacementBlueprint : PlacementBlueprintTestCase + public class TestSceneHitCirclePlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject); protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs similarity index 73% rename from osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs index fed62188ab..32043bf5d7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseHitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -12,11 +12,11 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseHitCircleSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene { private readonly DrawableHitCircle drawableObject; - public TestCaseHitCircleSelectionBlueprint() + public TestSceneHitCircleSelectionBlueprint() { var hitCircle = new HitCircle { Position = new Vector2(256, 192) }; hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs new file mode 100644 index 0000000000..64e7632b1b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuFlashlight.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneOsuFlashlight : TestSceneOsuPlayer + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), }; + + return base.CreatePlayer(ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs new file mode 100644 index 0000000000..0a33b09ba8 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneOsuPlayer : PlayerTestScene + { + public TestSceneOsuPlayer() + : base(new OsuRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs new file mode 100644 index 0000000000..8e73d6152f --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneResumeOverlay.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneResumeOverlay : ManualInputManagerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuResumeOverlay), + }; + + public TestSceneResumeOverlay() + { + ManualOsuInputManager osuInputManager; + CursorContainer cursor; + ResumeOverlay resume; + + bool resumeFired = false; + + Child = osuInputManager = new ManualOsuInputManager(new OsuRuleset().RulesetInfo) + { + Children = new Drawable[] + { + cursor = new CursorContainer(), + resume = new OsuResumeOverlay + { + GameplayCursor = cursor + }, + } + }; + + resume.ResumeAction = () => resumeFired = true; + + AddStep("move mouse to center", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("show", () => resume.Show()); + + AddStep("move mouse away", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.TopLeft)); + AddStep("click", () => osuInputManager.GameClick()); + AddAssert("not dismissed", () => !resumeFired && resume.State.Value == Visibility.Visible); + + AddStep("move mouse back", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + AddStep("click", () => osuInputManager.GameClick()); + AddAssert("dismissed", () => resumeFired && resume.State.Value == Visibility.Hidden); + } + + private class ManualOsuInputManager : OsuInputManager + { + public ManualOsuInputManager(RulesetInfo ruleset) + : base(ruleset) + { + } + + public void GameClick() + { + KeyBindingContainer.TriggerPressed(OsuAction.LeftButton); + KeyBindingContainer.TriggerReleased(OsuAction.LeftButton); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs similarity index 73% rename from osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs index 97978cff1e..3d8afd66f4 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseShaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneShaking.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.MathUtils; @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseShaking : TestCaseHitCircle + public class TestSceneShaking : TestSceneHitCircle { public override void Add(Drawable drawable) { diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs similarity index 94% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 1895913917..c5a27205d6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -16,6 +16,7 @@ using osuTK.Graphics; using osu.Game.Rulesets.Mods; using System.Linq; using NUnit.Framework; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -26,7 +27,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSlider : OsuTestCase + public class TestSceneSlider : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -43,9 +44,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); - public TestCaseSlider() + public TestSceneSlider() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); @@ -248,9 +248,9 @@ namespace osu.Game.Rulesets.Osu.Tests private void createCatmull(int repeats = 0) { - var repeatSamples = new List>(); + var repeatSamples = new List>(); for (int i = 0; i < repeats; i++) - repeatSamples.Add(new List()); + repeatSamples.Add(new List()); var slider = new Slider { @@ -270,11 +270,11 @@ namespace osu.Game.Rulesets.Osu.Tests addSlider(slider, 3, 1); } - private List> createEmptySamples(int repeats) + private List> createEmptySamples(int repeats) { - var repeatSamples = new List>(); + var repeatSamples = new List>(); for (int i = 0; i < repeats; i++) - repeatSamples.Add(new List()); + repeatSamples.Add(new List()); return repeatSamples; } @@ -291,7 +291,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); drawable.OnNewResult += onNewResult; @@ -300,6 +300,7 @@ namespace osu.Game.Rulesets.Osu.Tests } private float judgementOffsetDirection = 1; + private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { var osuObject = judgedObject as DrawableOsuHitObject; @@ -313,7 +314,7 @@ namespace osu.Game.Rulesets.Osu.Tests Origin = Anchor.Centre, Text = result.IsHit ? "Hit!" : "Miss!", Colour = result.IsHit ? Color4.Green : Color4.Red, - TextSize = 30, + Font = OsuFont.GetFont(size: 30), Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs similarity index 53% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs index 1c207d1fca..2a9c1d167b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderHidden.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -10,13 +10,13 @@ using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSliderHidden : TestCaseSlider + public class TestSceneSliderHidden : TestSceneSlider { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestCaseSliderHidden() + public TestSceneSliderHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs new file mode 100644 index 0000000000..2eb783233a --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -0,0 +1,363 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneSliderInput : RateAdjustedBeatmapTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SliderBall), + typeof(DrawableSlider), + typeof(DrawableSliderTick), + typeof(DrawableRepeatPoint), + typeof(DrawableOsuHitObject), + typeof(DrawableSliderHead), + typeof(DrawableSliderTail), + }; + + private const double time_before_slider = 250; + private const double time_slider_start = 1500; + private const double time_during_slide_1 = 2500; + private const double time_during_slide_2 = 3000; + private const double time_during_slide_3 = 3500; + private const double time_during_slide_4 = 3800; + + private List judgementResults; + private bool allJudgedFired; + + /// + /// Scenario: + /// - Press a key before a slider starts + /// - Press the other key on the slider head timed correctly while holding the original key + /// - Release the latter pressed key + /// Expected Result: + /// A passing test case will have the cursor lose tracking on replay frame 3. + /// + [Test] + public void TestInvalidKeyTransfer() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, + }); + + AddAssert("Tracking lost", assertMidSliderJudgementFail); + } + + /// + /// Scenario: + /// - Press a key on the slider head timed correctly + /// - Press the other key in the middle of the slider while holding the original key + /// - Release the original key used to hit the slider + /// Expected Result: + /// A passing test case will have the cursor continue tracking on replay frame 3. + /// + [Test] + public void TestLeftBeforeSliderThenRightThenLettingGoOfLeft() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_during_slide_1 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, + }); + + AddAssert("Tracking retained", assertGreatJudge); + } + + /// + /// Scenario: + /// - Press a key on the slider head timed correctly + /// - Press the other key in the middle of the slider while holding the original key + /// - Release the new key that was pressed second + /// Expected Result: + /// A passing test case will have the cursor continue tracking on replay frame 3. + /// + [Test] + public void TestTrackingRetentionLeftRightLeft() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, + }); + + AddAssert("Tracking retained", assertGreatJudge); + } + + /// + /// Scenario: + /// - Press a key before a slider starts + /// - Press the other key on the slider head timed correctly while holding the original key + /// - Release the key that was held down before the slider started. + /// Expected Result: + /// A passing test case will have the cursor continue tracking on replay frame 3 + /// + [Test] + public void TestTrackingLeftBeforeSliderToRight() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, + }); + + AddAssert("Tracking retained", assertGreatJudge); + } + + /// + /// Scenario: + /// - Press a key before a slider starts + /// - Hold the key down throughout the slider without pressing any other buttons. + /// Expected Result: + /// A passing test case will have the cursor track the slider, but miss the slider head. + /// + [Test] + public void TestTrackingPreclicked() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, + }); + + AddAssert("Tracking retained, sliderhead miss", assertHeadMissTailTracked); + } + + /// + /// Scenario: + /// - Press a key before a slider starts + /// - Hold the key down after the slider starts + /// - Move the cursor away from the slider body + /// - Move the cursor back onto the body + /// Expected Result: + /// A passing test case will have the cursor track the slider, miss the head, miss the ticks where its outside of the body, and resume tracking when the cursor returns. + /// + [Test] + public void TestTrackingReturnMidSlider() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(150, 150), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, + new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, + }); + + AddAssert("Tracking re-acquired", assertMidSliderJudgements); + } + + /// + /// Scenario: + /// - Press a key before a slider starts + /// - Press the other key on the slider head timed correctly while holding the original key + /// - Release the key used to hit the slider head + /// - While holding the first key, move the cursor away from the slider body + /// - Still holding the first key, move the cursor back to the slider body + /// Expected Result: + /// A passing test case will have the slider not track despite having the cursor return to the slider body. + /// + [Test] + public void TestTrackingReturnMidSliderKeyDownBefore() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, + new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, + }); + + AddAssert("Tracking lost", assertMidSliderJudgementFail); + } + + /// + /// Scenario: + /// - Wait for the slider to reach a mid-point + /// - Press a key away from the slider body + /// - While holding down the key, move into the slider body + /// Expected Result: + /// A passing test case will have the slider track the cursor after the cursor enters the slider body. + /// + [Test] + public void TestTrackingMidSlider() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(150, 150), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, + new OsuReplayFrame { Position = new Vector2(200, 200), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, + }); + + AddAssert("Tracking acquired", assertMidSliderJudgements); + } + + /// + /// Scenario: + /// - Press a key before the slider starts + /// - Press another key on the slider head while holding the original key + /// - Move out of the slider body while releasing the two pressed keys + /// - Move back into the slider body while pressing any key. + /// Expected Result: + /// A passing test case will have the slider track the cursor after the cursor enters the slider body. + /// + [Test] + public void TestMidSliderTrackingAcquired() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(100, 100), Time = time_during_slide_1 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_2 }, + }); + + AddAssert("Tracking acquired", assertMidSliderJudgements); + } + + [Test] + public void TestMidSliderTrackingAcquiredWithMouseDownOutsideSlider() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_before_slider }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton, OsuAction.RightButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.RightButton }, Time = time_during_slide_1 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.RightButton }, Time = time_during_slide_2 }, + }); + + AddAssert("Tracking acquired", assertMidSliderJudgements); + } + + /// + /// Scenario: + /// - Press a key on the slider head + /// - While holding the key, move outside of the slider body with the cursor + /// - Release the key while outside of the slider body + /// - Press the key again while outside of the slider body + /// - Move back into the slider body while holding the pressed key + /// Expected Result: + /// A passing test case will have the slider track the cursor after the cursor enters the slider body. + /// + [Test] + public void TestTrackingReleasedValidKey() + { + performTest(new List + { + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_slider_start }, + new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.LeftButton }, Time = time_during_slide_1 }, + new OsuReplayFrame { Position = new Vector2(100, 100), Time = time_during_slide_2 }, + new OsuReplayFrame { Position = new Vector2(100, 100), Actions = { OsuAction.LeftButton }, Time = time_during_slide_3 }, + new OsuReplayFrame { Position = new Vector2(0, 0), Actions = { OsuAction.LeftButton }, Time = time_during_slide_4 }, + }); + + AddAssert("Tracking acquired", assertMidSliderJudgements); + } + + private bool assertGreatJudge() => judgementResults.Last().Type == HitResult.Great; + + private bool assertHeadMissTailTracked() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great && judgementResults.First().Type == HitResult.Miss; + + private bool assertMidSliderJudgements() => judgementResults[judgementResults.Count - 2].Type == HitResult.Great; + + private bool assertMidSliderJudgementFail() => judgementResults[judgementResults.Count - 2].Type == HitResult.Miss; + + private ScoreAccessibleReplayPlayer currentPlayer; + + private void performTest(List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = + { + new Slider + { + StartTime = time_slider_start, + Position = new Vector2(0, 0), + Path = new SliderPath(PathType.PerfectCurve, new[] + { + Vector2.Zero, + new Vector2(25, 0), + }, 25), + } + }, + ControlPointInfo = + { + DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } } + }, + BeatmapInfo = + { + BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, + Ruleset = new OsuRuleset().RulesetInfo + }, + }); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + p.ScoreProcessor.AllJudged += () => + { + if (currentPlayer == p) allJudgedFired = true; + }; + }; + + LoadScreen(currentPlayer = p); + allJudgedFired = false; + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for all judged", () => allJudgedFired); + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, false, false) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs similarity index 70% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs index 1f693ad9f4..0522260150 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderPlacementBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSliderPlacementBlueprint : PlacementBlueprintTestCase + public class TestSceneSliderPlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject); protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs similarity index 84% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index cd07369ccf..8cf5a2f33e 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSliderSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneSliderSelectionBlueprint : SelectionBlueprintTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly DrawableSlider drawableObject; - public TestCaseSliderSelectionBlueprint() + public TestSceneSliderSelectionBlueprint() { var slider = new Slider { diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs similarity index 85% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 3b91ea93b8..3ed3f3e981 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -18,10 +18,10 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSpinner : OsuTestCase + public class TestSceneSpinner : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] -{ + { typeof(SpinnerDisc), typeof(DrawableSpinner), typeof(DrawableOsuHitObject) @@ -31,9 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests protected override Container Content => content; private int depthIndex; - protected readonly List Mods = new List(); - public TestCaseSpinner() + public TestSceneSpinner() { base.Content.Add(content = new OsuInputManager(new RulesetInfo { ID = 0 })); @@ -57,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests Depth = depthIndex++ }; - foreach (var mod in Mods.OfType()) + foreach (var mod in Mods.Value.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); Add(drawable); @@ -67,7 +66,8 @@ namespace osu.Game.Rulesets.Osu.Tests { private bool auto; - public TestDrawableSpinner(Spinner s, bool auto) : base(s) + public TestDrawableSpinner(Spinner s, bool auto) + : base(s) { this.auto = auto; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs similarity index 53% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs index b93a6ed0f1..a0ab1908d6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerHidden.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -10,13 +10,13 @@ using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestCaseSpinnerHidden : TestCaseSpinner + public class TestSceneSpinnerHidden : TestSceneSpinner { public override IReadOnlyList RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(OsuModHidden) }).ToList(); - public TestCaseSpinnerHidden() + public TestSceneSpinnerHidden() { - Mods.Add(new OsuModHidden()); + Mods.Value = new[] { new OsuModHidden() }; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs similarity index 70% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs index 9a90be2582..d74d072857 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerPlacementBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -11,7 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSpinnerPlacementBlueprint : PlacementBlueprintTestCase + public class TestSceneSpinnerPlacementBlueprint : PlacementBlueprintTestScene { protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject); diff --git a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs similarity index 83% rename from osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs index d7ba5d8105..c5cea76b14 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestCaseSpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -17,7 +17,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests { - public class TestCaseSpinnerSelectionBlueprint : SelectionBlueprintTestCase + public class TestSceneSpinnerSelectionBlueprint : SelectionBlueprintTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Tests private readonly DrawableSpinner drawableSpinner; - public TestCaseSpinnerSelectionBlueprint() + public TestSceneSpinnerSelectionBlueprint() { var spinner = new Spinner { 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 6117812f45..a99a93c3e9 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,14 +2,14 @@ - - - + + + WinExe - netcoreapp2.1 + netcoreapp2.2 diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs index 6d90c2a875..491d82b89e 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmap.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Osu.Beatmaps { Name = @"Circle Count", Content = circles.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Slider Count", Content = sliders.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Spinner Count", Content = spinners.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 510c32fd9f..6a41e93c35 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index db80948c94..7bb1f42802 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; @@ -23,60 +24,73 @@ namespace osu.Game.Rulesets.Osu.Beatmaps var osuBeatmap = (Beatmap)Beatmap; - // Reset stacking - foreach (var h in osuBeatmap.HitObjects) - h.StackHeight = 0; + if (osuBeatmap.HitObjects.Count > 0) + { + // Reset stacking + foreach (var h in osuBeatmap.HitObjects) + h.StackHeight = 0; - if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) - applyStacking(osuBeatmap); - else - applyStackingOld(osuBeatmap); + if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) + applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1); + else + applyStackingOld(osuBeatmap); + } } - private void applyStacking(Beatmap beatmap) + private void applyStacking(Beatmap beatmap, int startIndex, int endIndex) { - // Extend the end index to include objects they are stacked on - int extendedEndIndex = beatmap.HitObjects.Count - 1; - for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--) + if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be greater than {nameof(endIndex)}."); + if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be less than 0."); + if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0."); + + int extendedEndIndex = endIndex; + + if (endIndex < beatmap.HitObjects.Count - 1) { - int stackBaseIndex = i; - for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) + // Extend the end index to include objects they are stacked on + for (int i = endIndex; i >= startIndex; i--) { - OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; - if (stackBaseObject is Spinner) break; + int stackBaseIndex = i; - OsuHitObject objectN = beatmap.HitObjects[n]; - if (objectN is Spinner) - continue; - - double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; - double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency; - - if (objectN.StartTime - endTime > stackThreshold) - //We are no longer within stacking range of the next object. - break; - - if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance || - stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance) + for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) { - stackBaseIndex = n; + OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; + if (stackBaseObject is Spinner) break; - // HitObjects after the specified update range haven't been reset yet - objectN.StackHeight = 0; + OsuHitObject objectN = beatmap.HitObjects[n]; + if (objectN is Spinner) + continue; + + double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; + double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency; + + if (objectN.StartTime - endTime > stackThreshold) + //We are no longer within stacking range of the next object. + break; + + if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance + || (stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance)) + { + stackBaseIndex = n; + + // HitObjects after the specified update range haven't been reset yet + objectN.StackHeight = 0; + } } - } - if (stackBaseIndex > extendedEndIndex) - { - extendedEndIndex = stackBaseIndex; - if (extendedEndIndex == beatmap.HitObjects.Count - 1) - break; + if (stackBaseIndex > extendedEndIndex) + { + extendedEndIndex = stackBaseIndex; + if (extendedEndIndex == beatmap.HitObjects.Count - 1) + break; + } } } //Reverse pass for stack calculation. - int extendedStartIndex = 0; - for (int i = extendedEndIndex; i > 0; i--) + int extendedStartIndex = startIndex; + + for (int i = extendedEndIndex; i > startIndex; i--) { int n = i; /* We should check every note which has not yet got a stack. @@ -127,6 +141,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps if (objectN is Slider && Vector2Extensions.Distance(objectN.EndPosition, objectI.Position) < stack_distance) { int offset = objectI.StackHeight - objectN.StackHeight + 1; + for (int j = n + 1; j <= i; j++) { //For each object which was declared under this slider, we will offset it to appear *below* the slider end (rather than above). @@ -155,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps /* We have hit the first slider in a possible stack. * From this point on, we ALWAYS stack positive regardless. */ - while (--n >= 0) + while (--n >= startIndex) { OsuHitObject objectN = beatmap.HitObjects[n]; if (objectN is Spinner) continue; diff --git a/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs new file mode 100644 index 0000000000..f76635a932 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Configuration/OsuRulesetConfigManager.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; + +namespace osu.Game.Rulesets.Osu.Configuration +{ + public class OsuRulesetConfigManager : RulesetConfigManager + { + public OsuRulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) + : base(settings, ruleset, variant) + { + } + + protected override void InitialiseDefaults() + { + base.InitialiseDefaults(); + Set(OsuRulesetSetting.SnakingInSliders, true); + Set(OsuRulesetSetting.SnakingOutSliders, true); + Set(OsuRulesetSetting.ShowCursorTrail, true); + } + } + + public enum OsuRulesetSetting + { + SnakingInSliders, + SnakingOutSliders, + ShowCursorTrail + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 2b42eed0c5..6e991a1d08 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -1,8 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -13,10 +12,5 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double ApproachRate; public double OverallDifficulty; public int MaxCombo; - - public OsuDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) - { - } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 8fc2b69267..c197933233 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Difficulty.Skills; @@ -15,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyCalculator : DifficultyCalculator { - private const int section_length = 400; private const double difficulty_multiplier = 0.0675; public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) @@ -23,62 +25,56 @@ namespace osu.Game.Rulesets.Osu.Difficulty { } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - if (!beatmap.HitObjects.Any()) - return new OsuDifficultyAttributes(mods, 0); - - OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast().ToList(), timeRate); - Skill[] skills = - { - new Aim(), - new Speed() - }; - - double sectionLength = section_length * timeRate; - - // The first object doesn't generate a strain, so we begin with an incremented section end - double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; - - foreach (OsuDifficultyHitObject h in difficultyBeatmap) - { - while (h.BaseObject.StartTime > currentSectionEnd) - { - foreach (Skill s in skills) - { - s.SaveCurrentPeak(); - s.StartNewSectionFrom(currentSectionEnd); - } - - currentSectionEnd += sectionLength; - } - - foreach (Skill s in skills) - s.Process(h); - } + if (beatmap.HitObjects.Count == 0) + return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2; // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed 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; + double hitWindowGreat = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate; + double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) maxCombo += beatmap.HitObjects.OfType().Sum(s => s.NestedHitObjects.Count - 1); - return new OsuDifficultyAttributes(mods, starRating) + return new OsuDifficultyAttributes { + StarRating = starRating, + Mods = mods, AimStrain = aimRating, SpeedStrain = speedRating, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, - MaxCombo = maxCombo + MaxCombo = maxCombo, + Skills = skills }; } + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) + { + // The first jump is formed by the first two hitobjects of the map. + // If the map has less than two OsuHitObjects, the enumerator will not return anything. + for (int i = 1; i < beatmap.HitObjects.Count; i++) + { + var lastLast = i > 1 ? beatmap.HitObjects[i - 2] : null; + var last = beatmap.HitObjects[i - 1]; + var current = beatmap.HitObjects[i]; + + yield return new OsuDifficultyHitObject(current, lastLast, last, clockRate); + } + } + + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] + { + new Aim(), + new Speed() + }; + protected override Mod[] DifficultyAdjustmentMods => new Mod[] { new OsuModDoubleTime(), diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index b0887ac72b..093081b6a1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Difficulty { @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private int countMeh; private int countMiss; - public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle); @@ -87,11 +88,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAimValue() { - double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.AimStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f; + double rawAim = Attributes.AimStrain; + + if (mods.Any(m => m is OsuModTouchDevice)) + rawAim = Math.Pow(rawAim, 0.8); + + double aimValue = Math.Pow(5.0f * Math.Max(1.0f, rawAim / 0.0675f) - 4.0f, 3.0f) / 100000.0f; // Longer maps are worth more double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); aimValue *= lengthBonus; @@ -103,27 +109,28 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); double approachRateFactor = 1.0f; + if (Attributes.ApproachRate > 10.33f) - approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f); + approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); else if (Attributes.ApproachRate < 8.0f) { - // HD is worth more with lower ar! - if (mods.Any(h => h is OsuModHidden)) - approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate); - else - approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate); + approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate); } aimValue *= approachRateFactor; // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) - aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11. + aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); if (mods.Any(h => h is OsuModFlashlight)) { - // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. - aimValue *= 1.45f * lengthBonus; + // Apply object-based bonus for flashlight. + aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) + + (totalHits > 200 + ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) + + (totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f) + : 0.0f); } // Scale the aim value with accuracy _slightly_ @@ -140,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // Longer maps are worth more speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) + - (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0f) * 0.5f : 0.0f); // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available speedValue *= Math.Pow(0.97f, countMiss); @@ -149,13 +156,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (beatmapMaxCombo > 0) speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + double approachRateFactor = 1.0f; + if (Attributes.ApproachRate > 10.33f) + approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); + + speedValue *= approachRateFactor; + if (mods.Any(m => m is OsuModHidden)) - speedValue *= 1.18f; + speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); // Scale the speed value with accuracy _slightly_ - speedValue *= 0.5f + accuracy / 2.0f; + speedValue *= 0.02f + accuracy; // It is important to also consider accuracy difficulty when doing that - speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600; return speedValue; } @@ -183,7 +196,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); if (mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.02f; + accuracyValue *= 1.08f; if (mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02f; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs deleted file mode 100644 index 24d4677981..0000000000 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyBeatmap.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Rulesets.Osu.Objects; - -namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing -{ - /// - /// An enumerable container wrapping input as - /// which contains extra data required for difficulty calculation. - /// - public class OsuDifficultyBeatmap : IEnumerable - { - private readonly IEnumerator difficultyObjects; - - /// - /// Creates an enumerator, which preprocesses a list of s recieved as input, wrapping them as - /// which contains extra data required for difficulty calculation. - /// - public OsuDifficultyBeatmap(List objects, double timeRate) - { - // Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases. - // This should probably happen before the objects reach the difficulty calculator. - difficultyObjects = createDifficultyObjectEnumerator(objects.OrderBy(h => h.StartTime).ToList(), timeRate); - } - - /// - /// Returns an enumerator that enumerates all s in the . - /// - public IEnumerator GetEnumerator() => difficultyObjects; - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - private IEnumerator createDifficultyObjectEnumerator(List objects, double timeRate) - { - // The first jump is formed by the first two hitobjects of the map. - // If the map has less than two OsuHitObjects, the enumerator will not return anything. - for (int i = 1; i < objects.Count; i++) - yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d8e3b340c9..eacac7ae6a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -1,24 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osuTK; namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { - /// - /// A wrapper around extending it with additional data required for difficulty calculation. - /// - public class OsuDifficultyHitObject + public class OsuDifficultyHitObject : DifficultyHitObject { private const int normalized_radius = 52; - /// - /// The this refers to. - /// - public OsuHitObject BaseObject { get; } + protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; /// /// Normalized distance from the end position of the previous to the start position of this . @@ -31,78 +27,80 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double TravelDistance { get; private set; } /// - /// Milliseconds elapsed since the StartTime of the previous . + /// Angle the player has to take to hit this . + /// Calculated as the angle between the circles (current-2, current-1, current). /// - public double DeltaTime { get; private set; } + public double? Angle { get; private set; } /// /// Milliseconds elapsed since the start time of the previous , with a minimum of 50ms. /// - public double StrainTime { get; private set; } + public readonly double StrainTime; + private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; - private readonly double timeRate; - /// - /// Initializes the object calculating extra data required for difficulty calculation. - /// - public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate) + public OsuDifficultyHitObject(HitObject hitObject, HitObject lastLastObject, HitObject lastObject, double clockRate) + : base(hitObject, lastObject, clockRate) { - this.lastObject = lastObject; - this.timeRate = timeRate; - - BaseObject = currentObject; + this.lastLastObject = (OsuHitObject)lastLastObject; + this.lastObject = (OsuHitObject)lastObject; setDistances(); - setTimingValues(); - // Calculate angle here + + // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure + StrainTime = Math.Max(50, DeltaTime); } private void setDistances() { // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. float scalingFactor = normalized_radius / (float)BaseObject.Radius; + if (BaseObject.Radius < 30) { float smallCircleBonus = Math.Min(30 - (float)BaseObject.Radius, 5) / 50; scalingFactor *= 1 + smallCircleBonus; } - Vector2 lastCursorPosition = lastObject.StackedPosition; - - var lastSlider = lastObject as Slider; - if (lastSlider != null) + if (lastObject is Slider lastSlider) { computeSliderCursorPosition(lastSlider); - lastCursorPosition = lastSlider.LazyEndPosition ?? lastCursorPosition; - TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; } + Vector2 lastCursorPosition = getEndCursorPosition(lastObject); + // Don't need to jump to reach spinners if (!(BaseObject is Spinner)) JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; - } - private void setTimingValues() - { - DeltaTime = (BaseObject.StartTime - lastObject.StartTime) / timeRate; + if (lastLastObject != null) + { + Vector2 lastLastCursorPosition = getEndCursorPosition(lastLastObject); - // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure - StrainTime = Math.Max(50, DeltaTime); + Vector2 v1 = lastLastCursorPosition - lastObject.StackedPosition; + Vector2 v2 = BaseObject.StackedPosition - lastCursorPosition; + + float dot = Vector2.Dot(v1, v2); + float det = v1.X * v2.Y - v1.Y * v2.X; + + Angle = Math.Abs(Math.Atan2(det, dot)); + } } private void computeSliderCursorPosition(Slider slider) { if (slider.LazyEndPosition != null) return; + slider.LazyEndPosition = slider.StackedPosition; float approxFollowCircleRadius = (float)(slider.Radius * 3); var computeVertex = new Action(t => { - double progress = ((int)t - (int)slider.StartTime) / (float)(int)slider.SpanDuration; - if (progress % 2 > 1) + double progress = (t - slider.StartTime) / slider.SpanDuration; + if (progress % 2 >= 1) progress = 1 - progress % 1; else progress = progress % 1; @@ -125,7 +123,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing var scoringTimes = slider.NestedHitObjects.Skip(1).Select(t => t.StartTime); foreach (var time in scoringTimes) computeVertex(time); - computeVertex(slider.EndTime); + } + + private Vector2 getEndCursorPosition(OsuHitObject hitObject) + { + Vector2 pos = hitObject.StackedPosition; + + if (hitObject is Slider slider) + { + computeSliderCursorPosition(slider); + pos = slider.LazyEndPosition ?? pos; + } + + return pos; } } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index f11b6d66f6..e74f4933b2 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -1,8 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -11,10 +14,46 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Aim : Skill { + private const double angle_bonus_begin = Math.PI / 3; + private const double timing_threshold = 107; + protected override double SkillMultiplier => 26.25; protected override double StrainDecayBase => 0.15; - protected override double StrainValueOf(OsuDifficultyHitObject current) - => (Math.Pow(current.TravelDistance, 0.99) + Math.Pow(current.JumpDistance, 0.99)) / current.StrainTime; + protected override double StrainValueOf(DifficultyHitObject current) + { + if (current.BaseObject is Spinner) + return 0; + + var osuCurrent = (OsuDifficultyHitObject)current; + + double result = 0; + + if (Previous.Count > 0) + { + var osuPrevious = (OsuDifficultyHitObject)Previous[0]; + + if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin) + { + const double scale = 90; + + var angleBonus = Math.Sqrt( + Math.Max(osuPrevious.JumpDistance - scale, 0) + * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2) + * Math.Max(osuCurrent.JumpDistance - scale, 0)); + result = 1.5 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime); + } + } + + double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance); + double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); + + return Math.Max( + result + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), + (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime + ); + } + + private double applyDiminishingExp(double val) => Math.Pow(val, 0.99); } } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 1cde03624b..01f2fb8dc8 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -1,7 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; +using osu.Game.Rulesets.Osu.Objects; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -10,30 +14,50 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Speed : Skill { + private const double single_spacing_threshold = 125; + + private const double angle_bonus_begin = 5 * Math.PI / 6; + private const double pi_over_4 = Math.PI / 4; + private const double pi_over_2 = Math.PI / 2; + protected override double SkillMultiplier => 1400; protected override double StrainDecayBase => 0.3; - private const double single_spacing_threshold = 125; - private const double stream_spacing_threshold = 110; - private const double almost_diameter = 90; + private const double min_speed_bonus = 75; // ~200BPM + private const double max_speed_bonus = 45; // ~330BPM + private const double speed_balancing_factor = 40; - protected override double StrainValueOf(OsuDifficultyHitObject current) + protected override double StrainValueOf(DifficultyHitObject current) { - double distance = current.TravelDistance + current.JumpDistance; + if (current.BaseObject is Spinner) + return 0; - double speedValue; - if (distance > single_spacing_threshold) - speedValue = 2.5; - else if (distance > stream_spacing_threshold) - speedValue = 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold); - else if (distance > almost_diameter) - speedValue = 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter); - else if (distance > almost_diameter / 2) - speedValue = 0.95 + 0.25 * (distance - almost_diameter / 2) / (almost_diameter / 2); - else - speedValue = 0.95; + var osuCurrent = (OsuDifficultyHitObject)current; - return speedValue / current.StrainTime; + double distance = Math.Min(single_spacing_threshold, osuCurrent.TravelDistance + osuCurrent.JumpDistance); + double deltaTime = Math.Max(max_speed_bonus, current.DeltaTime); + + double speedBonus = 1.0; + if (deltaTime < min_speed_bonus) + speedBonus = 1 + Math.Pow((min_speed_bonus - deltaTime) / speed_balancing_factor, 2); + + double angleBonus = 1.0; + + if (osuCurrent.Angle != null && osuCurrent.Angle.Value < angle_bonus_begin) + { + angleBonus = 1 + Math.Pow(Math.Sin(1.5 * (angle_bonus_begin - osuCurrent.Angle.Value)), 2) / 3.57; + + if (osuCurrent.Angle.Value < pi_over_2) + { + angleBonus = 1.28; + if (distance < 90 && osuCurrent.Angle.Value < pi_over_4) + angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1); + else if (distance < 90) + angleBonus += (1 - angleBonus) * Math.Min((90 - distance) / 10, 1) * Math.Sin((pi_over_2 - osuCurrent.Angle.Value) / pi_over_4); + } + } + + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / osuCurrent.StrainTime; } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs index de219ed5a5..7f6a60c400 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/Components/HitCirclePiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components PositionBindable.BindValueChanged(_ => UpdatePosition(), true); StackHeightBindable.BindValueChanged(_ => UpdatePosition()); - ScaleBindable.BindValueChanged(v => Scale = new Vector2(v), true); + ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true); } protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index eddd399a4a..a4050f0c31 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { @@ -22,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles { base.LoadComplete(); - // Fixes a 1-frame position discrpancy due to the first mouse move event happening in the next frame - HitObject.Position = GetContainingInputManager().CurrentState.Mouse.Position; + // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame + HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero; } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs index a59dac1834..83787e2219 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCircleSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs index 882a9a3d81..315a5a2b9d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitObjectPiece.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Objects; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index 8431d5d5d0..dd524252f3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -16,7 +15,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints : base(hitObject) { } - - public override void AdjustPosition(DragEvent dragEvent) => OsuObject.Position += dragEvent.Delta; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs index ef7254d9c9..8fd1d6d6f9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/SliderPiece.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 18efe21404..e257369ad9 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components path = new SmoothPath { Anchor = Anchor.Centre, - PathWidth = 1 + PathRadius = 1 }, marker = new CircularContainer { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 0089c2dddd..df846b5d5b 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index 5ea85676b5..957550a051 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Allocation; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChild = body = new ManualSliderBody { AccentColour = Color4.Transparent, - PathWidth = slider.Scale * 64 + PathRadius = slider.Scale * 64 }; } @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components body.BorderColour = colours.Yellow; PositionBindable.BindValueChanged(_ => updatePosition(), true); - ScaleBindable.BindValueChanged(v => body.PathWidth = v * 64, true); + ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * 64, true); } private void updatePosition() => Position = slider.StackedPosition; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs index 205ac6bea3..2ecfea2e3e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderCirclePiece.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; @@ -37,6 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case SliderPosition.Start: Position = slider.StackedPosition + slider.Path.PositionAt(0); break; + case SliderPosition.End: Position = slider.StackedPosition + slider.Path.PositionAt(1); break; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs index 4bac9d3556..c9f005495c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderCircleSelectionBlueprint.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -10,13 +9,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint { - private readonly Slider slider; - public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position) : base(hitObject) { - this.slider = slider; - InternalChild = new SliderCirclePiece(slider, position); Select(); @@ -24,7 +19,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input. public override bool HandlePositionalInput => false; - - public override void AdjustPosition(DragEvent dragEvent) => slider.Position += dragEvent.Delta; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index ba3501cb2b..55de626d7d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -47,6 +47,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders setState(PlacementState.Initial); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame + HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero; + } + protected override bool OnMouseMove(MouseMoveEvent e) { switch (state) @@ -54,6 +62,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: HitObject.Position = e.MousePosition; return true; + case PlacementState.Body: cursor = e.MousePosition - HitObject.Position; return true; @@ -69,6 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case PlacementState.Initial: beginCurve(); break; + case PlacementState.Body: switch (e.Button) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs index a117a7056e..616bb17e05 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPosition.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 2206a36738..fb8c081ff7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs index af648cfa1b..ae94848c81 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/Components/SpinnerPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components PositionBindable.BindValueChanged(_ => updatePosition(), true); StackHeightBindable.BindValueChanged(_ => updatePosition()); - ScaleBindable.BindValueChanged(v => ring.Scale = new Vector2(v), true); + ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true); } private void updatePosition() => Position = spinner.Position; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 804a4fcba0..03d761c67f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Input.Events; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs index 4b98866613..25cef3b251 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerSelectionBlueprint.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -20,10 +19,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos); - - public override void AdjustPosition(DragEvent dragEvent) - { - // Spinners don't support position adjustments - } } } diff --git a/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs new file mode 100644 index 0000000000..bcb6099cfb --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/DrawableOsuEditRuleset.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class DrawableOsuEditRuleset : DrawableOsuRuleset + { + public DrawableOsuEditRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) + { + } + + protected override Playfield CreatePlayfield() => new OsuPlayfieldNoCursor(); + + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer { Size = Vector2.One }; + + private class OsuPlayfieldNoCursor : OsuPlayfield + { + protected override GameplayCursorContainer CreateCursor() => null; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs index ddab70d53a..9c94fe0e3d 100644 --- a/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/HitCircleCompositionTool.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs deleted file mode 100644 index 05bc7dada0..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Cursor; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Edit -{ - public class OsuEditRulesetContainer : OsuRulesetContainer - { - public OsuEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - - protected override CursorContainer CreateCursor() => null; - - protected override Playfield CreatePlayfield() => new OsuPlayfield { Size = Vector2.One }; - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a706e1d4be..c5452ae0aa 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -1,20 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Osu.Edit { @@ -25,8 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit { } - protected override RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - => new OsuEditRulesetContainer(ruleset, beatmap); + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + => new DrawableOsuEditRuleset(ruleset, beatmap, mods); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; - protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both }; + public override SelectionHandler CreateSelectionHandler() => new OsuSelectionHandler(); public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) { @@ -43,8 +42,10 @@ namespace osu.Game.Rulesets.Osu.Edit { case DrawableHitCircle circle: return new HitCircleSelectionBlueprint(circle); + case DrawableSlider slider: return new SliderSelectionBlueprint(slider); + case DrawableSpinner spinner: return new SpinnerSelectionBlueprint(spinner); } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs new file mode 100644 index 0000000000..1ab1219ab0 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuSelectionHandler : SelectionHandler + { + public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) + { + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + h.Position += dragEvent.Delta; + } + + base.HandleDrag(blueprint, dragEvent); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs index 6d4f67c597..a377deb35f 100644 --- a/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SliderCompositionTool.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; diff --git a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs index 5c19a1bac0..0de0af8f8c 100644 --- a/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs +++ b/osu.Game.Rulesets.Osu/Edit/SpinnerCompositionTool.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; diff --git a/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs b/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs index 3000031c78..ad292b0439 100644 --- a/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/ComboResult.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; @@ -9,8 +9,10 @@ namespace osu.Game.Rulesets.Osu.Judgements { [Description(@"")] None, + [Description(@"Good")] Good, + [Description(@"Amazing")] Perfect } diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs index b1c9760866..7a5b98864c 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -16,13 +16,33 @@ namespace osu.Game.Rulesets.Osu.Judgements { default: return 0; + case HitResult.Meh: return 50; + case HitResult.Good: return 100; + case HitResult.Great: return 300; } } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.02; + + case HitResult.Meh: + case HitResult.Good: + case HitResult.Great: + return 0.01; + + default: + return 0; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs index 17b8b4399f..c7661bddb1 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuJudgementResult.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs index d52de9f971..5104d9494b 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 37d5f57fcb..401bd28d7c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -10,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModAutopilot : Mod { public override string Name => "Autopilot"; - public override string ShortenedName => "AP"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot; + public override string Acronym => "AP"; + public override IconUsage Icon => OsuIcon.ModAutopilot; public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 1eb2f59010..bea2bbcb32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -7,7 +7,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Mods { @@ -15,12 +16,10 @@ namespace osu.Game.Rulesets.Osu.Mods { public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).Append(typeof(OsuModSpunOut)).ToArray(); - protected override Score CreateReplayScore(Beatmap beatmap) + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { - return new Score - { - Replay = new OsuAutoGenerator(beatmap).Generate() - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "Autoplay" } }, + Replay = new OsuAutoGenerator(beatmap).Generate() + }; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs new file mode 100644 index 0000000000..445f81c6d4 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -0,0 +1,196 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModBlinds : Mod, IApplicableToDrawableRuleset, IApplicableToScoreProcessor + { + public override string Name => "Blinds"; + public override string Description => "Play with blinds on your screen."; + public override string Acronym => "BL"; + + public override IconUsage Icon => FontAwesome.Solid.Adjust; + public override ModType Type => ModType.DifficultyIncrease; + + public override bool Ranked => false; + + public override double ScoreMultiplier => 1.12; + private DrawableOsuBlinds blinds; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap)); + } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + scoreProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + + /// + /// Element for the Blinds mod drawing 2 black boxes covering the whole screen which resize inside a restricted area with some leniency. + /// + public class DrawableOsuBlinds : Container + { + /// + /// Black background boxes behind blind panel textures. + /// + private Box blackBoxLeft, blackBoxRight; + + private Drawable panelLeft, panelRight, bgPanelLeft, bgPanelRight; + + private readonly Beatmap beatmap; + + /// + /// Value between 0 and 1 setting a maximum "closedness" for the blinds. + /// Useful for animating how far the blinds can be opened while keeping them at the original position if they are wider open than this. + /// + private const float target_clamp = 1; + + private readonly float targetBreakMultiplier = 0; + private readonly float easing = 1; + + private readonly CompositeDrawable restrictTo; + + /// + /// + /// Percentage of playfield to extend blinds over. Basically moves the origin points where the blinds start. + /// + /// + /// -1 would mean the blinds always cover the whole screen no matter health. + /// 0 would mean the blinds will only ever be on the edge of the playfield on 0% health. + /// 1 would mean the blinds are fully outside the playfield on 50% health. + /// Infinity would mean the blinds are always outside the playfield except on 100% health. + /// + /// + private const float leniency = 0.1f; + + public DrawableOsuBlinds(CompositeDrawable restrictTo, Beatmap beatmap) + { + this.restrictTo = restrictTo; + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + Children = new[] + { + blackBoxLeft = new Box + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Y, + }, + blackBoxRight = new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = Color4.Black, + RelativeSizeAxes = Axes.Y, + }, + bgPanelLeft = new ModBlindsPanel + { + Origin = Anchor.TopRight, + Colour = Color4.Gray, + }, + panelLeft = new ModBlindsPanel { Origin = Anchor.TopRight, }, + bgPanelRight = new ModBlindsPanel { Colour = Color4.Gray }, + panelRight = new ModBlindsPanel() + }; + } + + private float calculateGap(float value) => MathHelper.Clamp(value, 0, target_clamp) * targetBreakMultiplier; + + // lagrange polinominal for (0,0) (0.6,0.4) (1,1) should make a good curve + private static float applyAdjustmentCurve(float value) => 0.6f * value * value + 0.4f * value; + + protected override void Update() + { + float start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X; + float end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X; + + float rawWidth = end - start; + + start -= rawWidth * leniency * 0.5f; + end += rawWidth * leniency * 0.5f; + + float width = (end - start) * 0.5f * applyAdjustmentCurve(calculateGap(easing)); + + // different values in case the playfield ever moves from center to somewhere else. + blackBoxLeft.Width = start + width; + blackBoxRight.Width = DrawWidth - end + width; + + panelLeft.X = start + width; + panelRight.X = end - width; + bgPanelLeft.X = start; + bgPanelRight.X = end; + } + + protected override void LoadComplete() + { + const float break_open_early = 500; + const float break_close_late = 250; + + base.LoadComplete(); + + var firstObj = beatmap.HitObjects[0]; + var startDelay = firstObj.StartTime - firstObj.TimePreempt; + + using (BeginAbsoluteSequence(startDelay + break_close_late, true)) + leaveBreak(); + + foreach (var breakInfo in beatmap.Breaks) + { + if (breakInfo.HasEffect) + { + using (BeginAbsoluteSequence(breakInfo.StartTime - break_open_early, true)) + { + enterBreak(); + using (BeginDelayedSequence(breakInfo.Duration + break_open_early + break_close_late, true)) + leaveBreak(); + } + } + } + } + + private void enterBreak() => this.TransformTo(nameof(targetBreakMultiplier), 0f, 1000, Easing.OutSine); + + private void leaveBreak() => this.TransformTo(nameof(targetBreakMultiplier), 1f, 2500, Easing.OutBounce); + + /// + /// 0 is open, 1 is closed. + /// + public void AnimateClosedness(float value) => this.TransformTo(nameof(easing), value, 200, Easing.OutQuint); + + public class ModBlindsPanel : Sprite + { + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get("Play/osu/blinds-panel"); + } + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs index ff3f6ea7cf..371dfe6a1a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs index 45b538d506..933c6cde1c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs index 465775b175..f13c7d2ff6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 84a35b04fe..7fa3dbe07e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -1,22 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.MathUtils; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModFlashlight : ModFlashlight + public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObjects { public override double ScoreMultiplier => 1.12; private const float default_flashlight_size = 180; - public override Flashlight CreateFlashlight() => new OsuFlashlight(); + private OsuFlashlight flashlight; + + public override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(); + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var s in drawables.OfType()) + { + s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + } + } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition { @@ -25,9 +41,22 @@ namespace osu.Game.Rulesets.Osu.Mods FlashlightSize = new Vector2(0, getSizeFor(0)); } + public void OnSliderTrackingChange(ValueChangedEvent e) + { + // If a slider is in a tracking state, a further dim should be applied to the (remaining) visible portion of the playfield over a brief duration. + this.TransformTo(nameof(FlashlightDim), e.NewValue ? 0.8f : 0.0f, 50); + } + protected override bool OnMouseMove(MouseMoveEvent e) { - FlashlightPosition = e.MousePosition; + const double follow_delay = 120; + + var position = FlashlightPosition; + var destination = e.MousePosition; + + FlashlightPosition = Interpolation.ValueAt( + MathHelper.Clamp(Clock.ElapsedFrameTime, 0, follow_delay), position, destination, 0, follow_delay, Easing.Out); + return base.OnMouseMove(e); } @@ -41,9 +70,9 @@ namespace osu.Game.Rulesets.Osu.Mods return default_flashlight_size; } - protected override void OnComboChange(int newCombo) + protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs new file mode 100644 index 0000000000..8072dc09c1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -0,0 +1,86 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModGrow : Mod, IReadFromConfig, IApplicableToDrawableHitObjects + { + public override string Name => "Grow"; + + public override string Acronym => "GR"; + + public override IconUsage Icon => FontAwesome.Solid.ArrowsAltV; + + public override ModType Type => ModType.Fun; + + public override string Description => "Hit them at the right size!"; + + public override double ScoreMultiplier => 1; + + private Bindable increaseFirstObjectVisibility = new Bindable(); + + public void ReadFromConfig(OsuConfigManager config) + { + increaseFirstObjectVisibility = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility); + } + + public void ApplyToDrawableHitObjects(IEnumerable drawables) + { + foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0)) + { + switch (drawable) + { + case DrawableSpinner _: + continue; + + default: + drawable.ApplyCustomUpdateState += ApplyCustomState; + break; + } + } + } + + protected virtual void ApplyCustomState(DrawableHitObject drawable, ArmedState state) + { + var h = (OsuHitObject)drawable.HitObject; + + // apply grow effect + switch (drawable) + { + case DrawableSliderHead _: + case DrawableSliderTail _: + // special cases we should *not* be scaling. + break; + + case DrawableSlider _: + case DrawableHitCircle _: + { + using (drawable.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + drawable.ScaleTo(0.5f).Then().ScaleTo(1, h.TimePreempt, Easing.OutSine); + break; + } + } + + // remove approach circles + switch (drawable) + { + case DrawableHitCircle circle: + // we don't want to see the approach circle + using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true)) + circle.ApproachCircle.Hide(); + break; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs index dc3ba592fb..4769e7660b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 32d2d40671..80686b7983 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; @@ -26,9 +26,6 @@ namespace osu.Game.Rulesets.Osu.Mods if (slider == null) return; - slider.HeadCircle.Position = new Vector2(slider.HeadCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.HeadCircle.Position.Y); - slider.TailCircle.Position = new Vector2(slider.TailCircle.Position.X, OsuPlayfield.BASE_SIZE.Y - slider.TailCircle.Position.Y); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 4eff2a55c8..ddf708d0f1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -59,11 +59,13 @@ namespace osu.Game.Rulesets.Osu.Mods circle.FadeOut(fadeOutDuration); break; + case DrawableSlider slider: using (slider.BeginAbsoluteSequence(fadeOutStartTime, true)) slider.Body.FadeOut(longFadeDuration, Easing.Out); break; + case DrawableSliderTick sliderTick: // slider ticks fade out over up to one second var tickFadeOutDuration = Math.Min(sliderTick.HitObject.TimePreempt - DrawableSliderTick.ANIM_DURATION, 1000); @@ -72,6 +74,7 @@ namespace osu.Game.Rulesets.Osu.Mods sliderTick.FadeOut(tickFadeOutDuration); break; + case DrawableSpinner spinner: // hide elements we don't care about. spinner.Disc.Hide(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs index 9c1b2d8a35..5668c17792 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs index a51cb9d888..9f707a5aa6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index 5169c8186b..da462eb6e8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 8d27502b3c..bc5d02258f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -13,7 +13,7 @@ using static osu.Game.Input.Handlers.ReplayInputHandler; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToRulesetContainer + public class OsuModRelax : ModRelax, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray(); @@ -37,11 +37,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (time < osuHit.HitObject.StartTime - relax_leniency) continue; - if (osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime || osuHit.IsHit) + if ((osuHit.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime) || osuHit.IsHit) continue; requiresHit |= osuHit is DrawableHitCircle && osuHit.IsHovered && osuHit.HitObject.HitWindows.CanBeHit(relativetime); - requiresHold |= osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered) || osuHit is DrawableSpinner; + requiresHold |= (osuHit is DrawableSlider slider && (slider.Ball.IsHovered || osuHit.IsHovered)) || osuHit is DrawableSpinner; } if (requiresHit) @@ -79,10 +79,10 @@ namespace osu.Game.Rulesets.Osu.Mods state.Apply(osuInputManager.CurrentState, osuInputManager); } - public void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { // grab the input manager for future use. - osuInputManager = (OsuInputManager)rulesetContainer.KeyBindingInputManager; + osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager; osuInputManager.AllowUserPresses = false; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 6aa864d9b2..1cdcddbd33 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -10,8 +11,8 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModSpunOut : Mod { public override string Name => "Spun Out"; - public override string ShortenedName => "SO"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_spunout; + public override string Acronym => "SO"; + public override IconUsage Icon => OsuIcon.ModSpunout; public override ModType Type => ModType.DifficultyReduction; public override string Description => @"Spinners will be automatically completed."; public override double ScoreMultiplier => 0.9; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index fdd5099ad6..0403e81229 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 139ce4cc4b..8360e2692e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -9,9 +10,9 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModTarget : Mod { public override string Name => "Target"; - public override string ShortenedName => "TP"; + public override string Acronym => "TP"; public override ModType Type => ModType.Conversion; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_target; + public override IconUsage Icon => OsuIcon.ModTarget; public override string Description => @"Practice keeping up with the beat of the song."; public override double ScoreMultiplier => 1; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs new file mode 100644 index 0000000000..571756d056 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModTouchDevice : Mod + { + public override string Name => "Touch Device"; + public override string Acronym => "TD"; + public override double ScoreMultiplier => 1; + + public override bool Ranked => true; + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index dcd1896601..9b079895fa 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Mods internal class OsuModTransform : Mod, IApplicableToDrawableHitObjects { public override string Name => "Transform"; - public override string ShortenedName => "TR"; - public override FontAwesome Icon => FontAwesome.fa_arrows; + public override string Acronym => "TR"; + public override IconUsage Icon => FontAwesome.Solid.ArrowsAlt; public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods { foreach (var drawable in drawables) { - var hitObject = (OsuHitObject) drawable.HitObject; + var hitObject = (OsuHitObject)drawable.HitObject; float appearDistance = (float)(hitObject.TimePreempt - hitObject.TimeFadeIn) / 2; @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Mods .MoveTo(originalPosition, moveDuration, Easing.InOutSine); } - theta += (float) hitObject.TimeFadeIn / 1000; + theta += (float)hitObject.TimeFadeIn / 1000; } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 93f1070231..17fcd03dd5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -16,8 +16,8 @@ namespace osu.Game.Rulesets.Osu.Mods internal class OsuModWiggle : Mod, IApplicableToDrawableHitObjects { public override string Name => "Wiggle"; - public override string ShortenedName => "WG"; - public override FontAwesome Icon => FontAwesome.fa_certificate; + public override string Acronym => "WG"; + public override IconUsage Icon => FontAwesome.Solid.Certificate; public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs index f15be94b8a..9106f4c7bd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Connects hit objects visually, for example with follow points. /// - public abstract class ConnectionRenderer : Container + public abstract class ConnectionRenderer : LifetimeManagementContainer where T : HitObject { /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index bd329266aa..aacf3ee08d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 61219d9bb9..7569626230 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -12,39 +12,44 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public class FollowPointRenderer : ConnectionRenderer { private int pointDistance = 32; + /// /// Determines how much space there is between points. /// public int PointDistance { - get { return pointDistance; } + get => pointDistance; set { if (pointDistance == value) return; + pointDistance = value; update(); } } private int preEmpt = 800; + /// /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time. /// public int PreEmpt { - get { return preEmpt; } + get => preEmpt; set { if (preEmpt == value) return; + preEmpt = value; update(); } } private IEnumerable hitObjects; + public override IEnumerable HitObjects { - get { return hitObjects; } + get => hitObjects; set { hitObjects = value; @@ -56,12 +61,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void update() { - Clear(); + ClearInternal(); if (hitObjects == null) return; OsuHitObject prevHitObject = null; + foreach (var currHitObject in hitObjects) { if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) @@ -86,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPoint fp; - Add(fp = new FollowPoint + AddInternal(fp = new FollowPoint { Position = pointStartPosition, Rotation = rotation, @@ -107,6 +113,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections fp.Expire(true); } } + prevHitObject = currHitObject; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 9b562745fa..fef0bfdc2c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osuTK; @@ -27,40 +28,60 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable stackHeightBindable = new Bindable(); private readonly IBindable scaleBindable = new Bindable(); + public OsuAction? HitAction => circle.HitAction; + + private readonly Container explodeContainer; + + private readonly Container scaleContainer; + public DrawableHitCircle(HitCircle h) : base(h) { Origin = Anchor.Centre; Position = HitObject.StackedPosition; - Scale = new Vector2(h.Scale); InternalChildren = new Drawable[] { - glow = new GlowPiece(), - circle = new CirclePiece + scaleContainer = new Container { - Hit = () => + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = explodeContainer = new Container { - if (AllJudged) - return false; + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + glow = new GlowPiece(), + circle = new CirclePiece + { + Hit = () => + { + if (AllJudged) + return false; - UpdateResult(true); - return true; - }, + UpdateResult(true); + return true; + }, + }, + number = new NumberPiece + { + Text = (HitObject.IndexInCurrentCombo + 1).ToString(), + }, + ring = new RingPiece(), + flash = new FlashPiece(), + explode = new ExplodePiece(), + ApproachCircle = new ApproachCircle + { + Alpha = 0, + Scale = new Vector2(4), + } + } + } }, - number = new NumberPiece - { - Text = (HitObject.IndexInCurrentCombo + 1).ToString(), - }, - ring = new RingPiece(), - flash = new FlashPiece(), - explode = new ExplodePiece(), - ApproachCircle = new ApproachCircle - { - Alpha = 0, - Scale = new Vector2(4), - } }; //may not be so correct @@ -72,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - scaleBindable.BindValueChanged(v => Scale = new Vector2(v)); + scaleBindable.BindValueChanged(scale => scaleContainer.Scale = new Vector2(scale.NewValue), true); positionBindable.BindTo(HitObject.PositionBindable); stackHeightBindable.BindTo(HitObject.StackHeightBindable); @@ -81,7 +102,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override Color4 AccentColour { - get { return base.AccentColour; } + get => base.AccentColour; set { base.AccentColour = value; @@ -103,6 +124,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } var result = HitObject.HitWindows.ResultFor(timeOffset); + if (result == HitResult.None) { Shake(Math.Abs(timeOffset) - HitObject.HitWindows.HalfWindowFor(HitResult.Miss)); @@ -118,6 +140,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt)); ApproachCircle.ScaleTo(1.1f, HitObject.TimePreempt); + ApproachCircle.Expire(true); } protected override void UpdateCurrentState(ArmedState state) @@ -131,14 +154,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Expire(true); + circle.HitAction = null; + // override lifetime end as FadeIn may have been changed externally, causing out expiration to be too early. LifetimeEnd = HitObject.StartTime + HitObject.HitWindows.HalfWindowFor(HitResult.Miss); break; + case ArmedState.Miss: ApproachCircle.FadeOut(50); this.FadeOut(100); Expire(); break; + case ArmedState.Hit: ApproachCircle.FadeOut(50); @@ -156,8 +183,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.FadeOut(); number.FadeOut(); - this.FadeOut(800) - .ScaleTo(Scale * 1.5f, 400, Easing.OutQuad); + this.FadeOut(800); + explodeContainer.ScaleTo(1.5f, 400, Easing.OutQuad); } Expire(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs index 1081f185ad..f372cb65ce 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Rulesets.Objects.Drawables; @@ -16,14 +16,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableOsuHitObject : DrawableHitObject { - public override bool IsPresent => base.IsPresent || State.Value == ArmedState.Idle && Clock?.CurrentTime >= HitObject.StartTime - HitObject.TimePreempt; - private readonly ShakeContainer shakeContainer; + // Must be set to update IsHovered as it's used in relax mdo to detect osu hit objects. + public override bool HandlePositionalInput => true; + protected DrawableOsuHitObject(OsuHitObject hitObject) : base(hitObject) { - base.AddInternal(shakeContainer = new ShakeContainer { RelativeSizeAxes = Axes.Both }); + base.AddInternal(shakeContainer = new ShakeContainer + { + ShakeDuration = 30, + RelativeSizeAxes = Axes.Both + }); Alpha = 0; } @@ -56,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables base.SkinChanged(skin, allowFallback); if (HitObject is IHasComboInformation combo) - AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; + AccentColour = skin.GetValue(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; } protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadeIn); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 17f6ddd211..938a2293ba 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -1,11 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osuTK; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -16,12 +15,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } - protected override void LoadComplete() + protected override void ApplyHitAnimations() { - if (Result.Type != HitResult.Miss) - JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); - - base.LoadComplete(); + JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + base.ApplyHitAnimations(); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index e0da154322..29a84b53a9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; using osu.Game.Rulesets.Objects.Drawables; using osuTK; -using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables new SkinnableDrawable("Play/osu/reversearrow", _ => new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_chevron_right + Icon = FontAwesome.Solid.ChevronRight }, restrictSize: false) }; } @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void CheckForResult(bool userTriggered, double timeOffset) { if (repeatPoint.StartTime <= Time.Current) - ApplyResult(r => r.Type = drawableSlider.Tracking ? HitResult.Great : HitResult.Miss); + ApplyResult(r => r.Type = drawableSlider.Tracking.Value ? HitResult.Great : HitResult.Miss); } protected override void UpdatePreemptState() @@ -66,9 +66,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(animDuration); break; + case ArmedState.Hit: this.FadeOut(animDuration, Easing.OutQuint) .ScaleTo(Scale * 1.5f, animDuration, Easing.Out); @@ -103,7 +105,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables break; } - float aimRotation = MathHelper.RadiansToDegrees((float)Math.Atan2(aimRotationVector.Y - Position.Y, aimRotationVector.X - Position.X)); while (Math.Abs(aimRotation - Rotation) > 180) aimRotation += aimRotation < Rotation ? 360 : -360; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index eed9a53ad7..05cb42d853 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -8,12 +8,13 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; -using osu.Game.Configuration; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Scoring; using osuTK.Graphics; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables { @@ -32,6 +33,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly IBindable scaleBindable = new Bindable(); private readonly IBindable pathBindable = new Bindable(); + [Resolved(CanBeNull = true)] + private OsuRulesetConfigManager config { get; set; } + public DrawableSlider(Slider s) : base(s) { @@ -46,12 +50,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { Body = new SnakingSliderBody(s) { - PathWidth = s.Scale * 64, + PathRadius = s.Scale * 64, }, ticks = new Container { RelativeSizeAxes = Axes.Both }, repeatPoints = new Container { RelativeSizeAxes = Axes.Both }, Ball = new SliderBall(s, this) { + GetInitialHitAction = () => HeadCircle.HitAction, BypassAutoSizeAxes = Axes.Both, Scale = new Vector2(s.Scale), AlwaysPresent = true, @@ -92,16 +97,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { - config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); - config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); + config?.BindWith(OsuRulesetSetting.SnakingInSliders, Body.SnakingIn); + config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut); positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition); - scaleBindable.BindValueChanged(v => + scaleBindable.BindValueChanged(scale => { - Body.PathWidth = HitObject.Scale * 64; - Ball.Scale = new Vector2(HitObject.Scale); + Body.PathRadius = scale.NewValue * 64; + Ball.Scale = new Vector2(scale.NewValue); }); positionBindable.BindTo(HitObject.PositionBindable); @@ -113,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override Color4 AccentColour { - get { return base.AccentColour; } + get => base.AccentColour; set { base.AccentColour = value; @@ -125,13 +130,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public bool Tracking; + public readonly Bindable Tracking = new Bindable(); protected override void Update() { base.Update(); - Tracking = Ball.Tracking; + Tracking.Value = Ball.Tracking; double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); @@ -151,6 +156,16 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + + Body.BorderSize = skin.GetValue(s => s.SliderBorderSize) ?? SliderBody.DEFAULT_BORDER_SIZE; + Body.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderTrackOverride") ? s.CustomColours["SliderTrackOverride"] : (Color4?)null) ?? AccentColour; + Body.BorderColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBorder") ? s.CustomColours["SliderBorder"] : (Color4?)null) ?? Color4.White; + Ball.AccentColour = skin.GetValue(s => s.CustomColours.ContainsKey("SliderBall") ? s.CustomColours["SliderBall"] : (Color4?)null) ?? AccentColour; + } + protected override void CheckForResult(bool userTriggered, double timeOffset) { if (userTriggered || Time.Current < slider.EndTime) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs index f4962f6113..66b6f0f9ac 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderHead.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs index 4ff05d8995..23c5494cf5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 531e6a37af..72b648bfd0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; @@ -20,7 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public override bool DisplayResult => false; - public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick) + public DrawableSliderTick(SliderTick sliderTick) + : base(sliderTick) { Size = new Vector2(16) * sliderTick.Scale; Origin = Anchor.Centre; @@ -66,10 +67,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables case ArmedState.Idle: this.Delay(HitObject.TimePreempt).FadeOut(); break; + case ArmedState.Miss: this.FadeOut(ANIM_DURATION); this.FadeColour(Color4.Red, ANIM_DURATION / 2); break; + case ArmedState.Hit: this.FadeOut(ANIM_DURATION, Easing.OutQuint); this.ScaleTo(Scale * 1.5f, ANIM_DURATION, Easing.Out); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index d0d9479ed1..1794da54b7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Graphics; @@ -11,7 +11,8 @@ using osuTK.Graphics; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; using osu.Game.Screens.Ranking; using osu.Game.Rulesets.Scoring; @@ -42,7 +43,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private Color4 normalColour; private Color4 completeColour; - public DrawableSpinner(Spinner s) : base(s) + public DrawableSpinner(Spinner s) + : base(s) { Origin = Anchor.Centre; Position = s.Position; @@ -75,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(48), - Icon = FontAwesome.fa_asterisk, + Icon = FontAwesome.Solid.Asterisk, Shadow = false, }, } @@ -130,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Colour = colours.BlueDark; glow.Colour = colours.BlueDark; - positionBindable.BindValueChanged(v => Position = v); + positionBindable.BindValueChanged(pos => Position = pos.NewValue); positionBindable.BindTo(HitObject.PositionBindable); } @@ -217,9 +219,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables switch (state) { + case ArmedState.Idle: + Expire(true); + break; + case ArmedState.Hit: sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out); break; + case ArmedState.Miss: sequence.ScaleTo(Scale * 0.8f, 320, Easing.In); break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs index e89ec1775b..b1815b23c9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Osu.Objects.Drawables { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs index 23903e1b5e..cae2a7c36d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/ITrackSnaking.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs index 07d99bda42..9981585f9e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ApproachCircle.cs @@ -1,10 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Skinning; @@ -12,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public class ApproachCircle : Container { + public override bool RemoveWhenNotAlive => false; + public ApproachCircle() { Anchor = Anchor.Centre; @@ -23,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces [BackgroundDependencyLoader] private void load(TextureStore textures) { - Child = new SkinnableDrawable("Play/osu/approachcircle", name => new Sprite { Texture = textures.Get(name) }); + Child = new SkinnableSprite("Play/osu/approachcircle"); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs index 1a9acf708a..786cac7198 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/CirclePiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; @@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public Func Hit; + public OsuAction? HitAction; + public CirclePiece() { Size = new Vector2((float)OsuHitObject.OBJECT_RADIUS * 2); @@ -35,7 +37,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { case OsuAction.LeftButton: case OsuAction.RightButton: - return IsHovered && (Hit?.Invoke() ?? false); + if (IsHovered && (Hit?.Invoke() ?? false)) + { + HitAction = action; + return true; + } + + break; } return false; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs index 86b60c3443..047ff943ff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/DefaultCirclePiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs index 2a2c845143..b960f40578 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ExplodePiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs index 8880c96e3c..8e5eb886aa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/FlashPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs index 48b78bac40..917695c790 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/GlowPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs index 6f4ccd60a9..d69df1d5c2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ManualSliderBody.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs index 87ca2f05e1..84034d3ee9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/NumberPiece.cs @@ -1,12 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Sprites; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -17,8 +19,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public string Text { - get { return number.Text; } - set { number.Text = value; } + get => number.Text; + set => number.Text = value; } public NumberPiece() @@ -42,9 +44,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }, s => s.GetTexture("Play/osu/hitcircle") == null), number = new SkinnableSpriteText("Play/osu/number-text", _ => new OsuSpriteText { - Font = @"Venera", + Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, - TextSize = 40, }, restrictSize: false) { Text = @"1" diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs index 75b34959bd..28180a7f71 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/RingPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 5144ce4e7a..7d1d77ae96 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -1,10 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -13,18 +15,20 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { - public class SliderBall : CircularContainer, ISliderProgress + public class SliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition { private const float width = 128; private Color4 accentColour = Color4.Black; + public Func GetInitialHitAction; + /// /// The colour that is used for the slider ball. /// public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; @@ -104,18 +108,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private Vector2? lastScreenSpaceMousePosition; - protected override bool OnMouseDown(MouseDownEvent e) - { - lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; - return base.OnMouseUp(e); - } - protected override bool OnMouseMove(MouseMoveEvent e) { lastScreenSpaceMousePosition = e.ScreenSpaceMousePosition; @@ -129,15 +121,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces base.ClearTransformsAfter(time, false, targetMember); } + public override void ApplyTransformsAt(double time, bool propagateChildren = false) + { + // For the same reasons as above w.r.t rewinding, we shouldn't propagate to children here either. + base.ApplyTransformsAt(time, false); + } + private bool tracking; public bool Tracking { - get { return tracking; } + get => tracking; private set { if (value == tracking) return; + tracking = value; FollowCircle.ScaleTo(tracking ? 2f : 1, 300, Easing.OutQuint); @@ -145,20 +144,72 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - private bool canCurrentlyTrack => Time.Current >= slider.StartTime && Time.Current < slider.EndTime; + /// + /// If the cursor moves out of the ball's radius we still need to be able to receive positional updates to stop tracking. + /// + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + /// + /// The point in time after which we can accept any key for tracking. Before this time, we may need to restrict tracking to the key used to hit the head circle. + /// + /// This is a requirement to stop the case where a player holds down one key (from before the slider) and taps the second key while maintaining full scoring (tracking) of sliders. + /// Visually, this special case can be seen below (time increasing from left to right): + /// + /// Z Z+X Z + /// o========o + /// + /// Without this logic, tracking would continue through the entire slider even though no key hold action is directly attributing to it. + /// + /// In all other cases, no special handling is required (either key being pressed is allowable as valid tracking). + /// + /// The reason for storing this as a time value (rather than a bool) is to correctly handle rewind scenarios. + /// + private double? timeToAcceptAnyKeyAfter; protected override void Update() { base.Update(); - if (Time.Current < slider.EndTime) + // from the point at which the head circle is hit, this will be non-null. + // it may be null if the head circle was missed. + var headCircleHitAction = GetInitialHitAction(); + + if (headCircleHitAction == null) + timeToAcceptAnyKeyAfter = null; + + var actions = drawableSlider?.OsuActionInputManager?.PressedActions; + + // if the head circle was hit with a specific key, tracking should only occur while that key is pressed. + if (headCircleHitAction != null && timeToAcceptAnyKeyAfter == null) { - // Make sure to use the base version of ReceivePositionalInputAt so that we correctly check the position. - Tracking = canCurrentlyTrack - && lastScreenSpaceMousePosition.HasValue - && ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) - && (drawableSlider?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false); + var otherKey = headCircleHitAction == OsuAction.RightButton ? OsuAction.LeftButton : OsuAction.RightButton; + + // we can return to accepting all keys if the initial head circle key is the *only* key pressed, or all keys have been released. + if (actions?.Contains(otherKey) != true) + timeToAcceptAnyKeyAfter = Time.Current; } + + Tracking = + // in valid time range + Time.Current >= slider.StartTime && Time.Current < slider.EndTime && + // in valid position range + lastScreenSpaceMousePosition.HasValue && base.ReceivePositionalInputAt(lastScreenSpaceMousePosition.Value) && + // valid action + (actions?.Any(isValidTrackingAction) ?? false); + } + + /// + /// Check whether a given user input is a valid tracking action. + /// + private bool isValidTrackingAction(OsuAction action) + { + bool headCircleHit = GetInitialHitAction().HasValue; + + // if the head circle was hit, we may not yet be allowed to accept any key, so we must use the initial hit action. + if (headCircleHit && (!timeToAcceptAnyKeyAfter.HasValue || Time.Current <= timeToAcceptAnyKeyAfter.Value)) + return action == GetInitialHitAction(); + + return action == OsuAction.LeftButton || action == OsuAction.RightButton; } public void UpdateProgress(double completionProgress) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 4604a00fdd..33b3667c4f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -1,28 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; -using osu.Framework.Graphics.Primitives; using osuTK; using osuTK.Graphics; -using osuTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { public abstract class SliderBody : CompositeDrawable { + public const float DEFAULT_BORDER_SIZE = 1; + private readonly SliderPath path; protected Path Path => path; - private readonly BufferedContainer container; - - public float PathWidth + public float PathRadius { - get => path.PathWidth; - set => path.PathWidth = value; + get => path.PathRadius; + set => path.PathRadius = value; } /// @@ -40,9 +37,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { if (path.AccentColour == value) return; - path.AccentColour = value; - container.ForceRedraw(); + path.AccentColour = value; } } @@ -56,24 +52,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { if (path.BorderColour == value) return; - path.BorderColour = value; - container.ForceRedraw(); + path.BorderColour = value; } } - public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; + /// + /// Used to size the path border. + /// + public float BorderSize + { + get => path.BorderSize; + set + { + if (path.BorderSize == value) + return; + + path.BorderSize = value; + } + } protected SliderBody() { - InternalChild = container = new BufferedContainer - { - RelativeSizeAxes = Axes.Both, - CacheDrawnFrameBuffer = true, - Child = path = new SliderPath { Blending = BlendingMode.None } - }; - - container.Attach(RenderbufferInternalFormat.DepthComponent16); + InternalChild = path = new SliderPath(); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => path.ReceivePositionalInputAt(screenSpacePos); @@ -82,14 +83,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// Sets the vertices of the path which should be drawn by this . /// /// The vertices - protected void SetVertices(IReadOnlyList vertices) - { - path.Vertices = vertices; - container.ForceRedraw(); - } + protected void SetVertices(IReadOnlyList vertices) => path.Vertices = vertices; private class SliderPath : SmoothPath { + private const float border_max_size = 8f; + private const float border_min_size = 0f; + private const float border_portion = 0.128f; private const float gradient_portion = 1 - border_portion; @@ -105,6 +105,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { if (borderColour == value) return; + borderColour = value; InvalidateTexture(); @@ -120,18 +121,40 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces { if (accentColour == value) return; + accentColour = value; InvalidateTexture(); } } + private float borderSize = DEFAULT_BORDER_SIZE; + + public float BorderSize + { + get => borderSize; + set + { + if (borderSize == value) + return; + + if (value < border_min_size || value > border_max_size) + return; + + borderSize = value; + + InvalidateTexture(); + } + } + + private float calculatedBorderPortion => BorderSize * border_portion; + protected override Color4 ColourAt(float position) { - if (position <= border_portion) + if (calculatedBorderPortion != 0f && position <= calculatedBorderPortion) return BorderColour; - position -= border_portion; + position -= calculatedBorderPortion; return new Color4(AccentColour.R, AccentColour.G, AccentColour.B, (opacity_at_edge - (opacity_at_edge - opacity_at_centre) * position / gradient_portion) * AccentColour.A); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs index 49c66c4063..73b184bffe 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SnakingSliderBody.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -54,18 +54,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces var spanProgress = slider.ProgressAt(completionProgress); double start = 0; - double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1; + double end = SnakingIn.Value ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadeIn, 0, 1) : 1; if (span >= slider.SpanCount() - 1) { if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) { start = 0; - end = SnakingOut ? spanProgress : 1; + end = SnakingOut.Value ? spanProgress : 1; } else { - start = SnakingOut ? spanProgress : 0; + start = SnakingOut.Value ? spanProgress : 0; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs index e2d67f16a9..77228e28af 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerBackground.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; @@ -15,10 +16,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public Color4 AccentColour { - get - { - return Disc.Colour; - } + get => Disc.Colour; set { Disc.Colour = value; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 59400a71ea..448a2eada7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; @@ -17,8 +17,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public Color4 AccentColour { - get { return background.AccentColour; } - set { background.AccentColour = value; } + get => background.AccentColour; + set => background.AccentColour = value; } private readonly SpinnerBackground background; @@ -43,12 +43,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; private bool tracking; + public bool Tracking { - get { return tracking; } + get => tracking; set { if (value == tracking) return; + tracking = value; background.FadeTo(tracking ? tracking_alpha : idle_alpha, 100); @@ -56,12 +58,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } private bool complete; + public bool Complete { - get { return complete; } + get => complete; set { if (value == complete) return; + complete = value; updateCompleteTick(); diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs index 0bdd4296fb..b1d90c49f6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerSpmCounter.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces @@ -23,16 +24,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = @"0", - Font = @"Venera", - TextSize = 24 + Font = OsuFont.Numeric.With(size: 24) }, new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = @"SPINS PER MINUTE", - Font = @"Venera", - TextSize = 12, + Font = OsuFont.Numeric.With(size: 12), Y = 30 } }; @@ -42,10 +41,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public double SpinsPerMinute { - get { return spm; } + get => spm; private set { if (value == spm) return; + spm = value; spmText.Text = Math.Truncate(value).ToString(@"#0"); } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs index cc9bb1975d..9219fab830 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerTicks.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs index 59b204bdaf..0e29a1dcd8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/TrianglesPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics.Backgrounds; diff --git a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs index d1656a9672..d652db0fd4 100644 --- a/osu.Game.Rulesets.Osu/Objects/HitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/HitCircle.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.Judgements; diff --git a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs index 43ec7ae006..7594f7c2e0 100644 --- a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs +++ b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 462df43b65..364c182dd4 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osuTK; @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects public virtual Vector2 Position { - get => PositionBindable; + get => PositionBindable.Value; set => PositionBindable.Value = value; } @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Objects public int StackHeight { - get => StackHeightBindable; + get => StackHeightBindable.Value; set => StackHeightBindable.Value = value; } @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Objects public float Scale { - get => ScaleBindable; + get => ScaleBindable.Value; set => ScaleBindable.Value = value; } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs index 8405498554..add8fd53c7 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitWindows.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs index c8621cdbcf..63713541b4 100644 --- a/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/RepeatPoint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index d508ec2636..a4638c31f2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; -using osu.Framework.Configuration; +using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -26,8 +26,11 @@ namespace osu.Game.Rulesets.Osu.Objects public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double Duration => EndTime - StartTime; + private Cached endPositionCache; + + public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); + public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); - public override Vector2 EndPosition => Position + this.CurvePositionAt(1); public override int ComboIndex { @@ -56,7 +59,11 @@ namespace osu.Game.Rulesets.Osu.Objects public SliderPath Path { get => PathBindable.Value; - set => PathBindable.Value = value; + set + { + PathBindable.Value = value; + endPositionCache.Invalidate(); + } } public double Distance => Path.Distance; @@ -73,6 +80,8 @@ namespace osu.Game.Rulesets.Osu.Objects if (TailCircle != null) TailCircle.Position = EndPosition; + + endPositionCache.Invalidate(); } } @@ -90,9 +99,19 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; - public List> NodeSamples { get; set; } = new List>(); + public List> NodeSamples { get; set; } = new List>(); - public int RepeatCount { get; set; } + private int repeatCount; + + public int RepeatCount + { + get => repeatCount; + set + { + repeatCount = value; + endPositionCache.Invalidate(); + } + } /// /// The length of one span of this . @@ -135,111 +154,79 @@ namespace osu.Game.Rulesets.Osu.Objects { base.CreateNestedHitObjects(); - createSliderEnds(); - createTicks(); - createRepeatPoints(); - - if (LegacyLastTickOffset != null) - TailCircle.StartTime = Math.Max(StartTime + Duration / 2, TailCircle.StartTime - LegacyLastTickOffset.Value); - } - - private void createSliderEnds() - { - HeadCircle = new SliderCircle + foreach (var e in + SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) { - StartTime = StartTime, - Position = Position, - Samples = getNodeSamples(0), - SampleControlPoint = SampleControlPoint, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; + var firstSample = Samples.Find(s => s.Name == HitSampleInfo.HIT_NORMAL) + ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) + var sampleList = new List(); - TailCircle = new SliderTailCircle(this) - { - StartTime = EndTime, - Position = EndPosition, - IndexInCurrentCombo = IndexInCurrentCombo, - ComboIndex = ComboIndex, - }; + if (firstSample != null) + sampleList.Add(new HitSampleInfo + { + Bank = firstSample.Bank, + Volume = firstSample.Volume, + Name = @"slidertick", + }); - AddNested(HeadCircle); - AddNested(TailCircle); - } - - private void createTicks() - { - var length = Path.Distance; - var tickDistance = MathHelper.Clamp(TickDistance, 0, length); - - if (tickDistance == 0) return; - - var minDistanceFromEnd = Velocity * 0.01; - - var spanCount = this.SpanCount(); - - for (var span = 0; span < spanCount; span++) - { - var spanStartTime = StartTime + span * SpanDuration; - var reversed = span % 2 == 1; - - for (var d = tickDistance; d <= length; d += tickDistance) + switch (e.Type) { - if (d > length - minDistanceFromEnd) + case SliderEventType.Tick: + AddNested(new SliderTick + { + SpanIndex = e.SpanIndex, + SpanStartTime = e.SpanStartTime, + StartTime = e.Time, + Position = Position + Path.PositionAt(e.PathProgress), + StackHeight = StackHeight, + Scale = Scale, + Samples = sampleList + }); break; - var distanceProgress = d / length; - var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - - var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) - ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) - var sampleList = new List(); - - if (firstSample != null) - sampleList.Add(new SampleInfo + case SliderEventType.Head: + AddNested(HeadCircle = new SliderCircle { - Bank = firstSample.Bank, - Volume = firstSample.Volume, - Name = @"slidertick", + StartTime = e.Time, + Position = Position, + Samples = getNodeSamples(0), + SampleControlPoint = SampleControlPoint, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboIndex = ComboIndex, }); + break; - AddNested(new SliderTick - { - SpanIndex = span, - SpanStartTime = spanStartTime, - StartTime = spanStartTime + timeProgress * SpanDuration, - Position = Position + Path.PositionAt(distanceProgress), - StackHeight = StackHeight, - Scale = Scale, - Samples = sampleList - }); + case SliderEventType.LegacyLastTick: + // we need to use the LegacyLastTick here for compatibility reasons (difficulty). + // it is *okay* to use this because the TailCircle is not used for any meaningful purpose in gameplay. + // if this is to change, we should revisit this. + AddNested(TailCircle = new SliderTailCircle(this) + { + StartTime = e.Time, + Position = EndPosition, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboIndex = ComboIndex, + }); + break; + + case SliderEventType.Repeat: + AddNested(new RepeatPoint + { + RepeatIndex = e.SpanIndex, + SpanDuration = SpanDuration, + StartTime = StartTime + (e.SpanIndex + 1) * SpanDuration, + Position = Position + Path.PositionAt(e.PathProgress), + StackHeight = StackHeight, + Scale = Scale, + Samples = getNodeSamples(e.SpanIndex + 1) + }); + break; } } } - private void createRepeatPoints() - { - for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) - { - AddNested(new RepeatPoint - { - RepeatIndex = repeatIndex, - SpanDuration = SpanDuration, - StartTime = StartTime + repeat * SpanDuration, - Position = Position + Path.PositionAt(repeat % 2), - StackHeight = StackHeight, - Scale = Scale, - Samples = getNodeSamples(1 + repeatIndex) - }); - } - } - - private List getNodeSamples(int nodeIndex) - { - if (nodeIndex < NodeSamples.Count) - return NodeSamples[nodeIndex]; - return Samples; - } + private List getNodeSamples(int nodeIndex) => + nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; public override Judgement CreateJudgement() => new OsuJudgement(); } diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs index deda951378..151902a752 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Osu.Objects { diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index 74a7a8d446..4f2af64161 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -1,13 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; namespace osu.Game.Rulesets.Osu.Objects { + /// + /// Note that this should not be used for timing correctness. + /// See usage in for more information. + /// public class SliderTailCircle : SliderCircle { private readonly IBindable pathBindable = new Bindable(); diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 906f0a0182..85439699dd 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 1c60fd4831..8a2fd3b7aa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/OsuInputManager.cs b/osu.Game.Rulesets.Osu/OsuInputManager.cs index e2f4c43a23..b9e083d35b 100644 --- a/osu.Game.Rulesets.Osu/OsuInputManager.cs +++ b/osu.Game.Rulesets.Osu/OsuInputManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.ComponentModel; @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets.Osu protected override bool Handle(UIEvent e) { if (!AllowUserPresses) return false; + return base.Handle(e); } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 30dd9c73d6..baa4aff413 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -9,30 +9,35 @@ using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings; using osu.Framework.Input.Bindings; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Beatmaps.Legacy; +using osu.Game.Configuration; +using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Osu.Beatmaps; +using osu.Game.Rulesets.Osu.Configuration; using osu.Game.Rulesets.Osu.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu { public class OsuRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new OsuRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableOsuRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.A, OsuAction.LeftButton), - new KeyBinding(InputKey.S, OsuAction.RightButton), + new KeyBinding(InputKey.Z, OsuAction.LeftButton), + new KeyBinding(InputKey.X, OsuAction.RightButton), new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton), new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; @@ -82,6 +87,9 @@ namespace osu.Game.Rulesets.Osu if (mods.HasFlag(LegacyMods.Target)) yield return new OsuModTarget(); + + if (mods.HasFlag(LegacyMods.TouchDevice)) + yield return new OsuModTouchDevice(); } public override IEnumerable GetModsFor(ModType type) @@ -96,6 +104,7 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModHalfTime(), new OsuModDaycore()), new OsuModSpunOut(), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -103,13 +112,15 @@ namespace osu.Game.Rulesets.Osu new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()), new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()), new OsuModHidden(), - new OsuModFlashlight(), + new MultiMod(new OsuModFlashlight(), new OsuModBlinds()), }; + case ModType.Conversion: return new Mod[] { new OsuModTarget(), }; + case ModType.Automation: return new Mod[] { @@ -117,22 +128,26 @@ namespace osu.Game.Rulesets.Osu new OsuModRelax(), new OsuModAutopilot(), }; + case ModType.Fun: - return new Mod[] { + return new Mod[] + { new OsuModTransform(), new OsuModWiggle(), - new OsuModDeflate(), + new MultiMod(new OsuModGrow(), new OsuModDeflate()), + new MultiMod(new ModWindUp(), new ModWindDown()), }; + default: return new Mod[] { }; } } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetOsu }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new OsuPerformanceCalculator(this, beatmap, score); public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this); @@ -140,12 +155,14 @@ namespace osu.Game.Rulesets.Osu public override string ShortName => "osu"; - public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this); + public override RulesetSettingsSubsection CreateSettings() => new OsuSettingsSubsection(this); public override int? LegacyID => 0; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + public OsuRuleset(RulesetInfo rulesetInfo = null) : base(rulesetInfo) { diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs index b61c53be6a..b9a7096330 100644 --- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Runtime.CompilerServices; @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.iOS")] diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index a0d040c40b..690263c6a0 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.MathUtils; @@ -8,14 +8,17 @@ using osu.Game.Rulesets.Osu.Objects; using System; using System.Linq; using osu.Framework.Graphics; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Osu.Beatmaps; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Replays { public class OsuAutoGenerator : OsuAutoGeneratorBase { + public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap; + #region Parameters /// @@ -42,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Replays #region Construction / Initialisation - public OsuAutoGenerator(Beatmap beatmap) + public OsuAutoGenerator(IBeatmap beatmap) : base(beatmap) { // Already superhuman, but still somewhat realistic @@ -196,6 +199,7 @@ namespace osu.Game.Rulesets.Osu.Replays // Wait until Auto could "see and react" to the next note. double waitTime = h.StartTime - Math.Max(0.0, h.TimePreempt - reactionTime); + if (waitTime > lastFrame.Time) { lastFrame = new OsuReplayFrame(waitTime, lastFrame.Position) { Actions = lastFrame.Actions }; @@ -209,7 +213,7 @@ namespace osu.Game.Rulesets.Osu.Replays // Only "snap" to hitcircles if they are far enough apart. As the time between hitcircles gets shorter the snapping threshold goes up. if (timeDifference > 0 && // Sanity checks ((lastPosition - targetPos).Length > h.Radius * (1.5 + 100.0 / timeDifference) || // Either the distance is big enough - timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. + timeDifference >= 266)) // ... or the beats are slow enough to tap anyway. { // Perform eased movement for (double time = lastFrame.Time + FrameDelay; time < h.StartTime; time += FrameDelay) @@ -289,7 +293,6 @@ namespace osu.Game.Rulesets.Osu.Replays { // We add intermediate frames for spinning / following a slider here. case Spinner spinner: - { Vector2 difference = startPosition - SPINNER_CENTRE; float radius = difference.Length; @@ -312,9 +315,8 @@ namespace osu.Game.Rulesets.Osu.Replays endFrame.Position = endPosition; break; - } + case Slider slider: - { for (double j = FrameDelay; j < slider.Duration; j += FrameDelay) { Vector2 pos = slider.StackedPositionAt(j / slider.Duration); @@ -323,7 +325,6 @@ namespace osu.Game.Rulesets.Osu.Replays AddFrameToReplay(new OsuReplayFrame(slider.EndTime, new Vector2(slider.StackedEndPosition.X, slider.StackedEndPosition.Y), action)); break; - } } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 2530644413..9ab358ee12 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -1,18 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Osu.Objects; using System; using System.Collections.Generic; +using osu.Game.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; -using osu.Game.Users; namespace osu.Game.Rulesets.Osu.Replays { - public abstract class OsuAutoGeneratorBase : AutoGenerator + public abstract class OsuAutoGeneratorBase : AutoGenerator { #region Constants @@ -20,6 +19,7 @@ namespace osu.Game.Rulesets.Osu.Replays /// Constants (for spinners). /// protected static readonly Vector2 SPINNER_CENTRE = OsuPlayfield.BASE_SIZE / 2; + protected const float SPIN_RADIUS = 50; /// @@ -34,16 +34,10 @@ namespace osu.Game.Rulesets.Osu.Replays protected Replay Replay; protected List Frames => Replay.Frames; - protected OsuAutoGeneratorBase(Beatmap beatmap) + protected OsuAutoGeneratorBase(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; + Replay = new Replay(); // We are using ApplyModsToRate and not ApplyModsToTime to counteract the speed up / slow down from HalfTime / DoubleTime so that we remain at a constant framerate of 60 fps. FrameDelay = ApplyModsToRate(1000.0 / 60.0); @@ -52,6 +46,7 @@ namespace osu.Game.Rulesets.Osu.Replays #endregion #region Utilities + protected double ApplyModsToTime(double v) => v; protected double ApplyModsToRate(double v) => v; diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs similarity index 54% rename from osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs rename to osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs index 230709a4aa..c6ac1dd346 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayInputHandler.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuFramedReplayInputHandler.cs @@ -1,18 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Input.StateChanges; using osu.Framework.MathUtils; +using osu.Game.Replays; using osu.Game.Rulesets.Replays; using osuTK; namespace osu.Game.Rulesets.Osu.Replays { - public class OsuReplayInputHandler : FramedReplayInputHandler + public class OsuFramedReplayInputHandler : FramedReplayInputHandler { - public OsuReplayInputHandler(Replay replay) + public OsuFramedReplayInputHandler(Replay replay) : base(replay) { } @@ -23,10 +25,14 @@ namespace osu.Game.Rulesets.Osu.Replays { get { - if (!HasFrames) + var frame = CurrentFrame; + + if (frame == null) return null; - return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); + Debug.Assert(CurrentTime != null); + + return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position; } } @@ -40,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Replays }, new ReplayState { - PressedActions = CurrentFrame.Actions + PressedActions = CurrentFrame?.Actions ?? new List() } }; } diff --git a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs index 82dfa7f0ad..4d90fcadd5 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuReplayFrame.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; using osuTK; diff --git a/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..bf345811a2 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,179 @@ +osu file format v14 + +[General] +StackLeniency: 0.3 +Mode: 0 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +62500,-500,4,2,1,50,0,0 +71000,-100,4,2,1,50,0,0 + +[HitObjects] +// Circles spaced 1 beat apart, with increasing jump distance +126,112,500,5,0,0:0:0:0: +130,155,1000,1,0,0:0:0:0: +131,269,1500,1,0,0:0:0:0: +341,269,2000,1,0,0:0:0:0: +113,95,2500,1,0,0:0:0:0: + +// Circles spaced 1/2 beat apart, with increasing jump distance +108,104,3500,5,0,0:0:0:0: +110,145,3750,1,0,0:0:0:0: +115,262,4000,1,0,0:0:0:0: +285,265,4250,1,0,0:0:0:0: +458,48,4500,1,0,0:0:0:0: +35,199,4750,1,0,0:0:0:0: +251,340,5000,1,0,0:0:0:0: +20,352,5250,1,0,0:0:0:0: +426,62,5500,1,0,0:0:0:0: + +// Circles spaced 1/4 beat apart, with increasing jump distances +211,138,6500,5,0,0:0:0:0: +99,256,6625,1,0,0:0:0:0: +68,129,6750,1,0,0:0:0:0: +371,340,6875,1,0,0:0:0:0: +241,219,7000,1,0,0:0:0:0: +252,148,7125,1,0,0:0:0:0: +434,97,7250,1,0,0:0:0:0: +40,38,7375,1,0,0:0:0:0: +114,334,7500,1,0,0:0:0:0: +301,19,7625,1,0,0:0:0:0: +441,241,7750,1,0,0:0:0:0: +121,91,7875,1,0,0:0:0:0: +270,384,8000,1,0,0:0:0:0: +488,92,8125,1,0,0:0:0:0: +332,82,8250,1,0,0:0:0:0: +108,240,8375,1,0,0:0:0:0: +281,268,8500,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/2 beat apart, small jump distances, changing angles +252,191,9500,5,0,0:0:0:0: +356,191,9750,1,0,0:0:0:0: +311,268,10000,1,0,0:0:0:0: +190,270,10250,1,0,0:0:0:0: +107,199,10500,1,0,0:0:0:0: +172,105,10750,1,0,0:0:0:0: +297,102,11000,1,0,0:0:0:0: +373,178,11250,1,0,0:0:0:0: +252,195,11500,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/2 beat apart, large jump distances, changing angles +140,187,12500,5,0,0:0:0:0: +451,331,12750,1,0,0:0:0:0: +46,338,13000,1,0,0:0:0:0: +204,50,13250,1,0,0:0:0:0: +464,162,13500,1,0,0:0:0:0: +252,346,13750,1,0,0:0:0:0: +13,175,14000,1,0,0:0:0:0: +488,181,14250,1,0,0:0:0:0: +251,187,14500,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/4 beat apart, small jump distances, changing angles +188,192,15500,5,0,0:0:0:0: +298,194,15625,1,0,0:0:0:0: +317,84,15750,1,0,0:0:0:0: +185,85,15875,1,0,0:0:0:0: +77,200,16000,1,0,0:0:0:0: +184,303,16125,1,0,0:0:0:0: +295,225,16250,1,0,0:0:0:0: +300,84,16375,1,0,0:0:0:0: +144,82,16500,1,0,0:0:0:0: +141,215,16625,1,0,0:0:0:0: +314,184,16750,1,0,0:0:0:0: +188,192,16875,1,0,0:0:0:0: +188,192,17000,1,0,0:0:0:0: + +// Constant spaced circles spaced 1/4 beat apart, large jump distances, changing angles +97,192,18000,5,0,0:0:0:0: +336,38,18125,1,0,0:0:0:0: +440,322,18250,1,0,0:0:0:0: +39,331,18375,1,0,0:0:0:0: +98,39,18500,1,0,0:0:0:0: +460,179,18625,1,0,0:0:0:0: +245,338,18750,1,0,0:0:0:0: +12,184,18875,1,0,0:0:0:0: +250,41,19000,1,0,0:0:0:0: +265,193,19125,1,0,0:0:0:0: +486,22,19250,1,0,0:0:0:0: +411,205,19375,1,0,0:0:0:0: +107,198,19500,1,0,0:0:0:0: + +// Short sliders spaced 1 beat apart +28,108,20500,2,0,L|196:107,1,160 +25,177,21500,2,0,L|193:176,1,160 +26,308,22500,2,0,L|194:307,1,160 +320,89,23500,2,0,L|488:88,1,160 + +// Short sliders spaced 1/2 beat apart +28,108,25000,6,0,L|196:107,1,160 +27,173,25750,2,0,L|195:172,1,160 +25,292,26500,2,0,L|193:291,1,160 +340,213,27250,2,0,L|508:212,1,160 +21,44,28000,2,0,L|189:43,1,160 + +// Short sliders spaced 1/4 beat apart +28,108,29500,6,0,L|196:107,1,160 +30,169,30125,2,0,L|198:168,1,160 +35,282,30750,2,0,L|203:281,1,160 +327,286,31375,2,0,L|495:285,1,160 +51,61,32000,2,0,L|219:60,1,160 + +// Large, medium-paced slider shapes +// PerfectCurve +66,86,33500,6,0,P|246:348|427:44,1,800 +66,86,36500,2,0,P|246:348|427:44,1,800 +66,86,39500,2,0,P|246:348|427:44,1,800 +// Linear +66,72,42500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120 +66,72,46500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120 +66,72,50500,2,0,B|419:65|419:65|66:316|66:316|426:318,1,1120 +// Bezier +76,287,54500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640 +76,287,57000,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640 +76,287,59500,2,0,B|440:325|138:128|470:302|500:30|130:85|66:82,1,640 + +// Large slow slider with many ticks +81,170,62500,6,0,P|263:78|168:268,1,480 + +// Fast slider with many repeats +102,152,71000,6,0,L|175:153,18,64 + +// Slider-circle combos, spaced 1/2 beat apart +106,204,75500,6,0,P|275:33|171:304,1,800 +255,179,78250,1,0,0:0:0:0: +106,204,78500,2,0,P|275:33|171:304,1,800 +255,179,81250,1,0,0:0:0:0: +106,204,81500,2,0,P|275:33|171:304,1,800 + +// Circle-spinner combos, spaced 1/2 beat apart +82,69,85000,5,0,0:0:0:0: +256,192,85250,8,0,86000,0:0:0:0: +384,189,86250,5,0,0:0:0:0: +256,192,86500,12,0,87000,0:0:0:0: + +// Spinner-spinner combos, spaced 1/2 beat apart +256,192,88000,12,0,89000,0:0:0:0: +256,192,89250,12,0,90250,0:0:0:0: +256,192,90500,12,0,91500,0:0:0:0: +256,192,91750,12,0,92750,0:0:0:0: +256,192,93000,12,0,94000,0:0:0:0: + +// Slider-spinner combos, spaced 1/2 beat apart +49,89,95000,6,0,L|214:87,1,160 +256,192,95625,12,0,96500,0:0:0:0: +12,299,96625,6,0,L|177:297,1,160 +256,192,97250,12,0,98125,0:0:0:0: +295,107,98250,6,0,L|460:105,1,160 +256,192,98875,12,0,99750,0:0:0:0: +279,325,99875,6,0,L|444:323,1,160 +256,192,100500,12,0,101375,0:0:0:0: +197,197,101500,6,0,L|362:195,1,160 +256,192,102125,12,0,103000,0:0:0:0: diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index a9d39e88b4..cf0565c6da 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -14,14 +15,13 @@ namespace osu.Game.Rulesets.Osu.Scoring { internal class OsuScoreProcessor : ScoreProcessor { - public OsuScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + public OsuScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } private float hpDrainRate; - private readonly Dictionary scoreResultCounts = new Dictionary(); private readonly Dictionary comboResultCounts = new Dictionary(); protected override void ApplyBeatmap(Beatmap beatmap) @@ -34,23 +34,9 @@ namespace osu.Game.Rulesets.Osu.Scoring protected override void Reset(bool storeResults) { base.Reset(storeResults); - - scoreResultCounts.Clear(); comboResultCounts.Clear(); } - public override void PopulateScore(Score score) - { - base.PopulateScore(score); - - score.Statistics[HitResult.Great] = scoreResultCounts.GetOrDefault(HitResult.Great); - score.Statistics[HitResult.Good] = scoreResultCounts.GetOrDefault(HitResult.Good); - score.Statistics[HitResult.Meh] = scoreResultCounts.GetOrDefault(HitResult.Meh); - score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss); - } - - private const double harshness = 0.01; - protected override void ApplyResult(JudgementResult result) { base.ApplyResult(result); @@ -58,35 +44,35 @@ namespace osu.Game.Rulesets.Osu.Scoring var osuResult = (OsuJudgementResult)result; if (result.Type != HitResult.None) - { - scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; - } + } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + { switch (result.Type) { case HitResult.Great: - Health.Value += (10.2 - hpDrainRate) * harshness; - break; + return 10.2 - hpDrainRate; case HitResult.Good: - Health.Value += (8 - hpDrainRate) * harshness; - break; + return 8 - hpDrainRate; case HitResult.Meh: - Health.Value += (4 - hpDrainRate) * harshness; - break; + return 4 - hpDrainRate; - /*case HitResult.SliderTick: - Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01; - break;*/ + // case HitResult.SliderTick: + // return Math.Max(7 - hpDrainRate, 0) * 0.01; case HitResult.Miss: - Health.Value -= hpDrainRate * (harshness * 2); - break; + return hpDrainRate; + + default: + return 0; } } protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement); + + public override HitWindows CreateHitWindows() => new OsuHitWindows(); } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index b25b3e5cb8..b986076593 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Diagnostics; using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.OpenGL.Buffers; +using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { private int currentIndex; - private Shader shader; + private IShader shader; private Texture texture; private Vector2 size => texture.Size * Scale; @@ -35,7 +35,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public override bool IsPresent => true; - private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); private const int max_sprites = 2048; private readonly TrailPart[] parts = new TrailPart[max_sprites]; @@ -44,23 +43,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly InputResampler resampler = new InputResampler(); - protected override DrawNode CreateDrawNode() => new TrailDrawNode(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - TrailDrawNode tNode = (TrailDrawNode)node; - tNode.Shader = shader; - tNode.Texture = texture; - tNode.Size = size; - tNode.Time = time; - tNode.Shared = trailDrawNodeSharedData; - - for (int i = 0; i < parts.Length; ++i) - if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID) - tNode.Parts[i] = parts[i]; - } + protected override DrawNode CreateDrawNode() => new TrailDrawNode(this); public CursorTrail() { @@ -71,8 +54,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor for (int i = 0; i < max_sprites; i++) { - parts[i].InvalidationID = 0; - parts[i].WasUpdated = true; + // InvalidationID 1 forces an update of each part of the cursor trail the first time ApplyState is run on the draw node + // This is to prevent garbage data from being sent to the vertex shader, resulting in visual issues on some platforms + parts[i].InvalidationID = 1; } } @@ -81,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [BackgroundDependencyLoader] private void load(ShaderManager shaders, TextureStore textures) { - shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); + shader = shaders.Load(@"CursorTrail", FragmentShaderDescriptor.TEXTURE); texture = textures.Get(@"Cursor/cursortrail"); Scale = new Vector2(1 / texture.ScaleAdjust); } @@ -164,89 +148,79 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public Vector2 Position; public float Time; public long InvalidationID; - public bool WasUpdated; - } - - private class TrailDrawNodeSharedData - { - public VertexBuffer VertexBuffer; } private class TrailDrawNode : DrawNode { - public Shader Shader; - public Texture Texture; + protected new CursorTrail Source => (CursorTrail)base.Source; - public float Time; - public TrailDrawNodeSharedData Shared; + private IShader shader; + private Texture texture; - public readonly TrailPart[] Parts = new TrailPart[max_sprites]; - public Vector2 Size; + private float time; - public TrailDrawNode() + private readonly TrailPart[] parts = new TrailPart[max_sprites]; + private Vector2 size; + + private readonly VertexBatch vertexBatch = new QuadBatch(max_sprites, 1); + + public TrailDrawNode(CursorTrail source) + : base(source) { for (int i = 0; i < max_sprites; i++) + parts[i].InvalidationID = 0; + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.size; + time = Source.time; + + for (int i = 0; i < Source.parts.Length; ++i) { - Parts[i].InvalidationID = 0; - Parts[i].WasUpdated = false; + if (Source.parts[i].InvalidationID > parts[i].InvalidationID) + parts[i] = Source.parts[i]; } } public override void Draw(Action vertexAction) { - if (Shared.VertexBuffer == null) - Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); - - Shader.GetUniform("g_FadeClock").UpdateValue(ref Time); - - int updateStart = -1, updateEnd = 0; - for (int i = 0; i < Parts.Length; ++i) - { - if (Parts[i].WasUpdated) - { - if (updateStart == -1) - updateStart = i; - updateEnd = i + 1; - - int start = i * 4; - int end = start; - - Vector2 pos = Parts[i].Position; - float time = Parts[i].Time; - - Texture.DrawQuad( - new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), - DrawColourInfo.Colour, - null, - v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex - { - Position = v.Position, - TexturePosition = v.TexturePosition, - Time = time + 1, - Colour = v.Colour, - }); - - Parts[i].WasUpdated = false; - } - else if (updateStart != -1) - { - Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); - updateStart = -1; - } - } - - // Update all remaining vertices that have been changed. - if (updateStart != -1) - Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4); - base.Draw(vertexAction); - Shader.Bind(); + shader.Bind(); + shader.GetUniform("g_FadeClock").UpdateValue(ref time); - Texture.TextureGL.Bind(); - Shared.VertexBuffer.Draw(); + for (int i = 0; i < parts.Length; ++i) + { + Vector2 pos = parts[i].Position; + float localTime = parts[i].Time; - Shader.Unbind(); + DrawQuad( + texture, + new Quad(pos.X - size.X / 2, pos.Y - size.Y / 2, size.X, size.Y), + DrawColourInfo.Colour, + null, + v => vertexBatch.Add(new TexturedTrailVertex + { + Position = v.Position, + TexturePosition = v.TexturePosition, + Time = localTime + 1, + Colour = v.Colour, + })); + } + + shader.Unbind(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBatch.Dispose(); } } @@ -255,10 +229,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { [VertexMember(2, VertexAttribPointerType.Float)] public Vector2 Position; + [VertexMember(4, VertexAttribPointerType.Float)] public Color4 Colour; + [VertexMember(2, VertexAttribPointerType.Float)] public Vector2 TexturePosition; + [VertexMember(1, VertexAttribPointerType.Float)] public float Time; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs deleted file mode 100644 index 4aa30777e9..0000000000 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ /dev/null @@ -1,194 +0,0 @@ -// 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.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.Bindings; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Skinning; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Osu.UI.Cursor -{ - public class GameplayCursor : CursorContainer, IKeyBindingHandler - { - protected override Drawable CreateCursor() => new OsuCursor(); - - protected override Container Content => fadeContainer; - - private readonly Container fadeContainer; - - public GameplayCursor() - { - InternalChild = fadeContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new CursorTrail { Depth = 1 } - } - }; - } - - private int downCount; - - private const float pressed_scale = 1.2f; - private const float released_scale = 1f; - - private float targetScale => downCount > 0 ? pressed_scale : released_scale; - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - downCount++; - ActiveCursor.ScaleTo(released_scale).ScaleTo(targetScale, 100, Easing.OutQuad); - break; - } - - return false; - } - - public bool OnReleased(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - if (--downCount == 0) - ActiveCursor.ScaleTo(targetScale, 200, Easing.OutQuad); - break; - } - - return false; - } - - public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. - - protected override void PopIn() - { - fadeContainer.FadeTo(1, 300, Easing.OutQuint); - ActiveCursor.ScaleTo(targetScale, 400, Easing.OutQuint); - } - - protected override void PopOut() - { - fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); - ActiveCursor.ScaleTo(targetScale * 0.8f, 450, Easing.OutQuint); - } - - public class OsuCursor : Container - { - private Drawable cursorContainer; - - private Bindable cursorScale; - private Bindable autoCursorScale; - private readonly IBindable beatmap = new Bindable(); - - public OsuCursor() - { - Origin = Anchor.Centre; - Size = new Vector2(42); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, IBindableBeatmap beatmap) - { - Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 6, - BorderColour = Color4.White, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Pink.Opacity(0.5f), - Radius = 5, - }, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = Size.X / 3, - BorderColour = Color4.White.Opacity(0.5f), - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, - }, - }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.1f), - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - }, - }, - } - }, restrictSize: false) - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }; - - this.beatmap.BindTo(beatmap); - this.beatmap.ValueChanged += v => calculateScale(); - - cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - cursorScale.ValueChanged += v => calculateScale(); - - autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += v => calculateScale(); - - calculateScale(); - } - - private void calculateScale() - { - float scale = (float)cursorScale.Value; - - if (autoCursorScale && beatmap.Value != null) - { - // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); - } - - cursorContainer.Scale = new Vector2(scale); - } - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs new file mode 100644 index 0000000000..27546fa424 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -0,0 +1,149 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursor : SkinReloadableDrawable + { + private bool cursorExpand; + + private Bindable cursorScale; + private Bindable autoCursorScale; + private readonly IBindable beatmap = new Bindable(); + + private Container expandTarget; + private Drawable scaleTarget; + + public OsuCursor() + { + Origin = Anchor.Centre; + Size = new Vector2(28); + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + cursorExpand = skin.GetValue(s => s.CursorExpand ?? true); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, IBindable beatmap) + { + InternalChild = expandTarget = new Container + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Child = scaleTarget = new SkinnableDrawable("cursor", _ => new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 6, + BorderColour = Color4.White, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Pink.Opacity(0.5f), + Radius = 5, + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = Size.X / 3, + BorderColour = Color4.White.Opacity(0.5f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true, + }, + }, + }, + new CircularContainer + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.1f), + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + }, + }, + } + }, restrictSize: false) + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + } + }; + + this.beatmap.BindTo(beatmap); + this.beatmap.ValueChanged += _ => calculateScale(); + + cursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); + cursorScale.ValueChanged += _ => calculateScale(); + + autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); + autoCursorScale.ValueChanged += _ => calculateScale(); + + calculateScale(); + } + + private void calculateScale() + { + float scale = (float)cursorScale.Value; + + if (autoCursorScale.Value && beatmap.Value != null) + { + // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. + scale *= (float)(1 - 0.7 * (1 + beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize - BeatmapDifficulty.DEFAULT_DIFFICULTY) / BeatmapDifficulty.DEFAULT_DIFFICULTY); + } + + scaleTarget.Scale = new Vector2(scale); + } + + private const float pressed_scale = 1.2f; + private const float released_scale = 1f; + + public void Expand() + { + if (!cursorExpand) return; + + expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + } + + public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs new file mode 100644 index 0000000000..893c7875fa --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Osu.UI.Cursor +{ + public class OsuCursorContainer : GameplayCursorContainer, IKeyBindingHandler + { + protected override Drawable CreateCursor() => new OsuCursor(); + + protected override Container Content => fadeContainer; + + private readonly Container fadeContainer; + + private readonly Bindable showTrail = new Bindable(true); + + private readonly CursorTrail cursorTrail; + + public OsuCursorContainer() + { + InternalChild = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + cursorTrail = new CursorTrail { Depth = 1 } + } + }; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuRulesetConfigManager config) + { + config?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail); + + showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true); + } + + private int downCount; + + private void updateExpandedState() + { + if (downCount > 0) + (ActiveCursor as OsuCursor)?.Expand(); + else + (ActiveCursor as OsuCursor)?.Contract(); + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + downCount++; + updateExpandedState(); + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + // Todo: Math.Max() is required as a temporary measure to address https://github.com/ppy/osu-framework/issues/2576 + downCount = Math.Max(0, downCount - 1); + + if (downCount == 0) + updateExpandedState(); + break; + } + + return false; + } + + public override bool HandlePositionalInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. + + protected override void PopIn() + { + fadeContainer.FadeTo(1, 300, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + } + + protected override void PopOut() + { + fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs new file mode 100644 index 0000000000..d185d7d4c9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Input.Handlers; +using osu.Game.Replays; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Configuration; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class DrawableOsuRuleset : DrawableRuleset + { + protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config; + + public DrawableOsuRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) + { + } + + public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); + + protected override Playfield CreatePlayfield() => new OsuPlayfield(); + + protected override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); + + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new OsuPlayfieldAdjustmentContainer(); + + protected override ResumeOverlay CreateResumeOverlay() => new OsuResumeOverlay(); + + public override DrawableHitObject CreateDrawableRepresentation(OsuHitObject h) + { + switch (h) + { + case HitCircle circle: + return new DrawableHitCircle(circle); + + case Slider slider: + return new DrawableSlider(slider); + + case Spinner spinner: + return new DrawableSpinner(spinner); + } + + return null; + } + + protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuFramedReplayInputHandler(replay); + + public override double GameplayStartTime + { + get + { + if (Objects.FirstOrDefault() is OsuHitObject first) + return first.StartTime - Math.Max(2000, first.TimePreempt); + + return 0; + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 3399fdb9a0..0cbe0cca85 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -11,46 +11,40 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using System.Linq; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.UI.Cursor; namespace osu.Game.Rulesets.Osu.UI { public class OsuPlayfield : Playfield { - private readonly Container approachCircles; + private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; private readonly ConnectionRenderer connectionLayer; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); + protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); + public OsuPlayfield() { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Size = new Vector2(0.75f); - - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + connectionLayer = new FollowPointRenderer { - connectionLayer = new FollowPointRenderer - { - RelativeSizeAxes = Axes.Both, - Depth = 2, - }, - judgementLayer = new JudgementContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1, - }, - HitObjectContainer, - approachCircles = new Container - { - RelativeSizeAxes = Axes.Both, - Depth = -1, - }, - } + RelativeSizeAxes = Axes.Both, + Depth = 2, + }, + judgementLayer = new JudgementContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1, + }, + HitObjectContainer, + approachCircles = new ApproachCircleProxyContainer + { + RelativeSizeAxes = Axes.Both, + Depth = -1, + }, }; } @@ -58,13 +52,26 @@ namespace osu.Game.Rulesets.Osu.UI { h.OnNewResult += onNewResult; - var c = h as IDrawableHitObjectWithProxiedApproach; - if (c != null) - approachCircles.Add(c.ProxiedLayer.CreateProxy()); + if (h is IDrawableHitObjectWithProxiedApproach c) + { + var original = c.ProxiedLayer; + + // Hitobjects only have lifetimes set on LoadComplete. For nested hitobjects (e.g. SliderHeads), this only happens when the parenting slider becomes visible. + // This delegation is required to make sure that the approach circles for those not-yet-loaded objects aren't added prematurely. + original.OnLoadComplete += addApproachCircleProxy; + } base.Add(h); } + private void addApproachCircleProxy(Drawable d) + { + var proxy = d.CreateProxy(); + proxy.LifetimeStart = d.LifetimeStart; + proxy.LifetimeEnd = d.LifetimeEnd; + approachCircles.Add(proxy); + } + public override void PostProcess() { connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); @@ -72,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.UI private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) { - if (!judgedObject.DisplayResult || !DisplayJudgements) + if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; DrawableOsuJudgement explosion = new DrawableOsuJudgement(result, judgedObject) @@ -86,5 +93,10 @@ namespace osu.Game.Rulesets.Osu.UI } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos); + + private class ApproachCircleProxyContainer : LifetimeManagementContainer + { + public void Add(Drawable approachCircleProxy) => AddInternal(approachCircleProxy); + } } } diff --git a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs similarity index 72% rename from osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs index df5bd84e75..e28ff5f460 100644 --- a/osu.Game.Rulesets.Osu/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfieldAdjustmentContainer.cs @@ -1,19 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.UI { - public class PlayfieldAdjustmentContainer : Container + public class OsuPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { protected override Container Content => content; private readonly Container content; - public PlayfieldAdjustmentContainer() + public OsuPlayfieldAdjustmentContainer() { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Size = new Vector2(0.75f); + InternalChild = new Container { Anchor = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs new file mode 100644 index 0000000000..9e5df0d6b1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuResumeOverlay : ResumeOverlay + { + private OsuClickToResumeCursor clickToResumeCursor; + + private GameplayCursorContainer localCursorContainer; + + public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; + + protected override string Message => "Click the orange cursor to resume"; + + [BackgroundDependencyLoader] + private void load() + { + Add(clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }); + } + + public override void Show() + { + base.Show(); + clickToResumeCursor.ShowAt(GameplayCursor.ActiveCursor.Position); + + if (localCursorContainer == null) + Add(localCursorContainer = new OsuCursorContainer()); + } + + public override void Hide() + { + localCursorContainer?.Expire(); + localCursorContainer = null; + + base.Hide(); + } + + protected override bool OnHover(HoverEvent e) => true; + + public class OsuClickToResumeCursor : OsuCursor, IKeyBindingHandler + { + public override bool HandlePositionalInput => true; + + public Action ResumeRequested; + + public OsuClickToResumeCursor() + { + RelativePositionAxes = Axes.Both; + } + + protected override bool OnHover(HoverEvent e) + { + updateColour(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateColour(); + base.OnHoverLost(e); + } + + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (!IsHovered) return false; + + this.ScaleTo(new Vector2(2), TRANSITION_TIME, Easing.OutQuint); + + ResumeRequested?.Invoke(); + return true; + } + + return false; + } + + public bool OnReleased(OsuAction action) => false; + + public void ShowAt(Vector2 activeCursorPosition) => Schedule(() => + { + updateColour(); + this.MoveTo(activeCursorPosition); + this.ScaleTo(new Vector2(4)).Then().ScaleTo(Vector2.One, 1000, Easing.OutQuint); + }); + + private void updateColour() + { + this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs deleted file mode 100644 index ea5718bed2..0000000000 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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.Graphics.Cursor; -using osu.Framework.Input; -using osu.Game.Beatmaps; -using osu.Game.Input.Handlers; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Replays; -using osu.Game.Rulesets.Osu.Scoring; -using osu.Game.Rulesets.Osu.UI.Cursor; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Osu.UI -{ - public class OsuRulesetContainer : RulesetContainer - { - public OsuRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - - public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(this); - - protected override Playfield CreatePlayfield() => new OsuPlayfield(); - - public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo); - - public override DrawableHitObject GetVisualRepresentation(OsuHitObject h) - { - switch (h) - { - case HitCircle circle: - return new DrawableHitCircle(circle); - case Slider slider: - return new DrawableSlider(slider); - case Spinner spinner: - return new DrawableSpinner(spinner); - } - - return null; - } - - protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); - - public override double GameplayStartTime - { - get - { - var first = (OsuHitObject)Objects.First(); - return first.StartTime - first.TimePreempt; - } - } - - protected override CursorContainer CreateCursor() => new GameplayCursor(); - } -} diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs b/osu.Game.Rulesets.Osu/UI/OsuSettings.cs deleted file mode 100644 index 25c009b117..0000000000 --- a/osu.Game.Rulesets.Osu/UI/OsuSettings.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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; -using osu.Game.Overlays.Settings; - -namespace osu.Game.Rulesets.Osu.UI -{ - public class OsuSettings : RulesetSettingsSubsection - { - protected override string Header => "osu!"; - - public OsuSettings(Ruleset ruleset) - : base(ruleset) - { - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - Children = new Drawable[] - { - new SettingsCheckbox - { - LabelText = "Snaking in sliders", - Bindable = config.GetBindable(OsuSetting.SnakingInSliders) - }, - new SettingsCheckbox - { - LabelText = "Snaking out sliders", - Bindable = config.GetBindable(OsuSetting.SnakingOutSliders) - }, - }; - } - } -} diff --git a/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs new file mode 100644 index 0000000000..88adf72551 --- /dev/null +++ b/osu.Game.Rulesets.Osu/UI/OsuSettingsSubsection.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets.Osu.Configuration; + +namespace osu.Game.Rulesets.Osu.UI +{ + public class OsuSettingsSubsection : RulesetSettingsSubsection + { + protected override string Header => "osu!"; + + public OsuSettingsSubsection(Ruleset ruleset) + : base(ruleset) + { + } + + [BackgroundDependencyLoader] + private void load() + { + var config = (OsuRulesetConfigManager)Config; + + Children = new Drawable[] + { + new SettingsCheckbox + { + LabelText = "Snaking in sliders", + Bindable = config.GetBindable(OsuRulesetSetting.SnakingInSliders) + }, + new SettingsCheckbox + { + LabelText = "Snaking out sliders", + Bindable = config.GetBindable(OsuRulesetSetting.SnakingOutSliders) + }, + new SettingsCheckbox + { + LabelText = "Cursor trail", + Bindable = config.GetBindable(OsuRulesetSetting.ShowCursorTrail) + }, + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..567220f316 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Taiko.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs new file mode 100644 index 0000000000..6613e9e2b4 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Rulesets.Taiko.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist new file mode 100644 index 0000000000..5fe822946a --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Rulesets.Taiko.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Rulesets-Taiko-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj new file mode 100644 index 0000000000..d2817b743c --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -0,0 +1,45 @@ + + + + + Debug + iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A} + Exe + osu.Game.Rulesets.Taiko.Tests + osu.Game.Rulesets.Taiko.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {F167E17A-7DE6-4AF5-B920-A5112296C695} + osu.Game.Rulesets.Taiko + + + + + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json index de7bf77070..3ef78bf6c8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json +++ b/osu.Game.Rulesets.Taiko.Tests/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Debug)", @@ -20,7 +20,7 @@ "request": "launch", "program": "dotnet", "args": [ - "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Taiko.Tests.dll" + "${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" ], "cwd": "${workspaceRoot}", "preLaunchTask": "Build (Release)", diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index db8480c742..68ae7544c2 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs new file mode 100644 index 0000000000..e7b6d8615b --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoDifficultyCalculatorTest.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TaikoDifficultyCalculatorTest : DifficultyCalculatorTest + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko"; + + [TestCase(2.9811338051242915d, "diffcalc-test")] + [TestCase(2.9811338051242915d, "diffcalc-test-strong")] + public void Test(double expected, string name) + => base.Test(expected, name); + + protected override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(new TaikoRuleset(), beatmap); + + protected override Ruleset CreateRuleset() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs similarity index 79% rename from osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs rename to osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs index b88aea0bbd..8c1b0c4c62 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneInputDrum.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -16,17 +16,17 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestCaseInputDrum : OsuTestCase + public class TestSceneInputDrum : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(InputDrum), typeof(DrumSampleMapping), - typeof(SampleInfo), + typeof(HitSampleInfo), typeof(SampleControlPoint) }; - public TestCaseInputDrum() + public TestSceneInputDrum() { Add(new TaikoInputManager(new RulesetInfo { ID = 1 }) { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs similarity index 79% rename from osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs rename to osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index dad7f34e71..6f9856df83 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestCaseTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -11,13 +11,13 @@ using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual; using osuTK; using osu.Game.Rulesets.Scoring; @@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestCaseTaikoPlayfield : OsuTestCase + public class TestSceneTaikoPlayfield : OsuTestScene { private const double default_duration = 1000; private const float scroll_time = 1000; @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override double TimePerAction => default_duration * 2; private readonly Random rng = new Random(1337); - private TaikoRulesetContainer rulesetContainer; + private DrawableTaikoRuleset drawableRuleset; private Container playfieldContainer; [BackgroundDependencyLoader] @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var controlPointInfo = new ControlPointInfo(); controlPointInfo.TimingPoints.Add(new TimingControlPoint()); - WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap + WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { HitObjects = new List { new CentreHit() }, BeatmapInfo = new BeatmapInfo @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, Height = 768, - Children = new[] { rulesetContainer = new TaikoRulesetContainer(new TaikoRuleset(), beatmap) } + Children = new[] { drawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap, Array.Empty()) } }); } @@ -100,15 +100,19 @@ namespace osu.Game.Rulesets.Taiko.Tests case 1: addCentreHit(false); break; + case 2: addCentreHit(true); break; + case 3: addDrumRoll(false); break; + case 4: addDrumRoll(true); break; + case 5: addSwell(); delay = scroll_time - 100; @@ -121,6 +125,7 @@ namespace osu.Game.Rulesets.Taiko.Tests default: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, rng.Next(25, 400)), 500); break; + case 6: playfieldContainer.Delay(delay).ResizeTo(new Vector2(1, TaikoPlayfield.DEFAULT_HEIGHT), 500); break; @@ -139,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); } private void addStrongHitJudgement(bool kiai) @@ -154,33 +159,33 @@ namespace osu.Game.Rulesets.Taiko.Tests var h = new DrawableTestHit(hit) { X = RNG.NextSingle(hitResult == HitResult.Good ? -0.1f : -0.05f, hitResult == HitResult.Good ? 0.1f : 0.05f) }; - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(h, new JudgementResult(new TaikoJudgement()) { Type = hitResult }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new TestStrongNestedHit(h), new JudgementResult(new TaikoStrongJudgement()) { Type = HitResult.Great }); } private void addMissJudgement() { - ((TaikoPlayfield)rulesetContainer.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); + ((TaikoPlayfield)drawableRuleset.Playfield).OnNewResult(new DrawableTestHit(new Hit()), new JudgementResult(new TaikoJudgement()) { Type = HitResult.Miss }); } private void addBarLine(bool major, double delay = scroll_time) { - BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay }; + BarLine bl = new BarLine { StartTime = drawableRuleset.Playfield.Time.Current + delay }; - rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); + drawableRuleset.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl)); } private void addSwell(double duration = default_duration) { var swell = new Swell { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, Duration = duration, }; swell.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableSwell(swell)); + drawableRuleset.Playfield.Add(new DrawableSwell(swell)); } private void addDrumRoll(bool strong, double duration = default_duration) @@ -190,40 +195,40 @@ namespace osu.Game.Rulesets.Taiko.Tests var d = new DrumRoll { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong, Duration = duration, }; d.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableDrumRoll(d)); + drawableRuleset.Playfield.Add(new DrawableDrumRoll(d)); } private void addCentreHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableCentreHit(h)); + drawableRuleset.Playfield.Add(new DrawableCentreHit(h)); } private void addRimHit(bool strong) { Hit h = new Hit { - StartTime = rulesetContainer.Playfield.Time.Current + scroll_time, + StartTime = drawableRuleset.Playfield.Time.Current + scroll_time, IsStrong = strong }; h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - rulesetContainer.Playfield.Add(new DrawableRimHit(h)); + drawableRuleset.Playfield.Add(new DrawableRimHit(h)); } private class TestStrongNestedHit : DrawableStrongNestedHit diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs new file mode 100644 index 0000000000..d0db193738 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneTaikoSuddenDeath : PlayerTestScene + { + public TestSceneTaikoSuddenDeath() + : base(new TaikoRuleset()) + { + } + + protected override bool AllowFail => true; + + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray(); + return new ScoreAccessiblePlayer(); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => + new TaikoBeatmap + { + HitObjects = + { + new Swell { StartTime = 1500 }, + new Hit { StartTime = 100000 }, + }, + BeatmapInfo = + { + Ruleset = new TaikoRuleset().RulesetInfo + } + }; + + [Test] + public void TestSpinnerDoesNotFail() + { + bool judged = false; + AddStep("Setup judgements", () => + { + judged = false; + ((ScoreAccessiblePlayer)Player).ScoreProcessor.NewJudgement += b => judged = true; + }); + AddUntilStep("swell judged", () => judged); + AddAssert("not failed", () => !Player.HasFailed); + } + + private class ScoreAccessiblePlayer : TestPlayer + { + public ScoreAccessiblePlayer() + : base(false, false) + { + } + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + } + } +} 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 3ba64398f3..216cc0222f 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,14 +2,14 @@ - - - + + + WinExe - netcoreapp2.1 + netcoreapp2.2 diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs index 91aafdae76..ad2596931d 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Audio; @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Taiko.Audio foreach (var s in samplePoints) { var centre = s.GetSampleInfo(); - var rim = s.GetSampleInfo(SampleInfo.HIT_CLAP); + var rim = s.GetSampleInfo(HitSampleInfo.HIT_CLAP); // todo: this is ugly centre.Namespace = "taiko"; @@ -43,9 +43,9 @@ namespace osu.Game.Rulesets.Taiko.Audio } } - private SkinnableSound addSound(SampleInfo sampleInfo) + private SkinnableSound addSound(HitSampleInfo hitSampleInfo) { - var drawable = new SkinnableSound(sampleInfo); + var drawable = new SkinnableSound(hitSampleInfo); Sounds.Add(drawable); return drawable; } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs index 36f6df7869..b595f43fbb 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmap.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Beatmaps @@ -23,19 +23,19 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { Name = @"Hit Count", Content = hits.ToString(), - Icon = FontAwesome.fa_circle_o + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Drumroll Count", Content = drumrolls.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle }, new BeatmapStatistic { Name = @"Swell Count", Content = swells.ToString(), - Icon = FontAwesome.fa_circle + Icon = FontAwesome.Regular.Circle } }; } diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index c4a84f416e..f0cf8d9c7d 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; @@ -79,9 +79,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps var curveData = obj as IHasCurve; // Old osu! used hit sounding to determine various hit type information - List samples = obj.Samples; + List samples = obj.Samples; - bool strong = samples.Any(s => s.Name == SampleInfo.HIT_FINISH); + bool strong = samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); if (distanceData != null) { @@ -117,14 +117,15 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); + List> allSamples = curveData != null ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; + for (double j = obj.StartTime; j <= obj.StartTime + taikoDuration + tickSpacing / 8; j += tickSpacing) { - List currentSamples = allSamples[i]; - bool isRim = currentSamples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); - strong = currentSamples.Any(s => s.Name == SampleInfo.HIT_FINISH); + List currentSamples = allSamples[i]; + bool isRim = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); + strong = currentSamples.Any(s => s.Name == HitSampleInfo.HIT_FINISH); if (isRim) { @@ -174,7 +175,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps } else { - bool isRim = samples.Any(s => s.Name == SampleInfo.HIT_CLAP || s.Name == SampleInfo.HIT_WHISTLE); + bool isRim = samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP || s.Name == HitSampleInfo.HIT_WHISTLE); if (isRim) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs new file mode 100644 index 0000000000..24345275c1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Preprocessing/TaikoDifficultyHitObject.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Preprocessing +{ + public class TaikoDifficultyHitObject : DifficultyHitObject + { + public readonly bool HasTypeChange; + + public TaikoDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) + : base(hitObject, lastObject, clockRate) + { + HasTypeChange = lastObject is RimHit != hitObject is RimHit; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs new file mode 100644 index 0000000000..c6fe273b50 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Difficulty.Skills +{ + public class Strain : Skill + { + private const double rhythm_change_base_threshold = 0.2; + private const double rhythm_change_base = 2.0; + + protected override double SkillMultiplier => 1; + protected override double StrainDecayBase => 0.3; + + private ColourSwitch lastColourSwitch = ColourSwitch.None; + + private int sameColourCount = 1; + + protected override double StrainValueOf(DifficultyHitObject current) + { + double addition = 1; + + // We get an extra addition if we are not a slider or spinner + if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) + { + if (hasColourChange(current)) + addition += 0.75; + + if (hasRhythmChange(current)) + addition += 1; + } + else + { + lastColourSwitch = ColourSwitch.None; + sameColourCount = 1; + } + + double additionFactor = 1; + + // Scale the addition factor linearly from 0.4 to 1 for DeltaTime from 0 to 50 + if (current.DeltaTime < 50) + additionFactor = 0.4 + 0.6 * current.DeltaTime / 50; + + return additionFactor * addition; + } + + private bool hasRhythmChange(DifficultyHitObject current) + { + // We don't want a division by zero if some random mapper decides to put two HitObjects at the same time. + if (current.DeltaTime == 0 || Previous.Count == 0 || Previous[0].DeltaTime == 0) + return false; + + double timeElapsedRatio = Math.Max(Previous[0].DeltaTime / current.DeltaTime, current.DeltaTime / Previous[0].DeltaTime); + + if (timeElapsedRatio >= 8) + return false; + + double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; + + return difference > rhythm_change_base_threshold && difference < 1 - rhythm_change_base_threshold; + } + + private bool hasColourChange(DifficultyHitObject current) + { + var taikoCurrent = (TaikoDifficultyHitObject)current; + + if (!taikoCurrent.HasTypeChange) + { + sameColourCount++; + return false; + } + + var oldColourSwitch = lastColourSwitch; + var newColourSwitch = sameColourCount % 2 == 0 ? ColourSwitch.Even : ColourSwitch.Odd; + + lastColourSwitch = newColourSwitch; + sameColourCount = 1; + + // We only want a bonus if the parity of the color switch changes + return oldColourSwitch != ColourSwitch.None && oldColourSwitch != newColourSwitch; + } + + private enum ColourSwitch + { + None, + Even, + Odd + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index d18d03b1db..75d3807bba 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -1,8 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -10,10 +9,5 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public double GreatHitWindow; public int MaxCombo; - - public TaikoDifficultyAttributes(Mod[] mods, double starRating) - : base(mods, starRating) - { - } } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 8b527c2c82..c8f3e18911 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -1,137 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; +using osu.Game.Rulesets.Taiko.Difficulty.Skills; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Difficulty { - internal class TaikoDifficultyCalculator : DifficultyCalculator + public class TaikoDifficultyCalculator : DifficultyCalculator { private const double star_scaling_factor = 0.04125; - /// - /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP. - /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. - /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. - /// - private const double strain_step = 400; - - /// - /// The weighting of each strain value decays to this number * it's previous value - /// - private const double decay_weight = 0.9; - public TaikoDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) : base(ruleset, beatmap) { } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) { - if (!beatmap.HitObjects.Any()) - return new TaikoDifficultyAttributes(mods, 0); + if (beatmap.HitObjects.Count == 0) + return new TaikoDifficultyAttributes { Mods = mods, Skills = skills }; - var difficultyHitObjects = new List(); - - foreach (var hitObject in beatmap.HitObjects) - difficultyHitObjects.Add(new TaikoHitObjectDifficulty((TaikoHitObject)hitObject)); - - // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. - difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); - - if (!calculateStrainValues(difficultyHitObjects, timeRate)) - return new DifficultyAttributes(mods, 0); - - double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor; - - return new TaikoDifficultyAttributes(mods, starRating) + return new TaikoDifficultyAttributes { - // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future - GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / timeRate, - MaxCombo = beatmap.HitObjects.Count(h => h is Hit) + StarRating = skills.Single().DifficultyValue() * star_scaling_factor, + Mods = mods, + // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future + GreatHitWindow = (int)(beatmap.HitObjects.First().HitWindows.Great / 2) / clockRate, + MaxCombo = beatmap.HitObjects.Count(h => h is Hit), + Skills = skills }; } - private bool calculateStrainValues(List objects, double timeRate) + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) { - // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. - using (var hitObjectsEnumerator = objects.GetEnumerator()) - { - if (!hitObjectsEnumerator.MoveNext()) return false; - - TaikoHitObjectDifficulty current = hitObjectsEnumerator.Current; - - // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. - while (hitObjectsEnumerator.MoveNext()) - { - var next = hitObjectsEnumerator.Current; - next?.CalculateStrains(current, timeRate); - current = next; - } - - return true; - } + for (int i = 1; i < beatmap.HitObjects.Count; i++) + yield return new TaikoDifficultyHitObject(beatmap.HitObjects[i], beatmap.HitObjects[i - 1], clockRate); } - private double calculateDifficulty(List objects, double timeRate) - { - double actualStrainStep = strain_step * timeRate; - - // Find the highest strain value within each strain step - List highestStrains = new List(); - double intervalEndTime = actualStrainStep; - double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval - - TaikoHitObjectDifficulty previousHitObject = null; - foreach (var hitObject in objects) - { - // While we are beyond the current interval push the currently available maximum to our strain list - while (hitObject.BaseHitObject.StartTime > intervalEndTime) - { - highestStrains.Add(maximumStrain); - - // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay - // until the beginning of the next interval. - if (previousHitObject == null) - { - maximumStrain = 0; - } - else - { - double decay = Math.Pow(TaikoHitObjectDifficulty.DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); - maximumStrain = previousHitObject.Strain * decay; - } - - // Go to the next time interval - intervalEndTime += actualStrainStep; - } - - // Obtain maximum strain - maximumStrain = Math.Max(hitObject.Strain, maximumStrain); - - previousHitObject = hitObject; - } - - // Build the weighted sum over the highest strains for each interval - double difficulty = 0; - double weight = 1; - highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - - foreach (double strain in highestStrains) - { - difficulty += weight * strain; - weight *= decay_weight; - } - - return difficulty; - } + protected override Skill[] CreateSkills(IBeatmap beatmap) => new Skill[] { new Strain() }; protected override Mod[] DifficultyAdjustmentMods => new Mod[] { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 734d5d0ad7..70249db0f6 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -9,6 +9,7 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko.Difficulty { @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private int countMeh; private int countMiss; - public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + public TaikoPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) : base(ruleset, beatmap, score) { } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs new file mode 100644 index 0000000000..604daa929f --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollJudgement.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Judgements +{ + public class TaikoDrumRollJudgement : TaikoJudgement + { + public override bool AffectsCombo => false; + + protected override double HealthIncreaseFor(HitResult result) + { + // Drum rolls can be ignored with no health penalty + switch (result) + { + case HitResult.Miss: + return 0; + + default: + return base.HealthIncreaseFor(result); + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs index 446dd0d11b..a617028f1c 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoDrumRollTickJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; @@ -13,10 +13,23 @@ namespace osu.Game.Rulesets.Taiko.Judgements { switch (result) { - default: - return 0; case HitResult.Great: return 200; + + default: + return 0; + } + } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Great: + return 0.15; + + default: + return 0; } } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs deleted file mode 100644 index 81a1bd1344..0000000000 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoIntermediateSwellJudgement.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.Taiko.Judgements -{ - public class TaikoIntermediateSwellJudgement : TaikoJudgement - { - public override HitResult MaxResult => HitResult.Great; - - public override bool AffectsCombo => false; - - /// - /// Computes the numeric result value for the combo portion of the score. - /// - /// The result to compute the value for. - /// The numeric result value. - protected override int NumericResultFor(HitResult result) => 0; - } -} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs index 9b1f7a08b5..eb5f443365 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -10,21 +10,36 @@ namespace osu.Game.Rulesets.Taiko.Judgements { public override HitResult MaxResult => HitResult.Great; - /// - /// Computes the numeric result value for the combo portion of the score. - /// - /// The result to compute the value for. - /// The numeric result value. protected override int NumericResultFor(HitResult result) { switch (result) { - default: - return 0; case HitResult.Good: return 100; + case HitResult.Great: return 300; + + default: + return 0; + } + } + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -1.0; + + case HitResult.Good: + return 1.1; + + case HitResult.Great: + return 3.0; + + default: + return 0; } } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs index ccfdeb5b0e..e045ea324f 100644 --- a/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoStrongJudgement.cs @@ -1,10 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Judgements { public class TaikoStrongJudgement : TaikoJudgement { + // MainObject already changes the HP + protected override double HealthIncreaseFor(HitResult result) => 0; + public override bool AffectsCombo => false; } } diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs new file mode 100644 index 0000000000..29be5e0eac --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellJudgement.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Judgements +{ + public class TaikoSwellJudgement : TaikoJudgement + { + public override bool AffectsCombo => false; + + protected override double HealthIncreaseFor(HitResult result) + { + switch (result) + { + case HitResult.Miss: + return -0.65; + + default: + return 0; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs new file mode 100644 index 0000000000..b28b6a0d17 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Judgements/TaikoSwellTickJudgement.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Taiko.Judgements +{ + public class TaikoSwellTickJudgement : TaikoJudgement + { + public override bool AffectsCombo => false; + + protected override int NumericResultFor(HitResult result) => 0; + + protected override double HealthIncreaseFor(HitResult result) => 0; + } +} diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index c63bf10542..5b890b3d03 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -1,24 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; +using osu.Game.Scoring; using osu.Game.Users; namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModAutoplay : ModAutoplay { - protected override Score CreateReplayScore(Beatmap beatmap) + public override Score CreateReplayScore(IBeatmap beatmap) => new Score { - return new Score - { - User = new User { Username = "mekkadosu!" }, - Replay = new TaikoAutoGenerator(beatmap).Generate(), - }; - } + ScoreInfo = new ScoreInfo { User = new User { Username = "mekkadosu!" } }, + Replay = new TaikoAutoGenerator(beatmap).Generate(), + }; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs index 80d8da6cf6..84aa5e6bba 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs index 21e48f2c8c..bc38213acc 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index 16364964f8..c51b47dc6e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 79dad84696..b7db3307ad 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; @@ -21,10 +22,10 @@ namespace osu.Game.Rulesets.Taiko.Mods private TaikoPlayfield playfield; - public override void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - playfield = (TaikoPlayfield)rulesetContainer.Playfield; - base.ApplyToRulesetContainer(rulesetContainer); + playfield = (TaikoPlayfield)drawableRuleset.Playfield; + base.ApplyToDrawableRuleset(drawableRuleset); } private class TaikoFlashlight : Flashlight @@ -48,9 +49,9 @@ namespace osu.Game.Rulesets.Taiko.Mods return default_flashlight_size; } - protected override void OnComboChange(int newCombo) + protected override void OnComboChange(ValueChangedEvent e) { - this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(newCombo)), FLASHLIGHT_FADE_DURATION); + this.TransformTo(nameof(FlashlightSize), new Vector2(0, getSizeFor(e.NewValue)), FLASHLIGHT_FADE_DURATION); } protected override string FragmentShader => "CircularFlashlight"; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs index ef609b893d..68d6305fbf 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 908778d4d6..49d225cdb5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index f4e12ede21..a6f902208c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; @@ -9,5 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public override string Description => @"Beats fade out before you hit them!"; public override double ScoreMultiplier => 1.06; + public override bool HasImplementation => false; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs index abe4b2e25e..e45081b6d6 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs index 5a8e903fc6..bf1006f1aa 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs index 027feed112..b107b14a03 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs index fd72291f1f..7be70d9ac3 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs index 6ed1ad3bda..7a0f6c7cd1 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index f7496642dd..a07012fd71 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Taiko.Objects { diff --git a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs index 898c562ea9..a6354b16ed 100644 --- a/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/CentreHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Taiko.Objects { diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs index d6f8522b77..f8909fb98c 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLine.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -44,17 +44,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables RelativeSizeAxes = Axes.Y; Width = tracker_width; - InternalChildren = new[] + AddInternal(Tracker = new Box { - Tracker = new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - EdgeSmoothness = new Vector2(0.5f, 0), - Alpha = 0.75f - } - }; + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + EdgeSmoothness = new Vector2(0.5f, 0), + Alpha = 0.75f + }); } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs index 3a1b4e33d2..4d3a1a3f8a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableBarLineMajor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs index a6e9972dd3..4979135f50 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableCentreHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 632dff4525..9b4df74a61 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs index a70d7bde0e..9259c693d9 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRollTick.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 6f7264e23b..4c8d5d5204 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -98,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables circlePiece?.FlashBox.FinishTransforms(); var offset = !AllJudged ? 0 : Time.Current - HitObject.StartTime; + using (BeginDelayedSequence(HitObject.StartTime - Time.Current + offset, true)) { switch (State.Value) @@ -108,15 +109,18 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables UnproxyContent(); this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire(); break; + case ArmedState.Miss: this.FadeOut(100) .Expire(); break; + case ArmedState.Hit: // If we're far enough away from the left stage, we should bring outselves in front of it ProxyContent(); var flash = circlePiece?.FlashBox; + if (flash != null) { flash.FadeTo(0.9f); @@ -173,13 +177,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables if (!userTriggered) { - if (timeOffset > second_hit_window) + if (timeOffset - MainObject.Result.TimeOffset > second_hit_window) ApplyResult(r => r.Type = HitResult.Miss); return; } - if (Math.Abs(MainObject.Result.TimeOffset - timeOffset) < second_hit_window) - ApplyResult(r => r.Type = HitResult.Great); + if (Math.Abs(timeOffset - MainObject.Result.TimeOffset) <= second_hit_window) + ApplyResult(r => r.Type = MainObject.Result.Type); } public override bool OnPressed(TaikoAction action) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs index 188cafe1db..5a12d71cea 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableRimHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs index b27de3832a..98a2e8a721 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableStrongNestedHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Judgements; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 1412bd3cea..5ec9dc61e2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { if (userTriggered) { - var nextTick = ticks.FirstOrDefault(j => !j.IsHit); + var nextTick = ticks.Find(j => !j.IsHit); nextTick?.TriggerResult(HitResult.Great); @@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables using (BeginAbsoluteSequence(HitObject.StartTime - preempt, true)) targetRing.ScaleTo(target_ring_scale, preempt * 4, Easing.OutQuint); break; + case ArmedState.Miss: case ArmedState.Hit: this.FadeOut(out_transition_time, Easing.Out); @@ -229,6 +230,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables // Ensure alternating centre and rim hits if (lastWasCentre == isCentre) return false; + lastWasCentre = isCentre; UpdateResult(true); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs index 36c468c6d6..41a8fd9a75 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwellTick.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Taiko.Objects.Drawables { - public class DrawableSwellTick : DrawableTaikoHitObject + public class DrawableSwellTick : DrawableTaikoHitObject { public override bool DisplayResult => false; - public DrawableSwellTick(TaikoHitObject hitObject) + public DrawableSwellTick(SwellTick hitObject) : base(hitObject) { } diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index cee9b40730..bd45b52d7b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Input.Bindings; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected DrawableTaikoHitObject(TaikoHitObject hitObject) : base(hitObject) { - InternalChildren = new[] + AddRangeInternal(new[] { nonProxiedContent = new Container { @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Child = Content = new Container { RelativeSizeAxes = Axes.Both } }, proxiedContent = new ProxiedContentContainer { RelativeSizeAxes = Axes.Both } - }; + }); } /// @@ -49,6 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected void ProxyContent() { if (isProxied) return; + isProxied = true; nonProxiedContent.Remove(Content); @@ -62,6 +63,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected void UnproxyContent() { if (!isProxied) return; + isProxied = false; proxiedContent.Remove(Content); @@ -109,6 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables MainPiece.KiaiMode = HitObject.Kiai; var strongObject = HitObject.NestedHitObjects.OfType().FirstOrDefault(); + if (strongObject != null) { var strongHit = CreateStrongHit(strongObject); @@ -119,7 +122,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables } // Normal and clap samples are handled by the drum - protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != SampleInfo.HIT_NORMAL && s.Name != SampleInfo.HIT_CLAP); + protected override IEnumerable GetSamples() => HitObject.Samples.Where(s => s.Name != HitSampleInfo.HIT_NORMAL && s.Name != HitSampleInfo.HIT_CLAP); protected override string SampleNamespace => "Taiko"; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs index b4cb3a35f6..7ed61ede96 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CentreHitSymbolPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index fdb73235de..b7db819717 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -9,6 +9,7 @@ using osu.Game.Graphics.Backgrounds; using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces /// public override Color4 AccentColour { - get { return base.AccentColour; } + get => base.AccentColour; set { base.AccentColour = value; @@ -46,7 +47,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces /// public override bool KiaiMode { - get { return base.KiaiMode; } + get => base.KiaiMode; set { base.KiaiMode = value; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs index c5b8228439..7e3272e42b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/ElongatedCirclePiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs index 613d8debb5..e4c964a884 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/RimHitSymbolPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs index 8455f4af58..0ed9923924 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/SwellSymbolPiece.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces { @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces new SpriteIcon { RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_asterisk, + Icon = FontAwesome.Solid.Asterisk, Shadow = false } }; diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs index 6795338a83..773e3ae907 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TaikoPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; using osuTK.Graphics; @@ -11,26 +11,25 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces public class TaikoPiece : BeatSyncedContainer, IHasAccentColour { private Color4 accentColour; + /// /// The colour of the inner circle and outer glows. /// public virtual Color4 AccentColour { - get { return accentColour; } - set { accentColour = value; } + get => accentColour; + set => accentColour = value; } private bool kiaiMode; + /// /// Whether Kiai mode effects are enabled for this circle piece. /// public virtual bool KiaiMode { - get { return kiaiMode; } - set - { - kiaiMode = value; - } + get => kiaiMode; + set => kiaiMode = value; } public TaikoPiece() diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs index 7327b9c565..83cf7a64ec 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/TickPiece.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -23,9 +23,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces private const float tick_size = 0.35f; private bool filled; + public bool Filled { - get { return filled; } + get => filled; set { filled = value; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 405ea85f0d..1d25735fe3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -1,10 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; using System; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { @@ -68,6 +70,7 @@ namespace osu.Game.Rulesets.Taiko.Objects return; bool first = true; + for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { AddNested(new DrumRollTick @@ -81,5 +84,7 @@ namespace osu.Game.Rulesets.Taiko.Objects first = false; } } + + public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs index 967d5acfd7..8448036f76 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRollTick.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Taiko.Judgements; diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 0b47aa490b..6cc9357580 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Taiko.Objects { diff --git a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs index 2e52e57f1f..6f6b089e03 100644 --- a/osu.Game.Rulesets.Taiko/Objects/RimHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/RimHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Taiko.Objects { diff --git a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs index fac3705110..2a03c23934 100644 --- a/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/StrongHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Taiko.Judgements; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 702bf63bf5..befa728570 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -1,8 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { @@ -17,7 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int RequiredHits = 10; - public override bool IsStrong { set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); } + public override bool IsStrong + { + set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); + } protected override void CreateNestedHitObjects() { @@ -26,5 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects for (int i = 0; i < RequiredHits; i++) AddNested(new SwellTick()); } + + public override Judgement CreateJudgement() => new TaikoSwellJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs index 49eb6d2a15..c2ae784b2a 100644 --- a/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs +++ b/osu.Game.Rulesets.Taiko/Objects/SwellTick.cs @@ -1,9 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { public class SwellTick : TaikoHitObject { + public override Judgement CreateJudgement() => new TaikoSwellTickJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 9c86b60688..3592d73004 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs deleted file mode 100644 index 70a516af79..0000000000 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObjectDifficulty.cs +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Rulesets.Taiko.Objects -{ - internal class TaikoHitObjectDifficulty - { - /// - /// Factor by how much individual / overall strain decays per second. - /// - /// - /// These values are results of tweaking a lot and taking into account general feedback. - /// - internal const double DECAY_BASE = 0.30; - - private const double type_change_bonus = 0.75; - private const double rhythm_change_bonus = 1.0; - private const double rhythm_change_base_threshold = 0.2; - private const double rhythm_change_base = 2.0; - - internal TaikoHitObject BaseHitObject; - - /// - /// Measures note density in a way - /// - internal double Strain = 1; - - private double timeElapsed; - private int sameTypeSince = 1; - - private bool isRim => BaseHitObject is RimHit; - - public TaikoHitObjectDifficulty(TaikoHitObject baseHitObject) - { - BaseHitObject = baseHitObject; - } - - internal void CalculateStrains(TaikoHitObjectDifficulty previousHitObject, double timeRate) - { - // Rather simple, but more specialized things are inherently inaccurate due to the big difference playstyles and opinions make. - // See Taiko feedback thread. - timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; - double decay = Math.Pow(DECAY_BASE, timeElapsed / 1000); - - double addition = 1; - - // Only if we are no slider or spinner we get an extra addition - if (previousHitObject.BaseHitObject is Hit && BaseHitObject is Hit - && BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime < 1000) // And we only want to check out hitobjects which aren't so far in the past - { - addition += typeChangeAddition(previousHitObject); - addition += rhythmChangeAddition(previousHitObject); - } - - double additionFactor = 1.0; - // Scale AdditionFactor linearly from 0.4 to 1 for TimeElapsed from 0 to 50 - if (timeElapsed < 50.0) - additionFactor = 0.4 + 0.6 * timeElapsed / 50.0; - - Strain = previousHitObject.Strain * decay + addition * additionFactor; - } - - private TypeSwitch lastTypeSwitchEven = TypeSwitch.None; - private double typeChangeAddition(TaikoHitObjectDifficulty previousHitObject) - { - // If we don't have the same hit type, trigger a type change! - if (previousHitObject.isRim != isRim) - { - lastTypeSwitchEven = previousHitObject.sameTypeSince % 2 == 0 ? TypeSwitch.Even : TypeSwitch.Odd; - - // We only want a bonus if the parity of the type switch changes! - switch (previousHitObject.lastTypeSwitchEven) - { - case TypeSwitch.Even: - if (lastTypeSwitchEven == TypeSwitch.Odd) - return type_change_bonus; - break; - case TypeSwitch.Odd: - if (lastTypeSwitchEven == TypeSwitch.Even) - return type_change_bonus; - break; - } - } - // No type change? Increment counter and keep track of last type switch - else - { - lastTypeSwitchEven = previousHitObject.lastTypeSwitchEven; - sameTypeSince = previousHitObject.sameTypeSince + 1; - } - - return 0; - } - - private double rhythmChangeAddition(TaikoHitObjectDifficulty previousHitObject) - { - // We don't want a division by zero if some random mapper decides to put 2 HitObjects at the same time. - if (timeElapsed == 0 || previousHitObject.timeElapsed == 0) - return 0; - - double timeElapsedRatio = Math.Max(previousHitObject.timeElapsed / timeElapsed, timeElapsed / previousHitObject.timeElapsed); - - if (timeElapsedRatio >= 8) - return 0; - - double difference = Math.Log(timeElapsedRatio, rhythm_change_base) % 1.0; - - if (isWithinChangeThreshold(difference)) - return rhythm_change_bonus; - - return 0; - } - - private bool isWithinChangeThreshold(double value) - { - return value > rhythm_change_base_threshold && value < 1 - rhythm_change_base_threshold; - } - - private enum TypeSwitch - { - None, - Even, - Odd - } - } -} diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs index 289f084a45..f232919cbf 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitWindows.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps; @@ -14,15 +14,27 @@ namespace osu.Game.Rulesets.Taiko.Objects { { HitResult.Great, (100, 70, 40) }, { HitResult.Good, (240, 160, 100) }, - { HitResult.Meh, (270, 190, 140) }, - { HitResult.Miss, (400, 400, 400) }, + { HitResult.Miss, (270, 190, 140) }, }; + public override bool IsHitResultAllowed(HitResult result) + { + switch (result) + { + case HitResult.Great: + case HitResult.Good: + case HitResult.Miss: + return true; + + default: + return false; + } + } + public override void SetDifficulty(double difficulty) { Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]); Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]); - Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]); Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]); } } diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs index 26f70bc927..81f15fb293 100644 --- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs +++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Runtime.CompilerServices; @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.iOS")] diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index e7b2789010..422ba748e3 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -1,31 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Replays; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Replays; -using osu.Game.Users; +using osu.Game.Rulesets.Taiko.Beatmaps; namespace osu.Game.Rulesets.Taiko.Replays { - public class TaikoAutoGenerator : AutoGenerator + public class TaikoAutoGenerator : AutoGenerator { + public new TaikoBeatmap Beatmap => (TaikoBeatmap)base.Beatmap; + private const double swell_hit_speed = 50; - public TaikoAutoGenerator(Beatmap beatmap) + public TaikoAutoGenerator(IBeatmap beatmap) : base(beatmap) { - Replay = new Replay - { - User = new User - { - Username = @"Autoplay", - } - }; + Replay = new Replay(); } protected Replay Replay; @@ -55,6 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Replays int count = 0; int req = swell.RequiredHits; double hitRate = Math.Min(swell_hit_speed, swell.Duration / req); + for (double j = h.StartTime; j < endTime; j += hitRate) { TaikoAction action; @@ -65,12 +63,15 @@ namespace osu.Game.Rulesets.Taiko.Replays case 0: action = TaikoAction.LeftCentre; break; + case 1: action = TaikoAction.LeftRim; break; + case 2: action = TaikoAction.RightCentre; break; + case 3: action = TaikoAction.RightRim; break; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs index ab7856eb8f..97337acc45 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoFramedReplayInputHandler.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Replays; using System.Collections.Generic; using System.Linq; using osu.Framework.Input.StateChanges; +using osu.Game.Replays; namespace osu.Game.Rulesets.Taiko.Replays { @@ -17,6 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Replays protected override bool IsImportant(TaikoReplayFrame frame) => frame.Actions.Any(); - public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame.Actions } }; + public override List GetPendingInputs() => new List { new ReplayState { PressedActions = CurrentFrame?.Actions ?? new List() } }; } } diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs index 2177a3cbdc..5203415e90 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps; +using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Types; namespace osu.Game.Rulesets.Taiko.Replays diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu new file mode 100644 index 0000000000..33510eceb7 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test-strong.osu @@ -0,0 +1,257 @@ +osu file format v14 + +[General] +Mode: 1 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +62500,-500,4,2,1,50,0,0 +71000,-100,4,2,1,50,0,0 + +[HitObjects] +// Same as diffcalc-test with finishers on every note +142,122,0,5,4,0:0:0:0: +142,122,125,1,4,0:0:0:0: +142,122,250,1,4,0:0:0:0: +142,122,375,1,4,0:0:0:0: +142,122,500,1,4,0:0:0:0: +142,122,625,1,4,0:0:0:0: +142,122,750,1,4,0:0:0:0: +142,122,875,1,4,0:0:0:0: +142,122,1000,1,4,0:0:0:0: +142,122,1125,1,4,0:0:0:0: +142,122,1250,1,4,0:0:0:0: +142,122,1375,1,4,0:0:0:0: +142,122,1500,1,4,0:0:0:0: +119,106,2500,1,6,0:0:0:0: +119,106,2625,1,6,0:0:0:0: +119,106,2750,1,6,0:0:0:0: +119,106,2875,1,6,0:0:0:0: +119,106,3000,1,6,0:0:0:0: +119,106,3125,1,6,0:0:0:0: +119,106,3250,1,6,0:0:0:0: +119,106,3375,1,6,0:0:0:0: +119,106,3500,1,6,0:0:0:0: +119,106,3625,1,6,0:0:0:0: +119,106,3750,1,6,0:0:0:0: +119,106,3875,1,6,0:0:0:0: +119,106,4000,1,6,0:0:0:0: +136,90,5000,1,4,0:0:0:0: +136,90,5125,1,6,0:0:0:0: +136,90,5250,1,4,0:0:0:0: +136,90,5375,1,6,0:0:0:0: +136,90,5500,1,4,0:0:0:0: +136,90,5625,1,6,0:0:0:0: +136,90,5750,1,4,0:0:0:0: +136,90,5875,1,6,0:0:0:0: +136,90,6000,1,4,0:0:0:0: +136,90,6125,1,6,0:0:0:0: +136,90,6250,1,4,0:0:0:0: +136,90,6375,1,6,0:0:0:0: +136,90,6500,1,4,0:0:0:0: +86,113,7500,1,4,0:0:0:0: +86,113,7625,1,4,0:0:0:0: +86,113,7750,1,6,0:0:0:0: +86,113,7875,1,6,0:0:0:0: +86,113,8000,1,4,0:0:0:0: +86,113,8125,1,4,0:0:0:0: +86,113,8250,1,6,0:0:0:0: +86,113,8375,1,6,0:0:0:0: +86,113,8500,1,4,0:0:0:0: +86,113,8625,1,4,0:0:0:0: +86,113,8750,1,6,0:0:0:0: +86,113,8875,1,6,0:0:0:0: +86,113,9000,1,4,0:0:0:0: +146,90,10000,1,4,0:0:0:0: +146,90,10125,1,4,0:0:0:0: +146,90,10250,1,4,0:0:0:0: +146,90,10375,1,6,0:0:0:0: +146,90,10500,1,6,0:0:0:0: +146,90,10625,1,6,0:0:0:0: +146,90,10750,1,4,0:0:0:0: +146,90,10875,1,4,0:0:0:0: +146,90,11000,1,4,0:0:0:0: +146,90,11125,1,6,0:0:0:0: +146,90,11250,1,6,0:0:0:0: +146,90,11375,1,6,0:0:0:0: +146,90,11500,1,4,0:0:0:0: +146,90,11625,1,4,0:0:0:0: +146,90,11750,1,4,0:0:0:0: +146,90,11875,1,6,0:0:0:0: +146,90,12000,1,6,0:0:0:0: +146,90,12125,1,6,0:0:0:0: +146,90,12250,1,4,0:0:0:0: +146,90,12375,1,4,0:0:0:0: +146,90,12500,1,4,0:0:0:0: +69,99,13500,1,4,0:0:0:0: +69,99,13625,1,4,0:0:0:0: +69,99,13750,1,4,0:0:0:0: +69,99,13875,1,6,0:0:0:0: +69,99,14000,1,4,0:0:0:0: +69,99,14125,1,4,0:0:0:0: +69,99,14250,1,4,0:0:0:0: +69,99,14375,1,6,0:0:0:0: +69,99,14500,1,4,0:0:0:0: +69,99,14625,1,4,0:0:0:0: +69,99,14750,1,4,0:0:0:0: +69,99,14875,1,6,0:0:0:0: +69,99,15000,1,4,0:0:0:0: +69,99,15125,1,4,0:0:0:0: +69,99,15250,1,4,0:0:0:0: +69,99,15375,1,6,0:0:0:0: +69,99,15500,1,4,0:0:0:0: +83,89,16500,1,4,0:0:0:0: +83,89,16625,1,6,0:0:0:0: +83,89,16750,1,6,0:0:0:0: +83,89,16875,1,4,0:0:0:0: +83,89,17000,1,4,0:0:0:0: +83,89,17125,1,4,0:0:0:0: +83,89,17250,1,6,0:0:0:0: +83,89,17375,1,6,0:0:0:0: +83,89,17500,1,6,0:0:0:0: +83,89,17625,1,6,0:0:0:0: +83,89,17750,1,4,0:0:0:0: +83,89,17875,1,4,0:0:0:0: +83,89,18000,1,4,0:0:0:0: +83,89,18125,1,4,0:0:0:0: +83,89,18250,1,4,0:0:0:0: +83,89,18375,1,6,0:0:0:0: +83,89,18500,1,6,0:0:0:0: +83,89,18625,1,6,0:0:0:0: +83,89,18750,1,6,0:0:0:0: +83,89,18875,1,4,0:0:0:0: +83,89,19000,1,4,0:0:0:0: +83,89,19125,1,4,0:0:0:0: +83,89,19250,1,4,0:0:0:0: +83,89,19375,1,6,0:0:0:0: +83,89,19500,1,6,0:0:0:0: +83,89,19625,1,4,0:0:0:0: +84,122,20500,1,4,0:0:0:0: +84,122,20625,2,4,L|217:123,1,120 +84,122,21125,1,4,0:0:0:0: +84,122,21250,2,4,L|217:123,1,120 +84,122,21750,1,4,0:0:0:0: +84,122,21875,2,4,L|217:123,1,120 +84,122,22375,1,4,0:0:0:0: +84,122,22500,2,4,L|217:123,1,120 +84,122,23000,1,4,0:0:0:0: +84,122,23125,2,4,L|217:123,1,120 +99,106,24500,1,4,0:0:0:0: +99,106,24625,1,4,0:0:0:0: +99,106,24750,2,4,L|194:107,1,80 +99,106,25125,1,4,0:0:0:0: +99,106,25250,1,4,0:0:0:0: +99,106,25375,2,4,L|194:107,1,80 +99,106,25750,1,4,0:0:0:0: +99,106,25875,1,4,0:0:0:0: +99,106,26000,2,4,L|194:107,1,80 +99,106,26375,1,4,0:0:0:0: +99,106,26500,1,4,0:0:0:0: +99,106,26625,2,4,L|194:107,1,80 +99,106,27000,1,4,0:0:0:0: +99,106,27125,1,4,0:0:0:0: +99,106,27250,2,4,L|194:107,1,80 +121,103,28500,1,4,0:0:0:0: +121,103,28625,1,4,0:0:0:0: +121,103,28750,1,4,0:0:0:0: +121,103,28875,2,4,L|190:103,1,40 +121,103,29125,1,4,0:0:0:0: +121,103,29250,1,4,0:0:0:0: +121,103,29375,1,4,0:0:0:0: +121,103,29500,2,4,L|190:103,1,40 +121,103,29750,1,4,0:0:0:0: +121,103,29875,1,4,0:0:0:0: +121,103,30000,1,4,0:0:0:0: +121,103,30125,2,4,L|190:103,1,40 +121,103,30375,1,4,0:0:0:0: +121,103,30500,1,4,0:0:0:0: +121,103,30625,1,4,0:0:0:0: +121,103,30750,2,4,L|190:103,1,40 +121,103,31000,1,4,0:0:0:0: +121,103,31125,1,4,0:0:0:0: +121,103,31250,1,4,0:0:0:0: +121,103,31375,2,4,L|190:103,1,40 +121,103,32500,1,4,0:0:0:0: +121,103,32625,1,6,0:0:0:0: +121,103,32750,1,4,0:0:0:0: +121,103,32875,2,4,L|190:103,1,40 +121,103,33125,1,4,0:0:0:0: +121,103,33250,1,6,0:0:0:0: +121,103,33375,1,4,0:0:0:0: +121,103,33500,2,4,L|190:103,1,40 +121,103,33750,1,4,0:0:0:0: +121,103,33875,1,6,0:0:0:0: +121,103,34000,1,4,0:0:0:0: +121,103,34125,2,4,L|190:103,1,40 +121,103,34375,1,4,0:0:0:0: +121,103,34500,1,6,0:0:0:0: +121,103,34625,1,4,0:0:0:0: +121,103,34750,2,4,L|190:103,1,40 +121,103,35000,1,4,0:0:0:0: +121,103,35125,1,6,0:0:0:0: +121,103,35250,1,4,0:0:0:0: +121,103,35375,2,4,L|190:103,1,40 +121,103,36500,1,4,0:0:0:0: +121,103,36625,1,4,0:0:0:0: +121,103,36750,1,6,0:0:0:0: +121,103,36875,2,4,L|190:103,1,40 +121,103,37125,1,4,0:0:0:0: +121,103,37250,1,4,0:0:0:0: +121,103,37375,1,6,0:0:0:0: +121,103,37500,2,4,L|190:103,1,40 +121,103,37750,1,4,0:0:0:0: +121,103,37875,1,4,0:0:0:0: +121,103,38000,1,6,0:0:0:0: +121,103,38125,2,4,L|190:103,1,40 +121,103,38375,1,4,0:0:0:0: +121,103,38500,1,4,0:0:0:0: +121,103,38625,1,6,0:0:0:0: +121,103,38750,2,4,L|190:103,1,40 +121,103,39000,1,4,0:0:0:0: +121,103,39125,1,4,0:0:0:0: +121,103,39250,1,6,0:0:0:0: +121,103,39375,2,4,L|190:103,1,40 +107,106,40500,1,4,0:0:0:0: +107,106,40625,1,4,0:0:0:0: +107,106,40750,1,6,0:0:0:0: +107,106,40875,1,6,0:0:0:0: +46,112,41000,2,4,L|214:112,1,160 +107,106,41625,1,4,0:0:0:0: +107,106,41750,1,4,0:0:0:0: +107,106,41875,1,6,0:0:0:0: +107,106,42000,1,6,0:0:0:0: +46,112,42125,2,4,L|214:112,1,160 +107,106,42750,1,4,0:0:0:0: +107,106,42875,1,4,0:0:0:0: +107,106,43000,1,6,0:0:0:0: +107,106,43125,1,6,0:0:0:0: +46,112,43250,2,4,L|214:112,1,160 +107,106,43875,1,4,0:0:0:0: +107,106,44000,1,4,0:0:0:0: +107,106,44125,1,6,0:0:0:0: +107,106,44250,1,6,0:0:0:0: +46,112,44375,2,4,L|214:112,1,160 +107,106,45000,1,4,0:0:0:0: +107,106,45125,1,4,0:0:0:0: +107,106,45250,1,6,0:0:0:0: +107,106,45375,1,6,0:0:0:0: +46,112,45500,2,4,L|214:112,1,160 +256,192,47000,12,4,47500,0:0:0:0: +256,192,47625,12,4,48000,0:0:0:0: +256,192,48125,12,4,48500,0:0:0:0: +256,192,48625,12,4,49000,0:0:0:0: +256,192,50000,12,4,50500,0:0:0:0: +183,143,50625,5,4,0:0:0:0: +256,192,50750,12,4,51250,0:0:0:0: +114,106,51375,5,4,0:0:0:0: +256,192,51625,12,4,52125,0:0:0:0: +154,143,52250,5,4,0:0:0:0: +256,192,52375,12,4,52875,0:0:0:0: +116,111,53000,5,4,0:0:0:0: diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu new file mode 100644 index 0000000000..15326162ea --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/diffcalc-test.osu @@ -0,0 +1,285 @@ +osu file format v14 + +[General] +Mode: 1 + +[Difficulty] +CircleSize:4 +OverallDifficulty:7 +ApproachRate:8.3 +SliderMultiplier:1.6 +SliderTickRate:1 + +[TimingPoints] +500,500,4,2,1,50,1,0 +62500,-500,4,2,1,50,0,0 +71000,-100,4,2,1,50,0,0 + +[HitObjects] +// dd, spaced 1/4 beat apart +142,122,0,5,0,0:0:0:0: +142,122,125,1,0,0:0:0:0: +142,122,250,1,0,0:0:0:0: +142,122,375,1,0,0:0:0:0: +142,122,500,1,0,0:0:0:0: +142,122,625,1,0,0:0:0:0: +142,122,750,1,0,0:0:0:0: +142,122,875,1,0,0:0:0:0: +142,122,1000,1,0,0:0:0:0: +142,122,1125,1,0,0:0:0:0: +142,122,1250,1,0,0:0:0:0: +142,122,1375,1,0,0:0:0:0: +142,122,1500,1,0,0:0:0:0: + +// kk, spaced 1/4 beat apart +119,106,2500,1,2,0:0:0:0: +119,106,2625,1,2,0:0:0:0: +119,106,2750,1,2,0:0:0:0: +119,106,2875,1,2,0:0:0:0: +119,106,3000,1,2,0:0:0:0: +119,106,3125,1,2,0:0:0:0: +119,106,3250,1,2,0:0:0:0: +119,106,3375,1,2,0:0:0:0: +119,106,3500,1,2,0:0:0:0: +119,106,3625,1,2,0:0:0:0: +119,106,3750,1,2,0:0:0:0: +119,106,3875,1,2,0:0:0:0: +119,106,4000,1,2,0:0:0:0: + +// dk, spaced 1/4 beat apart +136,90,5000,1,0,0:0:0:0: +136,90,5125,1,2,0:0:0:0: +136,90,5250,1,0,0:0:0:0: +136,90,5375,1,2,0:0:0:0: +136,90,5500,1,0,0:0:0:0: +136,90,5625,1,2,0:0:0:0: +136,90,5750,1,0,0:0:0:0: +136,90,5875,1,2,0:0:0:0: +136,90,6000,1,0,0:0:0:0: +136,90,6125,1,2,0:0:0:0: +136,90,6250,1,0,0:0:0:0: +136,90,6375,1,2,0:0:0:0: +136,90,6500,1,0,0:0:0:0: + +// ddkk, spaced 1/4 beat apart +86,113,7500,1,0,0:0:0:0: +86,113,7625,1,0,0:0:0:0: +86,113,7750,1,2,0:0:0:0: +86,113,7875,1,2,0:0:0:0: +86,113,8000,1,0,0:0:0:0: +86,113,8125,1,0,0:0:0:0: +86,113,8250,1,2,0:0:0:0: +86,113,8375,1,2,0:0:0:0: +86,113,8500,1,0,0:0:0:0: +86,113,8625,1,0,0:0:0:0: +86,113,8750,1,2,0:0:0:0: +86,113,8875,1,2,0:0:0:0: +86,113,9000,1,0,0:0:0:0: + +// dddkkk, spaced 1/4 beat apart +146,90,10000,1,0,0:0:0:0: +146,90,10125,1,0,0:0:0:0: +146,90,10250,1,0,0:0:0:0: +146,90,10375,1,2,0:0:0:0: +146,90,10500,1,2,0:0:0:0: +146,90,10625,1,2,0:0:0:0: +146,90,10750,1,0,0:0:0:0: +146,90,10875,1,0,0:0:0:0: +146,90,11000,1,0,0:0:0:0: +146,90,11125,1,2,0:0:0:0: +146,90,11250,1,2,0:0:0:0: +146,90,11375,1,2,0:0:0:0: +146,90,11500,1,0,0:0:0:0: +146,90,11625,1,0,0:0:0:0: +146,90,11750,1,0,0:0:0:0: +146,90,11875,1,2,0:0:0:0: +146,90,12000,1,2,0:0:0:0: +146,90,12125,1,2,0:0:0:0: +146,90,12250,1,0,0:0:0:0: +146,90,12375,1,0,0:0:0:0: +146,90,12500,1,0,0:0:0:0: + +// dddk, spaced 1/4 beat apart +69,99,13500,1,0,0:0:0:0: +69,99,13625,1,0,0:0:0:0: +69,99,13750,1,0,0:0:0:0: +69,99,13875,1,2,0:0:0:0: +69,99,14000,1,0,0:0:0:0: +69,99,14125,1,0,0:0:0:0: +69,99,14250,1,0,0:0:0:0: +69,99,14375,1,2,0:0:0:0: +69,99,14500,1,0,0:0:0:0: +69,99,14625,1,0,0:0:0:0: +69,99,14750,1,0,0:0:0:0: +69,99,14875,1,2,0:0:0:0: +69,99,15000,1,0,0:0:0:0: +69,99,15125,1,0,0:0:0:0: +69,99,15250,1,0,0:0:0:0: +69,99,15375,1,2,0:0:0:0: +69,99,15500,1,0,0:0:0:0: + +// arbitrary pattern, spaced 1/4 beat apart +83,89,16500,1,0,0:0:0:0: +83,89,16625,1,2,0:0:0:0: +83,89,16750,1,2,0:0:0:0: +83,89,16875,1,0,0:0:0:0: +83,89,17000,1,0,0:0:0:0: +83,89,17125,1,0,0:0:0:0: +83,89,17250,1,2,0:0:0:0: +83,89,17375,1,2,0:0:0:0: +83,89,17500,1,2,0:0:0:0: +83,89,17625,1,2,0:0:0:0: +83,89,17750,1,0,0:0:0:0: +83,89,17875,1,0,0:0:0:0: +83,89,18000,1,0,0:0:0:0: +83,89,18125,1,0,0:0:0:0: +83,89,18250,1,0,0:0:0:0: +83,89,18375,1,2,0:0:0:0: +83,89,18500,1,2,0:0:0:0: +83,89,18625,1,2,0:0:0:0: +83,89,18750,1,2,0:0:0:0: +83,89,18875,1,0,0:0:0:0: +83,89,19000,1,0,0:0:0:0: +83,89,19125,1,0,0:0:0:0: +83,89,19250,1,0,0:0:0:0: +83,89,19375,1,2,0:0:0:0: +83,89,19500,1,2,0:0:0:0: +83,89,19625,1,0,0:0:0:0: + +// d-slider pattern, spaced 1/4 beat apart +84,122,20500,1,0,0:0:0:0: +84,122,20625,2,0,L|217:123,1,120 +84,122,21125,1,0,0:0:0:0: +84,122,21250,2,0,L|217:123,1,120 +84,122,21750,1,0,0:0:0:0: +84,122,21875,2,0,L|217:123,1,120 +84,122,22375,1,0,0:0:0:0: +84,122,22500,2,0,L|217:123,1,120 +84,122,23000,1,0,0:0:0:0: +84,122,23125,2,0,L|217:123,1,120 + +// dd-slider pattern, spaced 1/4 beat apart +99,106,24500,1,0,0:0:0:0: +99,106,24625,1,0,0:0:0:0: +99,106,24750,2,0,L|194:107,1,80 +99,106,25125,1,0,0:0:0:0: +99,106,25250,1,0,0:0:0:0: +99,106,25375,2,0,L|194:107,1,80 +99,106,25750,1,0,0:0:0:0: +99,106,25875,1,0,0:0:0:0: +99,106,26000,2,0,L|194:107,1,80 +99,106,26375,1,0,0:0:0:0: +99,106,26500,1,0,0:0:0:0: +99,106,26625,2,0,L|194:107,1,80 +99,106,27000,1,0,0:0:0:0: +99,106,27125,1,0,0:0:0:0: +99,106,27250,2,0,L|194:107,1,80 + +// ddd-slider pattern, spaced 1/4 beat apart +121,103,28500,1,0,0:0:0:0: +121,103,28625,1,0,0:0:0:0: +121,103,28750,1,0,0:0:0:0: +121,103,28875,2,0,L|190:103,1,40 +121,103,29125,1,0,0:0:0:0: +121,103,29250,1,0,0:0:0:0: +121,103,29375,1,0,0:0:0:0: +121,103,29500,2,0,L|190:103,1,40 +121,103,29750,1,0,0:0:0:0: +121,103,29875,1,0,0:0:0:0: +121,103,30000,1,0,0:0:0:0: +121,103,30125,2,0,L|190:103,1,40 +121,103,30375,1,0,0:0:0:0: +121,103,30500,1,0,0:0:0:0: +121,103,30625,1,0,0:0:0:0: +121,103,30750,2,0,L|190:103,1,40 +121,103,31000,1,0,0:0:0:0: +121,103,31125,1,0,0:0:0:0: +121,103,31250,1,0,0:0:0:0: +121,103,31375,2,0,L|190:103,1,40 + +// dkd-slider pattern, spaced 1/4 beat apart +121,103,32500,1,0,0:0:0:0: +121,103,32625,1,2,0:0:0:0: +121,103,32750,1,0,0:0:0:0: +121,103,32875,2,0,L|190:103,1,40 +121,103,33125,1,0,0:0:0:0: +121,103,33250,1,2,0:0:0:0: +121,103,33375,1,0,0:0:0:0: +121,103,33500,2,0,L|190:103,1,40 +121,103,33750,1,0,0:0:0:0: +121,103,33875,1,2,0:0:0:0: +121,103,34000,1,0,0:0:0:0: +121,103,34125,2,0,L|190:103,1,40 +121,103,34375,1,0,0:0:0:0: +121,103,34500,1,2,0:0:0:0: +121,103,34625,1,0,0:0:0:0: +121,103,34750,2,0,L|190:103,1,40 +121,103,35000,1,0,0:0:0:0: +121,103,35125,1,2,0:0:0:0: +121,103,35250,1,0,0:0:0:0: +121,103,35375,2,0,L|190:103,1,40 + +//ddk-slider pattern, spaced 1/4 beat apart +121,103,36500,1,0,0:0:0:0: +121,103,36625,1,0,0:0:0:0: +121,103,36750,1,2,0:0:0:0: +121,103,36875,2,0,L|190:103,1,40 +121,103,37125,1,0,0:0:0:0: +121,103,37250,1,0,0:0:0:0: +121,103,37375,1,2,0:0:0:0: +121,103,37500,2,0,L|190:103,1,40 +121,103,37750,1,0,0:0:0:0: +121,103,37875,1,0,0:0:0:0: +121,103,38000,1,2,0:0:0:0: +121,103,38125,2,0,L|190:103,1,40 +121,103,38375,1,0,0:0:0:0: +121,103,38500,1,0,0:0:0:0: +121,103,38625,1,2,0:0:0:0: +121,103,38750,2,0,L|190:103,1,40 +121,103,39000,1,0,0:0:0:0: +121,103,39125,1,0,0:0:0:0: +121,103,39250,1,2,0:0:0:0: +121,103,39375,2,0,L|190:103,1,40 + +//ddkk-slider pattern, spaced 1/4 beat apart +107,106,40500,1,0,0:0:0:0: +107,106,40625,1,0,0:0:0:0: +107,106,40750,1,2,0:0:0:0: +107,106,40875,1,2,0:0:0:0: +46,112,41000,2,0,L|214:112,1,160 +107,106,41625,1,0,0:0:0:0: +107,106,41750,1,0,0:0:0:0: +107,106,41875,1,2,0:0:0:0: +107,106,42000,1,2,0:0:0:0: +46,112,42125,2,0,L|214:112,1,160 +107,106,42750,1,0,0:0:0:0: +107,106,42875,1,0,0:0:0:0: +107,106,43000,1,2,0:0:0:0: +107,106,43125,1,2,0:0:0:0: +46,112,43250,2,0,L|214:112,1,160 +107,106,43875,1,0,0:0:0:0: +107,106,44000,1,0,0:0:0:0: +107,106,44125,1,2,0:0:0:0: +107,106,44250,1,2,0:0:0:0: +46,112,44375,2,0,L|214:112,1,160 +107,106,45000,1,0,0:0:0:0: +107,106,45125,1,0,0:0:0:0: +107,106,45250,1,2,0:0:0:0: +107,106,45375,1,2,0:0:0:0: +46,112,45500,2,0,L|214:112,1,160 + +// spinner-spinner pattern, spaced 1/4 beat apart +256,192,47000,12,0,47500,0:0:0:0: +256,192,47625,12,0,48000,0:0:0:0: +256,192,48125,12,0,48500,0:0:0:0: +256,192,48625,12,0,49000,0:0:0:0: + +// spinner-d pattern, spaced 1/4 beat apart +256,192,50000,12,0,50500,0:0:0:0: +183,143,50625,5,0,0:0:0:0: +256,192,50750,12,0,51250,0:0:0:0: +114,106,51375,5,0,0:0:0:0: +256,192,51625,12,0,52125,0:0:0:0: +154,143,52250,5,0,0:0:0:0: +256,192,52375,12,0,52875,0:0:0:0: +116,111,53000,5,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index cf33141027..68ddf2db19 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; @@ -13,54 +13,27 @@ namespace osu.Game.Rulesets.Taiko.Scoring internal class TaikoScoreProcessor : ScoreProcessor { /// - /// The HP awarded by a hit. + /// A value used for calculating . /// - private const double hp_hit_great = 0.03; - - /// - /// The HP awarded for a hit. - /// - private const double hp_hit_good = 0.011; - - /// - /// The minimum HP deducted for a . - /// This occurs when HP Drain = 0. - /// - private const double hp_miss_min = -0.0018; - - /// - /// The median HP deducted for a . - /// This occurs when HP Drain = 5. - /// - private const double hp_miss_mid = -0.0075; - - /// - /// The maximum HP deducted for a . - /// This occurs when HP Drain = 10. - /// - private const double hp_miss_max = -0.12; - - /// - /// The HP awarded for a hit. - /// - /// hits award less HP as they're more spammable, although in hindsight - /// this probably awards too little HP and is kept at this value for now for compatibility. - /// - /// - private const double hp_hit_tick = 0.00000003; + private const double object_count_factor = 3; /// /// Taiko fails at the end of the map if the player has not half-filled their HP bar. /// protected override bool DefaultFailCondition => JudgedHits == MaxHits && Health.Value <= 0.5; - private double hpIncreaseTick; - private double hpIncreaseGreat; - private double hpIncreaseGood; - private double hpIncreaseMiss; + /// + /// HP multiplier for a successful . + /// + private double hpMultiplier; - public TaikoScoreProcessor(RulesetContainer rulesetContainer) - : base(rulesetContainer) + /// + /// HP multiplier for a . + /// + private double hpMissMultiplier; + + public TaikoScoreProcessor(DrawableRuleset drawableRuleset) + : base(drawableRuleset) { } @@ -68,39 +41,13 @@ namespace osu.Game.Rulesets.Taiko.Scoring { base.ApplyBeatmap(beatmap); - double hpMultiplierNormal = 1 / (hp_hit_great * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); + hpMultiplier = 1 / (object_count_factor * beatmap.HitObjects.FindAll(o => o is Hit).Count * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); - hpIncreaseTick = hp_hit_tick; - hpIncreaseGreat = hpMultiplierNormal * hp_hit_great; - hpIncreaseGood = hpMultiplierNormal * hp_hit_good; - hpIncreaseMiss = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, hp_miss_min, hp_miss_mid, hp_miss_max); + hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } - protected override void ApplyResult(JudgementResult result) - { - base.ApplyResult(result); - - bool isTick = result.Judgement is TaikoDrumRollTickJudgement; - - // Apply HP changes - switch (result.Type) - { - case HitResult.Miss: - // Missing ticks shouldn't drop HP - if (!isTick) - Health.Value += hpIncreaseMiss; - break; - case HitResult.Good: - Health.Value += hpIncreaseGood; - break; - case HitResult.Great: - if (isTick) - Health.Value += hpIncreaseTick; - else - Health.Value += hpIncreaseGreat; - break; - } - } + protected override double HealthAdjustmentFactorFor(JudgementResult result) + => result.Type == HitResult.Miss ? hpMissMultiplier : hpMultiplier; protected override void Reset(bool storeResults) { @@ -108,5 +55,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring Health.Value = 0; } + + public override HitWindows CreateHitWindows() => new TaikoHitWindows(); } } diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index 3b430e7ad1..058bec5111 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; using osu.Framework.Input.Bindings; @@ -19,10 +19,13 @@ namespace osu.Game.Rulesets.Taiko { [Description("Left (rim)")] LeftRim, + [Description("Left (centre)")] LeftCentre, + [Description("Right (centre)")] RightCentre, + [Description("Right (rim)")] RightRim } diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7d9bc98957..a67004e9c7 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -9,20 +9,22 @@ using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.Replays.Types; +using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Difficulty; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Beatmaps; using osu.Game.Rulesets.Taiko.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Taiko { public class TaikoRuleset : Ruleset { - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new TaikoRulesetContainer(this, beatmap); + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) => new DrawableTaikoRuleset(this, beatmap, mods); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap); public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] @@ -84,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko new TaikoModNoFail(), new MultiMod(new TaikoModHalfTime(), new TaikoModDaycore()), }; + case ModType.DifficultyIncrease: return new Mod[] { @@ -93,12 +96,20 @@ namespace osu.Game.Rulesets.Taiko new TaikoModHidden(), new TaikoModFlashlight(), }; + case ModType.Automation: return new Mod[] { new MultiMod(new TaikoModAutoplay(), new ModCinema()), new TaikoModRelax(), }; + + case ModType.Fun: + return new Mod[] + { + new MultiMod(new ModWindUp(), new ModWindDown()) + }; + default: return new Mod[] { }; } @@ -108,11 +119,11 @@ namespace osu.Game.Rulesets.Taiko public override string ShortName => "taiko"; - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko }; public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); - public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score); + public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); public override int? LegacyID => 1; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs index c7eba91564..f91bbb14e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; @@ -33,18 +33,17 @@ namespace osu.Game.Rulesets.Taiko.UI case HitResult.Good: JudgementBody.Colour = colours.GreenLight; break; + case HitResult.Great: JudgementBody.Colour = colours.BlueLight; break; } } - protected override void LoadComplete() + protected override void ApplyHitAnimations() { - if (Result.IsHit) - this.MoveToY(-100, 500); - - base.LoadComplete(); + this.MoveToY(-100, 500); + base.ApplyHitAnimations(); } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs similarity index 79% rename from osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs rename to osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 99c83c243b..ec3a56e9c7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; @@ -16,18 +16,20 @@ using System.Linq; using osu.Framework.Input; using osu.Game.Configuration; using osu.Game.Input.Handlers; +using osu.Game.Replays; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI.Scrolling; namespace osu.Game.Rulesets.Taiko.UI { - public class TaikoRulesetContainer : ScrollingRulesetContainer + public class DrawableTaikoRuleset : DrawableScrollingRuleset { protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; protected override bool UserScrollSpeedAdjustment => false; - public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + public DrawableTaikoRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Left; TimeRange.Value = 7000; @@ -52,9 +54,11 @@ namespace osu.Game.Rulesets.Taiko.UI int currentIndex = 0; int currentBeat = 0; double time = timingPoints[currentIndex].Time; + while (time <= lastHitTime) { int nextIndex = currentIndex + 1; + if (nextIndex < timingPoints.Count && time > timingPoints[nextIndex].Time) { currentIndex = nextIndex; @@ -81,20 +85,25 @@ namespace osu.Game.Rulesets.Taiko.UI public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); - public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); + + protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo); - public override DrawableHitObject GetVisualRepresentation(TaikoHitObject h) + public override DrawableHitObject CreateDrawableRepresentation(TaikoHitObject h) { switch (h) { case CentreHit centreHit: return new DrawableCentreHit(centreHit); + case RimHit rimHit: return new DrawableRimHit(rimHit); + case DrumRoll drumRoll: return new DrawableDrumRoll(drumRoll); + case Swell swell: return new DrawableSwell(swell); } diff --git a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs index 84e40427f2..404960c26f 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitExplosion.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs index ab5bc841a6..2bb208bd1d 100644 --- a/osu.Game.Rulesets.Taiko/UI/HitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/HitTarget.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 6d84b69638..aa37ff7008 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; diff --git a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs index ad9b967ac6..e80b463481 100644 --- a/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs +++ b/osu.Game.Rulesets.Taiko/UI/KiaiHitExplosion.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 333b1ba83a..7427a3235d 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class TaikoPlayfield : ScrollingPlayfield { /// - /// Default height of a when inside a . + /// Default height of a when inside a . /// public const float DEFAULT_HEIGHT = 178; @@ -55,143 +56,137 @@ namespace osu.Game.Rulesets.Taiko.UI public TaikoPlayfield(ControlPointInfo controlPoints) { - InternalChild = new PlayfieldAdjustmentContainer + InternalChildren = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + backgroundContainer = new Container { - backgroundContainer = new Container + Name = "Transparent playfield background", + RelativeSizeAxes = Axes.Both, + Masking = true, + EdgeEffect = new EdgeEffectParameters { - Name = "Transparent playfield background", - RelativeSizeAxes = Axes.Both, - Masking = true, - EdgeEffect = new EdgeEffectParameters + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 5, + }, + Children = new Drawable[] + { + background = new Box { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.2f), - Radius = 5, + RelativeSizeAxes = Axes.Both, + Alpha = 0.6f }, - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.6f - }, - } - }, - new Container - { - Name = "Right area", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = left_area_size }, - Children = new Drawable[] - { - new Container - { - Name = "Masked elements before hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Children = new Drawable[] - { - hitExplosionContainer = new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Blending = BlendingMode.Additive, - }, - HitTarget = new HitTarget - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - } - } - }, - barlineContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } - }, - new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Masking = true, - Child = HitObjectContainer - }, - kiaiExplosionContainer = new Container - { - Name = "Kiai hit explosions", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - judgementContainer = new JudgementContainer - { - Name = "Judgements", - RelativeSizeAxes = Axes.Y, - Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, - Blending = BlendingMode.Additive - }, - } - }, - overlayBackgroundContainer = new Container - { - Name = "Left overlay", - RelativeSizeAxes = Axes.Y, - Size = new Vector2(left_area_size, 1), - Children = new Drawable[] - { - overlayBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - new InputDrum(controlPoints) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Scale = new Vector2(0.9f), - Margin = new MarginPadding { Right = 20 } - }, - new Box - { - Anchor = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 10, - Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), - }, - } - }, - new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - MaskingSmoothness = 0, - BorderThickness = 2, - AlwaysPresent = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - topLevelHitContainer = new Container - { - Name = "Top level hit objects", - RelativeSizeAxes = Axes.Both, } + }, + new Container + { + Name = "Right area", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = left_area_size }, + Children = new Drawable[] + { + new Container + { + Name = "Masked elements before hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Children = new Drawable[] + { + hitExplosionContainer = new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Blending = BlendingMode.Additive, + }, + HitTarget = new HitTarget + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + } + } + }, + barlineContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } + }, + new Container + { + Name = "Hit objects", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Masking = true, + Child = HitObjectContainer + }, + kiaiExplosionContainer = new Container + { + Name = "Kiai hit explosions", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + judgementContainer = new JudgementContainer + { + Name = "Judgements", + RelativeSizeAxes = Axes.Y, + Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, + Blending = BlendingMode.Additive + }, + } + }, + overlayBackgroundContainer = new Container + { + Name = "Left overlay", + RelativeSizeAxes = Axes.Y, + Size = new Vector2(left_area_size, 1), + Children = new Drawable[] + { + overlayBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new InputDrum(controlPoints) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Scale = new Vector2(0.9f), + Margin = new MarginPadding { Right = 20 } + }, + new Box + { + Anchor = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 10, + Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)), + }, + } + }, + new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + MaskingSmoothness = 0, + BorderThickness = 2, + AlwaysPresent = true, + Children = new[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + }, + topLevelHitContainer = new Container + { + Name = "Top level hit objects", + RelativeSizeAxes = Axes.Both, } }; } @@ -217,6 +212,7 @@ namespace osu.Game.Rulesets.Taiko.UI case DrawableBarLine barline: barlineContainer.Add(barline.CreateProxy()); break; + case DrawableTaikoHitObject taikoObject: topLevelHitContainer.Add(taikoObject.CreateProxiedContent()); break; @@ -225,7 +221,7 @@ namespace osu.Game.Rulesets.Taiko.UI internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result) { - if (!DisplayJudgements) + if (!DisplayJudgements.Value) return; if (!judgedObject.DisplayResult) @@ -237,6 +233,7 @@ namespace osu.Game.Rulesets.Taiko.UI if (result.IsHit) hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == ((DrawableStrongNestedHit)judgedObject).MainObject)?.VisualiseSecondHit(); break; + default: judgementContainer.Add(new DrawableTaikoJudgement(result, judgedObject) { diff --git a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs similarity index 52% rename from osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs rename to osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 73751e845e..84464b199e 100644 --- a/osu.Game.Rulesets.Taiko/UI/PlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -1,16 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI { - public class PlayfieldAdjustmentContainer : Container + public class TaikoPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer { private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; private const float default_aspect = 16f / 9f; + public TaikoPlayfieldAdjustmentContainer() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + } + protected override void Update() { base.Update(); diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 002d6a7e8d..656ebcc7c2 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -10,4 +10,4 @@ - \ No newline at end of file + diff --git a/osu.Game.Tests.iOS/AppDelegate.cs b/osu.Game.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..1e703e0c0a --- /dev/null +++ b/osu.Game.Tests.iOS/AppDelegate.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; + +namespace osu.Game.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs new file mode 100644 index 0000000000..a23fe4e129 --- /dev/null +++ b/osu.Game.Tests.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.Game.Tests.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, null, "AppDelegate"); + } + } +} diff --git a/osu.Game.Tests.iOS/Entitlements.plist b/osu.Game.Tests.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.Game.Tests.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist new file mode 100644 index 0000000000..98a4223116 --- /dev/null +++ b/osu.Game.Tests.iOS/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + osu.Game.Tests.iOS + CFBundleIdentifier + ppy.osu-Game-Tests-iOS + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj new file mode 100644 index 0000000000..ea5ab699f3 --- /dev/null +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -0,0 +1,60 @@ + + + + + Debug + iPhoneSimulator + Exe + {65FF8E19-6934-469B-B690-23C6D6E56A17} + osu.Game.Tests + osu.Game.Tests.iOS + + + + + + + libbass.a + PreserveNewest + + + libbass_fx.a + PreserveNewest + + + Linker.xml + + + + + %(RecursiveDir)%(Filename)%(Extension) + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {C92A607B-1FDD-4954-9F92-03FF547D9080} + osu.Game.Rulesets.Osu + + + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} + osu.Game.Rulesets.Catch + + + {48F4582B-7687-4621-9CBE-5C24197CB536} + osu.Game.Rulesets.Mania + + + {F167E17A-7DE6-4AF5-B920-A5112296C695} + osu.Game.Rulesets.Taiko + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 6d64b25906..d087251e7e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.IO; using NUnit.Framework; using osuTK; @@ -13,6 +14,7 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Osu; @@ -27,7 +29,7 @@ namespace osu.Game.Tests.Beatmaps.Formats [Test] public void TestDecodeBeatmapVersion() { - using (var resStream = Resource.OpenResource("beatmap-version.osu")) + using (var resStream = TestResources.OpenResource("beatmap-version.osu")) using (var stream = new StreamReader(resStream)) { var decoder = Decoder.GetDecoder(stream); @@ -39,7 +41,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion); Assert.AreEqual(6, working.Beatmap.BeatmapInfo.BeatmapVersion); - Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapVersion); + Assert.AreEqual(6, working.GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty()).BeatmapInfo.BeatmapVersion); } } @@ -47,7 +49,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapGeneral() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var beatmap = decoder.Decode(stream); @@ -70,7 +73,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEditor() { var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var beatmapInfo = decoder.Decode(stream).BeatmapInfo; @@ -95,7 +99,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapMetadata() { var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var beatmap = decoder.Decode(stream); @@ -119,7 +124,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapDifficulty() { var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty; @@ -137,7 +143,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapEvents() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var beatmap = decoder.Decode(stream); @@ -155,34 +162,106 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapTimingPoints() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var beatmap = decoder.Decode(stream); var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - var timingPoint = controlPoints.TimingPoints[0]; + Assert.AreEqual(42, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(42, controlPoints.SamplePoints.Count); + Assert.AreEqual(42, controlPoints.EffectPoints.Count); + + var timingPoint = controlPoints.TimingPointAt(0); + Assert.AreEqual(956, timingPoint.Time); + Assert.AreEqual(329.67032967033, timingPoint.BeatLength); + Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); + + timingPoint = controlPoints.TimingPointAt(48428); Assert.AreEqual(956, timingPoint.Time); Assert.AreEqual(329.67032967033d, timingPoint.BeatLength); Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); - Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); - var difficultyPoint = controlPoints.DifficultyPoints[0]; - Assert.AreEqual(116999, difficultyPoint.Time); - Assert.AreEqual(0.75000000000000189d, difficultyPoint.SpeedMultiplier); + timingPoint = controlPoints.TimingPointAt(119637); + Assert.AreEqual(119637, timingPoint.Time); + Assert.AreEqual(659.340659340659, timingPoint.BeatLength); + Assert.AreEqual(TimeSignatures.SimpleQuadruple, timingPoint.TimeSignature); - Assert.AreEqual(34, controlPoints.SamplePoints.Count); - var soundPoint = controlPoints.SamplePoints[0]; + var difficultyPoint = controlPoints.DifficultyPointAt(0); + Assert.AreEqual(0, difficultyPoint.Time); + Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); + + difficultyPoint = controlPoints.DifficultyPointAt(48428); + Assert.AreEqual(48428, difficultyPoint.Time); + Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); + + difficultyPoint = controlPoints.DifficultyPointAt(116999); + Assert.AreEqual(116999, difficultyPoint.Time); + Assert.AreEqual(0.75, difficultyPoint.SpeedMultiplier, 0.1); + + var soundPoint = controlPoints.SamplePointAt(0); Assert.AreEqual(956, soundPoint.Time); Assert.AreEqual("soft", soundPoint.SampleBank); Assert.AreEqual(60, soundPoint.SampleVolume); - Assert.AreEqual(8, controlPoints.EffectPoints.Count); - var effectPoint = controlPoints.EffectPoints[0]; + soundPoint = controlPoints.SamplePointAt(53373); + Assert.AreEqual(53373, soundPoint.Time); + Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(60, soundPoint.SampleVolume); + + soundPoint = controlPoints.SamplePointAt(119637); + Assert.AreEqual(119637, soundPoint.Time); + Assert.AreEqual("soft", soundPoint.SampleBank); + Assert.AreEqual(80, soundPoint.SampleVolume); + + var effectPoint = controlPoints.EffectPointAt(0); + Assert.AreEqual(0, effectPoint.Time); + Assert.IsFalse(effectPoint.KiaiMode); + Assert.IsFalse(effectPoint.OmitFirstBarLine); + + effectPoint = controlPoints.EffectPointAt(53703); Assert.AreEqual(53703, effectPoint.Time); Assert.IsTrue(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.OmitFirstBarLine); + + effectPoint = controlPoints.EffectPointAt(119637); + Assert.AreEqual(119637, effectPoint.Time); + Assert.IsFalse(effectPoint.KiaiMode); + Assert.IsFalse(effectPoint.OmitFirstBarLine); + } + } + + [Test] + public void TestDecodeOverlappingTimingPoints() + { + var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; + + using (var resStream = TestResources.OpenResource("overlapping-control-points.osu")) + using (var stream = new StreamReader(resStream)) + { + var controlPoints = decoder.Decode(stream).ControlPointInfo; + + Assert.That(controlPoints.DifficultyPointAt(500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); + Assert.That(controlPoints.DifficultyPointAt(1500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); + Assert.That(controlPoints.DifficultyPointAt(2500).SpeedMultiplier, Is.EqualTo(0.75).Within(0.1)); + Assert.That(controlPoints.DifficultyPointAt(3500).SpeedMultiplier, Is.EqualTo(1.5).Within(0.1)); + + Assert.That(controlPoints.EffectPointAt(500).KiaiMode, Is.True); + Assert.That(controlPoints.EffectPointAt(1500).KiaiMode, Is.True); + Assert.That(controlPoints.EffectPointAt(2500).KiaiMode, Is.False); + Assert.That(controlPoints.EffectPointAt(3500).KiaiMode, Is.True); + + Assert.That(controlPoints.SamplePointAt(500).SampleBank, Is.EqualTo("drum")); + Assert.That(controlPoints.SamplePointAt(1500).SampleBank, Is.EqualTo("drum")); + Assert.That(controlPoints.SamplePointAt(2500).SampleBank, Is.EqualTo("normal")); + Assert.That(controlPoints.SamplePointAt(3500).SampleBank, Is.EqualTo("drum")); + + Assert.That(controlPoints.TimingPointAt(500).BeatLength, Is.EqualTo(500).Within(0.1)); + Assert.That(controlPoints.TimingPointAt(1500).BeatLength, Is.EqualTo(500).Within(0.1)); + Assert.That(controlPoints.TimingPointAt(2500).BeatLength, Is.EqualTo(250).Within(0.1)); + Assert.That(controlPoints.TimingPointAt(3500).BeatLength, Is.EqualTo(500).Within(0.1)); } } @@ -190,7 +269,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapColours() { var decoder = new LegacySkinDecoder(); - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var comboColors = decoder.Decode(stream).ComboColours; @@ -215,7 +295,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsOsu() { var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu")) + + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { var beatmap = decoder.Decode(stream); @@ -237,7 +318,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapComboOffsetsCatch() { var decoder = new LegacyBeatmapDecoder(); - using (var resStream = Resource.OpenResource("hitobject-combo-offset.osu")) + + using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu")) using (var stream = new StreamReader(resStream)) { var beatmap = decoder.Decode(stream); @@ -259,7 +341,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeBeatmapHitObjects() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) + + using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new StreamReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -271,14 +354,14 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsNotNull(curveData); Assert.AreEqual(new Vector2(192, 168), positionData.Position); Assert.AreEqual(956, hitObjects[0].StartTime); - Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL)); positionData = hitObjects[1] as IHasPosition; Assert.IsNotNull(positionData); Assert.AreEqual(new Vector2(304, 56), positionData.Position); Assert.AreEqual(1285, hitObjects[1].StartTime); - Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)); } } @@ -286,7 +369,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeControlPointCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("controlpoint-custom-samplebank.osu")) + + using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -295,16 +379,20 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("normal-hitnormal", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); + + // The control point at the end time of the slider should be applied + Assert.AreEqual("soft-hitnormal8", getTestableSampleInfo(hitObjects[4]).LookupNames.First()); } - SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } [Test] public void TestDecodeHitObjectCustomSampleBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("hitobject-custom-samplebank.osu")) + + using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu")) using (var stream = new StreamReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -314,14 +402,15 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("normal-hitnormal3", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); } - SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } [Test] public void TestDecodeHitObjectFileSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("hitobject-file-samples.osu")) + + using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu")) using (var stream = new StreamReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -330,16 +419,18 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("hit_2.wav", getTestableSampleInfo(hitObjects[1]).LookupNames.First()); Assert.AreEqual("normal-hitnormal2", getTestableSampleInfo(hitObjects[2]).LookupNames.First()); Assert.AreEqual("hit_1.wav", getTestableSampleInfo(hitObjects[3]).LookupNames.First()); + Assert.AreEqual(70, getTestableSampleInfo(hitObjects[3]).Volume); } - SampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); + HitSampleInfo getTestableSampleInfo(HitObject hitObject) => hitObject.SampleControlPoint.ApplyTo(hitObject.Samples[0]); } [Test] public void TestDecodeSliderSamples() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("slider-samples.osu")) + + using (var resStream = TestResources.OpenResource("slider-samples.osu")) using (var stream = new StreamReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; @@ -347,34 +438,34 @@ namespace osu.Game.Tests.Beatmaps.Formats var slider1 = (ConvertSlider)hitObjects[0]; Assert.AreEqual(1, slider1.NodeSamples[0].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[0][0].Name); Assert.AreEqual(1, slider1.NodeSamples[1].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[1][0].Name); Assert.AreEqual(1, slider1.NodeSamples[2].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider1.NodeSamples[2][0].Name); var slider2 = (ConvertSlider)hitObjects[1]; Assert.AreEqual(2, slider2.NodeSamples[0].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name); - Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[0][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[0][1].Name); Assert.AreEqual(2, slider2.NodeSamples[1].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name); - Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[1][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[1][1].Name); Assert.AreEqual(2, slider2.NodeSamples[2].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name); - Assert.AreEqual(SampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider2.NodeSamples[2][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider2.NodeSamples[2][1].Name); var slider3 = (ConvertSlider)hitObjects[2]; Assert.AreEqual(2, slider3.NodeSamples[0].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name); - Assert.AreEqual(SampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[0][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_WHISTLE, slider3.NodeSamples[0][1].Name); Assert.AreEqual(1, slider3.NodeSamples[1].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[1][0].Name); Assert.AreEqual(2, slider3.NodeSamples[2].Count); - Assert.AreEqual(SampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name); - Assert.AreEqual(SampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name); + Assert.AreEqual(HitSampleInfo.HIT_NORMAL, slider3.NodeSamples[2][0].Name); + Assert.AreEqual(HitSampleInfo.HIT_CLAP, slider3.NodeSamples[2][1].Name); } } @@ -382,7 +473,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeHitObjectNullAdditionBank() { var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false }; - using (var resStream = Resource.OpenResource("hitobject-no-addition-bank.osu")) + + using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu")) using (var stream = new StreamReader(resStream)) { var hitObjects = decoder.Decode(stream).HitObjects; diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs new file mode 100644 index 0000000000..b4d219456c --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyDecoderTest.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.IO; +using NUnit.Framework; +using osu.Game.Beatmaps.Formats; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class LegacyDecoderTest + { + [Test] + public void TestDecodeComments() + { + var decoder = new LineLoggingDecoder(14); + + using (var resStream = TestResources.OpenResource("comments.osu")) + using (var stream = new StreamReader(resStream)) + { + decoder.Decode(stream); + + Assert.That(decoder.ParsedLines, Has.None.EqualTo("// Combo1: 0, 0, 0")); + Assert.That(decoder.ParsedLines, Has.None.EqualTo("//Combo2: 0, 0, 0")); + Assert.That(decoder.ParsedLines, Has.None.EqualTo(" // Combo3: 0, 0, 0")); + Assert.That(decoder.ParsedLines, Has.One.EqualTo("Combo1: 100, 100, 100 // Comment at end of line")); + } + } + + private class LineLoggingDecoder : LegacyDecoder + { + public readonly List ParsedLines = new List(); + + public LineLoggingDecoder(int version) + : base(version) + { + } + + protected override bool ShouldSkipLine(string line) + { + var result = base.ShouldSkipLine(line); + + if (!result) + ParsedLines.Add(line); + + return result; + } + } + + private class TestModel + { + } + } +} diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index 40857198f1..971518909d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.IO; using System.Linq; @@ -19,7 +19,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeStoryboardEvents() { var decoder = new LegacyStoryboardDecoder(); - using (var resStream = Resource.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) + + using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu")) using (var stream = new StreamReader(resStream)) { var storyboard = decoder.Decode(stream); @@ -29,28 +30,28 @@ namespace osu.Game.Tests.Beatmaps.Formats StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3); Assert.IsNotNull(background); - Assert.AreEqual(16, background.Elements.Count()); + Assert.AreEqual(16, background.Elements.Count); Assert.IsTrue(background.EnabledWhenFailing); Assert.IsTrue(background.EnabledWhenPassing); Assert.AreEqual("Background", background.Name); StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2); Assert.IsNotNull(fail); - Assert.AreEqual(0, fail.Elements.Count()); + Assert.AreEqual(0, fail.Elements.Count); Assert.IsTrue(fail.EnabledWhenFailing); Assert.IsFalse(fail.EnabledWhenPassing); Assert.AreEqual("Fail", fail.Name); StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1); Assert.IsNotNull(pass); - Assert.AreEqual(0, pass.Elements.Count()); + Assert.AreEqual(0, pass.Elements.Count); Assert.IsFalse(pass.EnabledWhenFailing); Assert.IsTrue(pass.EnabledWhenPassing); Assert.AreEqual("Pass", pass.Name); StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0); Assert.IsNotNull(foreground); - Assert.AreEqual(151, foreground.Elements.Count()); + Assert.AreEqual(151, foreground.Elements.Count); Assert.IsTrue(foreground.EnabledWhenFailing); Assert.IsTrue(foreground.EnabledWhenPassing); Assert.AreEqual("Foreground", foreground.Name); @@ -62,7 +63,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(15, spriteCount); Assert.AreEqual(1, animationCount); Assert.AreEqual(0, sampleCount); - Assert.AreEqual(background.Elements.Count(), spriteCount + animationCount + sampleCount); + Assert.AreEqual(background.Elements.Count, spriteCount + animationCount + sampleCount); var sprite = background.Elements.ElementAt(0) as StoryboardSprite; Assert.NotNull(sprite); @@ -70,9 +71,9 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); Assert.IsTrue(sprite.IsDrawable); Assert.AreEqual(Anchor.Centre, sprite.Origin); - Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path); + Assert.AreEqual("SB/black.jpg", sprite.Path); - var animation = background.Elements.ElementAt(12) as StoryboardAnimation; + var animation = background.Elements.OfType().First(); Assert.NotNull(animation); Assert.AreEqual(141175, animation.EndTime); Assert.AreEqual(10, animation.FrameCount); @@ -91,7 +92,8 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeVariableWithSuffix() { var decoder = new LegacyStoryboardDecoder(); - using (var resStream = Resource.OpenResource("variable-with-suffix.osb")) + + using (var resStream = TestResources.OpenResource("variable-with-suffix.osb")) using (var stream = new StreamReader(resStream)) { var storyboard = decoder.Decode(stream); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index f7e1653cdd..a725c58462 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.IO; using System.Linq; @@ -101,18 +101,19 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsNotNull(curveData); Assert.AreEqual(new Vector2(192, 168), positionData.Position); Assert.AreEqual(956, beatmap.HitObjects[0].StartTime); - Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == SampleInfo.HIT_NORMAL)); + Assert.IsTrue(beatmap.HitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL)); positionData = beatmap.HitObjects[1] as IHasPosition; Assert.IsNotNull(positionData); Assert.AreEqual(new Vector2(304, 56), positionData.Position); Assert.AreEqual(1285, beatmap.HitObjects[1].StartTime); - Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)); + Assert.IsTrue(beatmap.HitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)); } [TestCase(normal)] [TestCase(marathon)] + [Ignore("temporarily disabled pending DeepEqual fix (https://github.com/jamesfoster/DeepEqual/pull/35)")] // Currently fails: // [TestCase(with_sb)] public void TestParity(string beatmap) @@ -146,11 +147,11 @@ namespace osu.Game.Tests.Beatmaps.Formats /// The after being decoded by an . private Beatmap decode(string filename, out Beatmap jsonDecoded) { - using (var stream = Resource.OpenResource(filename)) + using (var stream = TestResources.OpenResource(filename)) using (var sr = new StreamReader(stream)) { - var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr); + using (var ms = new MemoryStream()) using (var sw = new StreamWriter(ms)) using (var sr2 = new StreamReader(ms)) diff --git a/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs new file mode 100644 index 0000000000..b3863bcf44 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/Formats/ParsingTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; +using NUnit.Framework; +using osu.Game.Beatmaps.Formats; + +namespace osu.Game.Tests.Beatmaps.Formats +{ + [TestFixture] + public class ParsingTest + { + [Test] + public void TestNaNHandling() => allThrow("NaN"); + + [Test] + public void TestBadStringHandling() => allThrow("Random string 123"); + + [TestCase(Parsing.MAX_PARSE_VALUE)] + [TestCase(-1)] + [TestCase(0)] + [TestCase(1)] + [TestCase(-Parsing.MAX_PARSE_VALUE)] + [TestCase(10, 10)] + [TestCase(-10, 10)] + public void TestValidRanges(double input, double limit = Parsing.MAX_PARSE_VALUE) + { + Assert.AreEqual(Parsing.ParseInt((input).ToString(CultureInfo.InvariantCulture), (int)limit), (int)input); + Assert.AreEqual(Parsing.ParseFloat((input).ToString(CultureInfo.InvariantCulture), (float)limit), (float)input); + Assert.AreEqual(Parsing.ParseDouble((input).ToString(CultureInfo.InvariantCulture), limit), input); + } + + [TestCase(double.PositiveInfinity)] + [TestCase(double.NegativeInfinity)] + [TestCase(999999999999)] + [TestCase(Parsing.MAX_PARSE_VALUE * 1.1)] + [TestCase(-Parsing.MAX_PARSE_VALUE * 1.1)] + [TestCase(11, 10)] + [TestCase(-11, 10)] + public void TestOutOfRangeHandling(double input, double limit = Parsing.MAX_PARSE_VALUE) + => allThrow(input.ToString(CultureInfo.InvariantCulture), limit); + + private void allThrow(string input, double limit = Parsing.MAX_PARSE_VALUE) + where T : Exception + { + Assert.Throws(getIntParseException(input) ?? typeof(T), () => Parsing.ParseInt(input, (int)limit)); + Assert.Throws(() => Parsing.ParseFloat(input, (float)limit)); + Assert.Throws(() => Parsing.ParseDouble(input, limit)); + } + + /// + /// may not be able to parse some inputs. + /// In this case we expect to receive the raw parsing exception. + /// + /// The input attempting to be parsed. + /// The type of exception thrown by . Null if no exception is thrown. + private Type getIntParseException(string input) + { + try + { + var _ = int.Parse(input); + } + catch (Exception e) + { + return e.GetType(); + } + + return null; + } + } +} diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 616ba132fd..ad0ed00989 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.IO; @@ -11,7 +11,10 @@ using NUnit.Framework; using osu.Framework.Platform; using osu.Game.IPC; using osu.Framework.Allocation; +using osu.Framework.Logging; using osu.Game.Beatmaps; +using osu.Game.IO; +using osu.Game.Tests.Resources; using SharpCompress.Archives.Zip; namespace osu.Game.Tests.Beatmaps.IO @@ -19,17 +22,15 @@ namespace osu.Game.Tests.Beatmaps.IO [TestFixture] public class ImportBeatmapTest { - public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; - [Test] - public void TestImportWhenClosed() + public async Task TestImportWhenClosed() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed")) { try { - loadOszIntoOsu(loadOsu(host)); + await LoadOszIntoOsu(loadOsu(host)); } finally { @@ -39,7 +40,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public void TestImportThenDelete() + public async Task TestImportThenDelete() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete")) @@ -48,7 +49,7 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = loadOsu(host); - var imported = loadOszIntoOsu(osu); + var imported = await LoadOszIntoOsu(osu); deleteBeatmapSet(imported, osu); } @@ -60,7 +61,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public void TestImportThenImport() + public async Task TestImportThenImport() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport")) @@ -69,17 +70,15 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = loadOsu(host); - var imported = loadOszIntoOsu(osu); - var importedSecondTime = loadOszIntoOsu(osu); + var imported = await LoadOszIntoOsu(osu); + var importedSecondTime = await 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); - var manager = osu.Dependencies.Get(); - - Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count); - Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); + checkBeatmapSetCount(osu, 1); + checkSingleReferencedFileCount(osu, 18); } finally { @@ -89,32 +88,43 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public void TestRollbackOnFailure() + public async Task 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 { + int itemAddRemoveFireCount = 0; + int loggedExceptionCount = 0; + + Logger.NewEntry += l => + { + if (l.Target == LoggingTarget.Database && l.Exception != null) + Interlocked.Increment(ref loggedExceptionCount); + }; + var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - int fireCount = 0; - // ReSharper disable once AccessToModifiedClosure - manager.ItemAdded += _ => fireCount++; - manager.ItemRemoved += _ => fireCount++; + manager.ItemAdded += _ => Interlocked.Increment(ref itemAddRemoveFireCount); + manager.ItemRemoved += _ => Interlocked.Increment(ref itemAddRemoveFireCount); - var imported = loadOszIntoOsu(osu); + var imported = await LoadOszIntoOsu(osu); - Assert.AreEqual(0, fireCount -= 1); + Assert.AreEqual(0, itemAddRemoveFireCount -= 1); imported.Hash += "-changed"; manager.Update(imported); - Assert.AreEqual(0, fireCount -= 2); + Assert.AreEqual(0, itemAddRemoveFireCount -= 2); - var breakTemp = createTemporaryBeatmap(); + checkBeatmapSetCount(osu, 1); + checkBeatmapCount(osu, 12); + checkSingleReferencedFileCount(osu, 18); + + var breakTemp = TestResources.GetTestBeatmapForImport(); MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 }); MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp)); @@ -128,19 +138,24 @@ namespace osu.Game.Tests.Beatmaps.IO 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); + try + { + await manager.Import(breakTemp); + } + catch + { + } // no events should be fired in the case of a rollback. - Assert.AreEqual(0, fireCount); + Assert.AreEqual(0, itemAddRemoveFireCount); - Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count); - Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); - Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count); + checkBeatmapSetCount(osu, 1); + checkBeatmapCount(osu, 12); + + checkSingleReferencedFileCount(osu, 18); + + Assert.AreEqual(1, loggedExceptionCount); } finally { @@ -150,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public void TestImportThenImportDifferentHash() + public async Task TestImportThenImportDifferentHash() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash")) @@ -160,19 +175,18 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); var manager = osu.Dependencies.Get(); - var imported = loadOszIntoOsu(osu); + var imported = await LoadOszIntoOsu(osu); imported.Hash += "-changed"; manager.Update(imported); - var importedSecondTime = loadOszIntoOsu(osu); + var importedSecondTime = await LoadOszIntoOsu(osu); Assert.IsTrue(imported.ID != importedSecondTime.ID); Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); // 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); + checkBeatmapSetCount(osu, 1); } finally { @@ -182,7 +196,7 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public void TestImportThenDeleteThenImport() + public async Task TestImportThenDeleteThenImport() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport")) @@ -191,11 +205,11 @@ namespace osu.Game.Tests.Beatmaps.IO { var osu = loadOsu(host); - var imported = loadOszIntoOsu(osu); + var imported = await LoadOszIntoOsu(osu); deleteBeatmapSet(imported, osu); - var importedSecondTime = loadOszIntoOsu(osu); + var importedSecondTime = await LoadOszIntoOsu(osu); // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. Assert.IsTrue(imported.ID == importedSecondTime.ID); @@ -208,6 +222,96 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [TestCase(true)] + [TestCase(false)] + public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set) + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}")) + { + try + { + var osu = loadOsu(host); + + var imported = await LoadOszIntoOsu(osu); + + if (set) + imported.OnlineBeatmapSetID = 1234; + else + imported.Beatmaps.First().OnlineBeatmapID = 1234; + + osu.Dependencies.Get().Update(imported); + + deleteBeatmapSet(imported, osu); + + var importedSecondTime = await LoadOszIntoOsu(osu); + + // check the newly "imported" beatmap has been reimported due to mismatch (even though hashes matched) + Assert.IsTrue(imported.ID != importedSecondTime.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportWithDuplicateBeatmapIDs() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID")) + { + try + { + var osu = loadOsu(host); + + var metadata = new BeatmapMetadata + { + Artist = "SomeArtist", + AuthorString = "SomeAuthor" + }; + + var difficulty = new BeatmapDifficulty(); + + var toImport = new BeatmapSetInfo + { + OnlineBeatmapSetID = 1, + Metadata = metadata, + Beatmaps = new List + { + new BeatmapInfo + { + OnlineBeatmapID = 2, + Metadata = metadata, + BaseDifficulty = difficulty + }, + new BeatmapInfo + { + OnlineBeatmapID = 2, + Metadata = metadata, + Status = BeatmapSetOnlineStatus.Loved, + BaseDifficulty = difficulty + } + } + }; + + var manager = osu.Dependencies.Get(); + + var imported = await manager.Import(toImport); + + Assert.NotNull(imported); + Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID); + Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID); + } + finally + { + host.Exit(); + } + } + } + [Test] [NonParallelizable] [Ignore("Binding IPC on Appveyor isn't working (port in use). Need to figure out why")] @@ -223,7 +327,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = loadOsu(host); - var temp = createTemporaryBeatmap(); + var temp = TestResources.GetTestBeatmapForImport(); var importer = new ArchiveImportIPCChannel(client); if (!importer.ImportAsync(temp).Wait(10000)) @@ -241,16 +345,16 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public void TestImportWhenFileOpen() + public async Task TestImportWhenFileOpen() { using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen")) { try { var osu = loadOsu(host); - var temp = createTemporaryBeatmap(); + var temp = TestResources.GetTestBeatmapForImport(); using (File.OpenRead(temp)) - osu.Dependencies.Get().Import(temp); + await osu.Dependencies.Get().Import(temp); ensureLoaded(osu); File.Delete(temp); Assert.IsFalse(File.Exists(temp), "We likely held a read lock on the file when we shouldn't"); @@ -262,21 +366,13 @@ namespace osu.Game.Tests.Beatmaps.IO } } - private string createTemporaryBeatmap() + public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null) { - var temp = Path.GetTempFileName() + ".osz"; - File.Copy(TEST_OSZ_PATH, temp, true); - Assert.IsTrue(File.Exists(temp)); - return temp; - } - - private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu, string path = null) - { - var temp = path ?? createTemporaryBeatmap(); + var temp = path ?? TestResources.GetTestBeatmapForImport(); var manager = osu.Dependencies.Get(); - manager.Import(temp); + await manager.Import(temp); var imported = manager.GetAllUsableBeatmapSets(); @@ -292,11 +388,32 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); manager.Delete(imported); - Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0); - Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count); + checkBeatmapSetCount(osu, 0); + checkBeatmapSetCount(osu, 1, true); + checkSingleReferencedFileCount(osu, 0); + Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } + private void checkBeatmapSetCount(OsuGameBase osu, int expected, bool includeDeletePending = false) + { + var manager = osu.Dependencies.Get(); + + Assert.AreEqual(expected, includeDeletePending + ? manager.QueryBeatmapSets(_ => true).ToList().Count + : manager.GetAllUsableBeatmapSets().Count); + } + + private void checkBeatmapCount(OsuGameBase osu, int expected) + { + Assert.AreEqual(expected, osu.Dependencies.Get().QueryBeatmaps(_ => true).ToList().Count); + } + + private void checkSingleReferencedFileCount(OsuGameBase osu, int expected) + { + Assert.AreEqual(expected, osu.Dependencies.Get().QueryFiles(f => f.ReferenceCount == 1).Count()); + } + private OsuGameBase loadOsu(GameHost host) { var osu = new OsuGameBase(); @@ -305,7 +422,7 @@ namespace osu.Game.Tests.Beatmaps.IO return osu; } - private void ensureLoaded(OsuGameBase osu, int timeout = 60000) + private static void ensureLoaded(OsuGameBase osu, int timeout = 60000) { IEnumerable resultSets = null; var store = osu.Dependencies.Get(); @@ -343,7 +460,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(beatmap?.HitObjects.Any() == true); } - private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + private static void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) { Task task = Task.Run(() => { diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 0039516c0c..37e0565df0 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.IO; using System.Linq; @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadBeatmaps() { - using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) + using (var osz = TestResources.GetTestBeatmapStream()) { var reader = new ZipArchiveReader(osz); string[] expected = @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadMetadata() { - using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) + using (var osz = TestResources.GetTestBeatmapStream()) { var reader = new ZipArchiveReader(osz); @@ -72,9 +72,10 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public void TestReadFile() { - using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) + using (var osz = TestResources.GetTestBeatmapStream()) { var reader = new ZipArchiveReader(osz); + using (var stream = new StreamReader( reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) { diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index e91cd2a624..0d6ed67767 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Game.Online.Chat; @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Chat } [Test] - public void TestCaseInsensitiveLinks() + public void TestInsensitiveLinks() { Message result = MessageFormatter.FormatMessage(new Message { Content = "look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF" }); diff --git a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs index 49494b65b9..760a033aff 100644 --- a/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs +++ b/osu.Game.Tests/NonVisual/DifficultyAdjustmentModCombinationsTest.cs @@ -1,10 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; namespace osu.Game.Tests.NonVisual @@ -15,29 +18,29 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestNoMods() { - var combinations = new TestDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator().CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(1, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); } [Test] public void TestSingleMod() { - var combinations = new TestDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(2, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); } [Test] public void TestDoubleMod() { - var combinations = new TestDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(4, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is MultiMod); Assert.IsTrue(combinations[3] is ModB); @@ -49,10 +52,10 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleMods() { - var combinations = new TestDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModIncompatibleWithA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is ModIncompatibleWithA); } @@ -60,10 +63,10 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDoubleIncompatibleMods() { - var combinations = new TestDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModA(), new ModB(), new ModIncompatibleWithA(), new ModIncompatibleWithAAndB()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(8, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModA); Assert.IsTrue(combinations[2] is MultiMod); Assert.IsTrue(combinations[3] is ModB); @@ -83,10 +86,10 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestIncompatibleThroughBaseType() { - var combinations = new TestDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); + var combinations = new TestLegacyDifficultyCalculator(new ModAofA(), new ModIncompatibleWithAofA()).CreateDifficultyAdjustmentModCombinations(); Assert.AreEqual(3, combinations.Length); - Assert.IsTrue(combinations[0] is NoModMod); + Assert.IsTrue(combinations[0] is ModNoMod); Assert.IsTrue(combinations[1] is ModAofA); Assert.IsTrue(combinations[2] is ModIncompatibleWithAofA); } @@ -94,7 +97,7 @@ namespace osu.Game.Tests.NonVisual private class ModA : Mod { public override string Name => nameof(ModA); - public override string ShortenedName => nameof(ModA); + public override string Acronym => nameof(ModA); public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithA), typeof(ModIncompatibleWithAAndB) }; @@ -103,7 +106,7 @@ namespace osu.Game.Tests.NonVisual private class ModB : Mod { public override string Name => nameof(ModB); - public override string ShortenedName => nameof(ModB); + public override string Acronym => nameof(ModB); public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModIncompatibleWithAAndB) }; @@ -112,7 +115,7 @@ namespace osu.Game.Tests.NonVisual private class ModIncompatibleWithA : Mod { public override string Name => $"Incompatible With {nameof(ModA)}"; - public override string ShortenedName => $"Incompatible With {nameof(ModA)}"; + public override string Acronym => $"Incompatible With {nameof(ModA)}"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA) }; @@ -130,15 +133,15 @@ namespace osu.Game.Tests.NonVisual private class ModIncompatibleWithAAndB : Mod { public override string Name => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; - public override string ShortenedName => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; + public override string Acronym => $"Incompatible With {nameof(ModA)} and {nameof(ModB)}"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModA), typeof(ModB) }; } - private class TestDifficultyCalculator : DifficultyCalculator + private class TestLegacyDifficultyCalculator : DifficultyCalculator { - public TestDifficultyCalculator(params Mod[] mods) + public TestLegacyDifficultyCalculator(params Mod[] mods) : base(null, null) { DifficultyAdjustmentMods = mods; @@ -146,7 +149,20 @@ namespace osu.Game.Tests.NonVisual protected override Mod[] DifficultyAdjustmentMods { get; } - protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => throw new NotImplementedException(); + protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate) + { + throw new NotImplementedException(); + } + + protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) + { + throw new NotImplementedException(); + } + + protected override Skill[] CreateSkills(IBeatmap beatmap) + { + throw new NotImplementedException(); + } } } } diff --git a/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs new file mode 100644 index 0000000000..73387fa5ab --- /dev/null +++ b/osu.Game.Tests/NonVisual/FramedReplayinputHandlerTest.cs @@ -0,0 +1,284 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Replays; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class FramedReplayinputHandlerTest + { + private Replay replay; + private TestInputHandler handler; + + [SetUp] + public void SetUp() + { + handler = new TestInputHandler(replay = new Replay + { + Frames = new List + { + new TestReplayFrame(0), + new TestReplayFrame(1000), + new TestReplayFrame(2000), + new TestReplayFrame(3000, true), + new TestReplayFrame(4000, true), + new TestReplayFrame(5000, true), + new TestReplayFrame(7000, true), + new TestReplayFrame(8000), + } + }); + } + + [Test] + public void TestNormalPlayback() + { + Assert.IsNull(handler.CurrentFrame); + + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + + //if we hit the first frame perfectly, time should progress to it. + setTime(1000, 1000); + confirmCurrentFrame(1); + confirmNextFrame(2); + + //in between non-important frames should progress based on input. + setTime(1200, 1200); + confirmCurrentFrame(1); + + setTime(1400, 1400); + confirmCurrentFrame(1); + + // progressing beyond the next frame should force time to that frame once. + setTime(2200, 2000); + confirmCurrentFrame(2); + + // second attempt should progress to input time + setTime(2200, 2200); + confirmCurrentFrame(2); + + // entering important section + setTime(3000, 3000); + confirmCurrentFrame(3); + + // cannot progress within + setTime(3500, null); + confirmCurrentFrame(3); + + setTime(4000, 4000); + confirmCurrentFrame(4); + + // still cannot progress + setTime(4500, null); + confirmCurrentFrame(4); + + setTime(5200, 5000); + confirmCurrentFrame(5); + + // important section AllowedImportantTimeSpan allowance + setTime(5200, 5200); + confirmCurrentFrame(5); + + setTime(7200, 7000); + confirmCurrentFrame(6); + + setTime(7200, null); + confirmCurrentFrame(6); + + // exited important section + setTime(8200, 8000); + confirmCurrentFrame(7); + confirmNextFrame(null); + + setTime(8200, 8200); + confirmCurrentFrame(7); + confirmNextFrame(null); + } + + [Test] + public void TestIntroTime() + { + setTime(-1000, -1000); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(-500, -500); + confirmCurrentFrame(null); + confirmNextFrame(0); + + setTime(0, 0); + confirmCurrentFrame(0); + confirmNextFrame(1); + } + + [Test] + public void TestBasicRewind() + { + setTime(2800, 0); + setTime(2800, 1000); + setTime(2800, 2000); + setTime(2800, 2800); + confirmCurrentFrame(2); + confirmNextFrame(3); + + // pivot without crossing a frame boundary + setTime(2700, 2700); + confirmCurrentFrame(2); + confirmNextFrame(1); + + // cross current frame boundary; should not yet update frame + setTime(1980, 1980); + confirmCurrentFrame(2); + confirmNextFrame(1); + + setTime(1200, 1200); + confirmCurrentFrame(2); + confirmNextFrame(1); + + //ensure each frame plays out until start + setTime(-500, 1000); + confirmCurrentFrame(1); + confirmNextFrame(0); + + setTime(-500, 0); + confirmCurrentFrame(0); + confirmNextFrame(null); + + setTime(-500, -500); + confirmCurrentFrame(0); + confirmNextFrame(null); + } + + [Test] + public void TestRewindInsideImportantSection() + { + // fast forward to important section + while (handler.SetFrameFromTime(3000) != null) + { + } + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + + setTime(3500, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(4000, 4000); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4500, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(4000, null); + confirmCurrentFrame(4); + confirmNextFrame(5); + + setTime(3500, null); + confirmCurrentFrame(4); + confirmNextFrame(3); + + setTime(3000, 3000); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + + [Test] + public void TestRewindOutOfImportantSection() + { + // fast forward to important section + while (handler.SetFrameFromTime(3500) != null) + { + } + + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3200, null); + // next frame doesn't change even though direction reversed, because of important section. + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(3000, null); + confirmCurrentFrame(3); + confirmNextFrame(4); + + setTime(2800, 2800); + confirmCurrentFrame(3); + confirmNextFrame(2); + } + + private void setTime(double set, double? expect) + { + Assert.AreEqual(expect, handler.SetFrameFromTime(set)); + } + + private void confirmCurrentFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.CurrentFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.CurrentFrame.Time); + } + else + { + Assert.IsNull(handler.CurrentFrame); + } + } + + private void confirmNextFrame(int? frame) + { + if (frame.HasValue) + { + Assert.IsNotNull(handler.NextFrame); + Assert.AreEqual(replay.Frames[frame.Value].Time, handler.NextFrame.Time); + } + else + { + Assert.IsNull(handler.NextFrame); + } + } + + private class TestReplayFrame : ReplayFrame + { + public readonly bool IsImportant; + + public TestReplayFrame(double time, bool isImportant = false) + : base(time) + { + IsImportant = isImportant; + } + } + + private class TestInputHandler : FramedReplayInputHandler + { + public TestInputHandler(Replay replay) + : base(replay) + { + } + + protected override double AllowedImportantTimeSpan => 1000; + + protected override bool IsImportant(TestReplayFrame frame) => frame.IsImportant; + } + } +} diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs new file mode 100644 index 0000000000..1c78b63499 --- /dev/null +++ b/osu.Game.Tests/NonVisual/LimitedCapacityStackTest.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Game.Rulesets.Difficulty.Utils; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class LimitedCapacityStackTest + { + private const int capacity = 3; + + private LimitedCapacityStack stack; + + [SetUp] + public void Setup() + { + stack = new LimitedCapacityStack(capacity); + } + + [Test] + public void TestEmptyStack() + { + Assert.AreEqual(0, stack.Count); + + Assert.Throws(() => + { + int unused = stack[0]; + }); + + int count = 0; + foreach (var unused in stack) + count++; + + Assert.AreEqual(0, count); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + public void TestInRangeElements(int count) + { + // e.g. 0 -> 1 -> 2 + for (int i = 0; i < count; i++) + stack.Push(i); + + Assert.AreEqual(count, stack.Count); + + // e.g. 2 -> 1 -> 0 (reverse order) + for (int i = 0; i < stack.Count; i++) + Assert.AreEqual(count - 1 - i, stack[i]); + + // e.g. indices 3, 4, 5, 6 (out of range) + for (int i = stack.Count; i < stack.Count + capacity; i++) + { + Assert.Throws(() => + { + int unused = stack[i]; + }); + } + } + + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + public void TestOverflowElements(int count) + { + // e.g. 0 -> 1 -> 2 -> 3 + for (int i = 0; i < count; i++) + stack.Push(i); + + Assert.AreEqual(capacity, stack.Count); + + // e.g. 3 -> 2 -> 1 (reverse order) + for (int i = 0; i < stack.Count; i++) + Assert.AreEqual(count - 1 - i, stack[i]); + + // e.g. indices 3, 4, 5, 6 (out of range) + for (int i = stack.Count; i < stack.Count + capacity; i++) + { + Assert.Throws(() => + { + int unused = stack[i]; + }); + } + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + public void TestEnumerator(int count) + { + // e.g. 0 -> 1 -> 2 -> 3 + for (int i = 0; i < count; i++) + stack.Push(i); + + int enumeratorCount = 0; + int expectedValue = count - 1; + + foreach (var item in stack) + { + Assert.AreEqual(expectedValue, item); + enumeratorCount++; + expectedValue--; + } + + Assert.AreEqual(stack.Count, enumeratorCount); + } + } +} diff --git a/osu.Game.Tests/Resources/Resource.cs b/osu.Game.Tests/Resources/Resource.cs deleted file mode 100644 index ef90591561..0000000000 --- a/osu.Game.Tests/Resources/Resource.cs +++ /dev/null @@ -1,20 +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.IO; -using System.Reflection; - -namespace osu.Game.Tests.Resources -{ - public static class Resource - { - public static Stream OpenResource(string name) - { - var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); - - return Assembly.GetExecutingAssembly().GetManifestResourceStream($@"osu.Game.Tests.Resources.{name}") ?? - Assembly.LoadFrom(Path.Combine(localPath, @"osu.Game.Resources.dll")).GetManifestResourceStream($@"osu.Game.Resources.{name}"); - } - } -} diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs new file mode 100644 index 0000000000..9cb85a63bf --- /dev/null +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using NUnit.Framework; +using osu.Framework.IO.Stores; + +namespace osu.Game.Tests.Resources +{ + public static class TestResources + { + public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}"); + + public static Stream GetTestBeatmapStream() => new DllResourceStore("osu.Game.Resources.dll").GetStream("Beatmaps/241526 Soleily - Renatus.osz"); + + public static string GetTestBeatmapForImport() + { + var temp = Path.GetTempFileName() + ".osz"; + + using (var stream = GetTestBeatmapStream()) + using (var newFile = File.Create(temp)) + stream.CopyTo(newFile); + + Assert.IsTrue(File.Exists(temp)); + return temp; + } + } +} diff --git a/osu.Game.Tests/Resources/comments.osu b/osu.Game.Tests/Resources/comments.osu new file mode 100644 index 0000000000..78b48dd804 --- /dev/null +++ b/osu.Game.Tests/Resources/comments.osu @@ -0,0 +1,9 @@ +osu file format v14 + +[Colours] + +// Combo1: 0, 0, 0 +//Combo2: 0, 0, 0 + // Combo3: 0, 0, 0 + +Combo1: 100, 100, 100 // Comment at end of line \ No newline at end of file diff --git a/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu b/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu index 1e0e6f558e..8e7c504109 100644 --- a/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu +++ b/osu.Game.Tests/Resources/controlpoint-custom-samplebank.osu @@ -8,9 +8,12 @@ SampleSet: Normal 2638,-100,4,1,1,40,0,0 3107,-100,4,1,2,40,0,0 3576,-100,4,1,0,40,0,0 +18287,-100,4,2,11,80,0,1 +18595,-100,4,2,8,80,0,1 [HitObjects] 255,193,2170,1,0,0:0:0:0: 256,191,2638,5,0,0:0:0:0: 255,193,3107,1,0,0:0:0:0: 256,191,3576,1,0,0:0:0:0: +112,200,18493,6,0,L|104:248,1,35,8|0,0:0|0:0,0:0:0:0: \ No newline at end of file diff --git a/osu.Game.Tests/Resources/hitobject-file-samples.osu b/osu.Game.Tests/Resources/hitobject-file-samples.osu index 588672e2d9..7c69f259b8 100644 --- a/osu.Game.Tests/Resources/hitobject-file-samples.osu +++ b/osu.Game.Tests/Resources/hitobject-file-samples.osu @@ -13,4 +13,4 @@ SampleSet: Normal 255,193,2170,1,0,0:0:0:0:hit_1.wav 256,191,2638,5,0,0:0:0:0:hit_2.wav 255,193,3107,1,0,0:0:0:0: -256,191,3576,1,0,0:0:0:0:hit_1.wav +256,191,3576,1,0,0:0:0:70:hit_1.wav diff --git a/osu.Game.Tests/Resources/overlapping-control-points.osu b/osu.Game.Tests/Resources/overlapping-control-points.osu new file mode 100644 index 0000000000..31d38a3d01 --- /dev/null +++ b/osu.Game.Tests/Resources/overlapping-control-points.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[TimingPoints] + +// Timing then inherited +0,500,4,2,0,100,1,0 +0,-66.6666666666667,4,3,0,100,0,1 + +// Inherited then timing (equivalent to previous) +1000,-66.6666666666667,4,3,0,100,0,1 +1000,500,4,2,0,100,1,0 + +// Inherited then timing (different to previous) +2000,-133.333333333333,4,1,0,100,0,0 +2000,250,4,2,0,100,1,0 + +// Timing then inherited (different to previous) +3000,500,4,2,0,100,1,0 +3000,-66.6666666666667,4,3,0,100,0,1 diff --git a/osu.Game.Tests/Resources/skin-empty.ini b/osu.Game.Tests/Resources/skin-empty.ini new file mode 100644 index 0000000000..b6c319fe3c --- /dev/null +++ b/osu.Game.Tests/Resources/skin-empty.ini @@ -0,0 +1,2 @@ +[General] +Name: test skin \ No newline at end of file diff --git a/osu.Game.Tests/Resources/skin.ini b/osu.Game.Tests/Resources/skin.ini new file mode 100644 index 0000000000..0e5737b4ea --- /dev/null +++ b/osu.Game.Tests/Resources/skin.ini @@ -0,0 +1,8 @@ +[General] +Name: test skin + +[Colours] +Combo1 : 142,199,255 +Combo2 : 255,128,128 +Combo3 : 128,255,255 +Combo7 : 100,100,100,100 \ No newline at end of file diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs new file mode 100644 index 0000000000..4babb07213 --- /dev/null +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -0,0 +1,160 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Tests.Resources; +using osu.Game.Users; + +namespace osu.Game.Tests.Scores.IO +{ + public class ImportScoreTest + { + [Test] + public async Task TestBasicImport() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestBasicImport")) + { + try + { + var osu = await loadOsu(host); + + var toImport = new ScoreInfo + { + Rank = ScoreRank.B, + TotalScore = 987654, + Accuracy = 0.8, + MaxCombo = 500, + Combo = 250, + User = new User { Username = "Test user" }, + Date = DateTimeOffset.Now, + OnlineScoreID = 12345, + }; + + var imported = await loadIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Rank, imported.Rank); + Assert.AreEqual(toImport.TotalScore, imported.TotalScore); + Assert.AreEqual(toImport.Accuracy, imported.Accuracy); + Assert.AreEqual(toImport.MaxCombo, imported.MaxCombo); + Assert.AreEqual(toImport.Combo, imported.Combo); + Assert.AreEqual(toImport.User.Username, imported.User.Username); + Assert.AreEqual(toImport.Date, imported.Date); + Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportMods() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportMods")) + { + try + { + var osu = await loadOsu(host); + + var toImport = new ScoreInfo + { + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + }; + + var imported = await loadIntoOsu(osu, toImport); + + Assert.IsTrue(imported.Mods.Any(m => m is OsuModHardRock)); + Assert.IsTrue(imported.Mods.Any(m => m is OsuModDoubleTime)); + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportStatistics() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportStatistics")) + { + try + { + var osu = await loadOsu(host); + + var toImport = new ScoreInfo + { + Statistics = new Dictionary + { + { HitResult.Perfect, 100 }, + { HitResult.Miss, 50 } + } + }; + + var imported = await loadIntoOsu(osu, toImport); + + Assert.AreEqual(toImport.Statistics[HitResult.Perfect], imported.Statistics[HitResult.Perfect]); + Assert.AreEqual(toImport.Statistics[HitResult.Miss], imported.Statistics[HitResult.Miss]); + } + finally + { + host.Exit(); + } + } + } + + private async Task loadIntoOsu(OsuGameBase osu, ScoreInfo score) + { + var beatmapManager = osu.Dependencies.Get(); + + score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + score.Ruleset = new OsuRuleset().RulesetInfo; + + var scoreManager = osu.Dependencies.Get(); + await scoreManager.Import(score); + + return scoreManager.GetAllUsableScores().First(); + } + + private async Task loadOsu(GameHost host) + { + var osu = new OsuGameBase(); + +#pragma warning disable 4014 + Task.Run(() => host.Run(osu)); +#pragma warning restore 4014 + + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + + var beatmapFile = TestResources.GetTestBeatmapForImport(); + var beatmapManager = osu.Dependencies.Get(); + await beatmapManager.Import(beatmapFile); + + return osu; + } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Task task = Task.Run(() => + { + while (!result()) Thread.Sleep(200); + }); + + Assert.IsTrue(task.Wait(timeout), failureMessage); + } + } +} diff --git a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs index 5e01213a48..d7f709dc03 100644 --- a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs +++ b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Game.Rulesets.UI.Scrolling.Algorithms; diff --git a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs index c1a5a0f3c9..106aa88be3 100644 --- a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs +++ b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Lists; diff --git a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs index 990fb92e6c..1f0c069f8d 100644 --- a/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs +++ b/osu.Game.Tests/ScrollAlgorithms/SequentialScrollTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Lists; diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs new file mode 100644 index 0000000000..24ef9e4535 --- /dev/null +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.IO; +using NUnit.Framework; +using osu.Game.Skinning; +using osu.Game.Tests.Resources; +using osuTK.Graphics; + +namespace osu.Game.Tests.Skins +{ + [TestFixture] + public class LegacySkinDecoderTest + { + [TestCase(true)] + [TestCase(false)] + public void TestDecodeSkinColours(bool hasColours) + { + var decoder = new LegacySkinDecoder(); + + using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini")) + using (var stream = new StreamReader(resStream)) + { + var comboColors = decoder.Decode(stream).ComboColours; + + List expectedColors; + if (hasColours) + expectedColors = new List + { + new Color4(142, 199, 255, 255), + new Color4(255, 128, 128, 255), + new Color4(128, 255, 255, 255), + new Color4(100, 100, 100, 100), + }; + else + expectedColors = new DefaultSkin().Configuration.ComboColours; + + Assert.AreEqual(expectedColors.Count, comboColors.Count); + for (int i = 0; i < expectedColors.Count; i++) + Assert.AreEqual(expectedColors[i], comboColors[i]); + } + } + } +} diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs new file mode 100644 index 0000000000..8b941e4633 --- /dev/null +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenBeatmap.cs @@ -0,0 +1,422 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; +using osu.Game.Screens; +using osu.Game.Screens.Backgrounds; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Screens.Select; +using osu.Game.Tests.Resources; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Background +{ + [TestFixture] + public class TestSceneBackgroundScreenBeatmap : ManualInputManagerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ScreenWithBeatmapBackground), + typeof(PlayerLoader), + typeof(Player), + typeof(UserDimContainer), + typeof(OsuScreen) + }; + + private DummySongSelect songSelect; + private TestPlayerLoader playerLoader; + private TestPlayer player; + private DatabaseContextFactory factory; + private BeatmapManager manager; + private RulesetStore rulesets; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + factory = new DatabaseContextFactory(LocalStorage); + factory.ResetDatabase(); + + using (var usage = factory.Get()) + usage.Migrate(); + + factory.ResetDatabase(); + + using (var usage = factory.Get()) + usage.Migrate(); + + Dependencies.Cache(rulesets = new RulesetStore(factory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, Beatmap.Default)); + Dependencies.Cache(new OsuConfigManager(LocalStorage)); + + manager.Import(TestResources.GetTestBeatmapForImport()).Wait(); + + Beatmap.SetDefault(); + } + + [SetUp] + public virtual void SetUp() => Schedule(() => + { + Child = new OsuScreenStack(songSelect = new DummySongSelect()) + { + RelativeSizeAxes = Axes.Both + }; + }); + + /// + /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel. + /// + [Test] + public void PlayerLoaderSettingsHoverTest() + { + setupUserSettings(); + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer { BlockLoad = true }))); + AddUntilStep("Wait for Player Loader to load", () => playerLoader?.IsLoaded ?? false); + AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); + AddStep("Trigger background preview", () => + { + InputManager.MoveMouseTo(playerLoader.ScreenPos); + InputManager.MoveMouseTo(playerLoader.VisualSettingsPos); + }); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); + waitForDim(); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); + } + + /// + /// In the case of a user triggering the dim preview the instant player gets loaded, then moving the cursor off of the visual settings: + /// The OnHover of PlayerLoader will trigger, which could potentially cause visual settings to be unapplied unless checked for in PlayerLoader. + /// We need to check that in this scenario, the dim and blur is still properly applied after entering player. + /// + [Test] + public void PlayerLoaderTransitionTest() + { + performFullSetup(); + AddStep("Trigger hover event", () => playerLoader.TriggerOnHover()); + AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + } + + /// + /// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard. + /// + [Test] + public void StoryboardBackgroundVisibilityTest() + { + performFullSetup(); + createFakeStoryboard(); + AddStep("Storyboard Enabled", () => + { + player.ReplacesBackground.Value = true; + player.StoryboardEnabled.Value = true; + }); + waitForDim(); + AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible()); + AddStep("Storyboard Disabled", () => + { + player.ReplacesBackground.Value = false; + player.StoryboardEnabled.Value = false; + }); + waitForDim(); + AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && player.IsStoryboardInvisible()); + } + + /// + /// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background. + /// + [Test] + public void StoryboardTransitionTest() + { + performFullSetup(); + createFakeStoryboard(); + AddStep("Exit to song select", () => player.Exit()); + waitForDim(); + AddAssert("Background is visible", () => songSelect.IsBackgroundVisible()); + } + + /// + /// Check if the is properly accepting user-defined visual changes at all. + /// + [Test] + public void DisableUserDimTest() + { + performFullSetup(); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false); + waitForDim(); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); + AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + } + + /// + /// Check if the visual settings container retains dim and blur when pausing + /// + [Test] + public void PauseTest() + { + performFullSetup(true); + AddStep("Pause", () => player.Pause()); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("Unpause", () => player.Resume()); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + } + + /// + /// Check if the visual settings container removes user dim when suspending for + /// + [Test] + public void TransitionTest() + { + performFullSetup(); + var results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }); + AddStep("Transition to Results", () => player.Push(results)); + AddUntilStep("Wait for results is current", results.IsCurrentScreen); + waitForDim(); + AddAssert("Screen is undimmed, original background retained", () => + songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); + } + + /// + /// Check if background gets undimmed and unblurred when leaving for + /// + [Test] + public void TransitionOutTest() + { + performFullSetup(); + AddStep("Exit to song select", () => player.Exit()); + waitForDim(); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect()); + } + + /// + /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim. + /// + [Test] + public void ResumeFromPlayerTest() + { + performFullSetup(); + AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos)); + AddStep("Resume PlayerLoader", () => player.Restart()); + waitForDim(); + AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); + waitForDim(); + AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); + } + + private void waitForDim() => AddWaitStep("Wait for dim", 5); + + private void createFakeStoryboard() => AddStep("Create storyboard", () => + { + player.StoryboardEnabled.Value = false; + player.ReplacesBackground.Value = false; + player.CurrentStoryboardContainer.Add(new OsuSpriteText + { + Size = new Vector2(250, 50), + Alpha = 1, + Colour = Color4.Tomato, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "THIS IS A STORYBOARD", + }); + }); + + private void performFullSetup(bool allowPause = false) + { + setupUserSettings(); + + AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new TestPlayer(allowPause)))); + + AddUntilStep("Wait for Player Loader to load", () => playerLoader.IsLoaded); + AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); + AddUntilStep("Wait for player to load", () => player.IsLoaded); + } + + private void setupUserSettings() + { + AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null); + AddStep("Set default user settings", () => + { + Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); + songSelect.DimLevel.Value = 0.7f; + songSelect.BlurLevel.Value = 0.4f; + }); + } + + private class DummySongSelect : PlaySongSelect + { + protected override BackgroundScreen CreateBackground() + { + FadeAccessibleBackground background = new FadeAccessibleBackground(Beatmap.Value); + DimEnabled.BindTo(background.EnableUserDim); + return background; + } + + public readonly Bindable DimEnabled = new Bindable(); + public readonly Bindable DimLevel = new Bindable(); + public readonly Bindable BlurLevel = new Bindable(); + + public new BeatmapCarousel Carousel => base.Carousel; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.DimLevel, DimLevel); + config.BindWith(OsuSetting.BlurLevel, BlurLevel); + } + + public bool IsBackgroundDimmed() => ((FadeAccessibleBackground)Background).CurrentColour == OsuColour.Gray(1 - (float)DimLevel.Value); + + public bool IsBackgroundUndimmed() => ((FadeAccessibleBackground)Background).CurrentColour == Color4.White; + + public bool IsUserBlurApplied() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2((float)BlurLevel.Value * 25); + + public bool IsUserBlurDisabled() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(0); + + public bool IsBackgroundInvisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 0; + + public bool IsBackgroundVisible() => ((FadeAccessibleBackground)Background).CurrentAlpha == 1; + + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); + + /// + /// Make sure every time a screen gets pushed, the background doesn't get replaced + /// + /// Whether or not the original background (The one created in DummySongSelect) is still the current background + public bool IsBackgroundCurrent() => ((FadeAccessibleBackground)Background).IsCurrentScreen(); + } + + private class FadeAccessibleResults : SoloResults + { + public FadeAccessibleResults(ScoreInfo score) + : base(score) + { + } + + protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); + + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); + } + + private class TestPlayer : Visual.TestPlayer + { + protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); + + protected override UserDimContainer CreateStoryboardContainer() + { + return new TestUserDimContainer(true) + { + RelativeSizeAxes = Axes.Both, + Alpha = 1, + EnableUserDim = { Value = true } + }; + } + + public UserDimContainer CurrentStoryboardContainer => StoryboardContainer; + + // Whether or not the player should be allowed to load. + public bool BlockLoad; + + public Bindable StoryboardEnabled; + public readonly Bindable ReplacesBackground = new Bindable(); + public readonly Bindable IsPaused = new Bindable(); + + public TestPlayer(bool allowPause = true) + : base(allowPause) + { + } + + public bool IsStoryboardVisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha == 1; + + public bool IsStoryboardInvisible() => ((TestUserDimContainer)CurrentStoryboardContainer).CurrentAlpha <= 1; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, CancellationToken token) + { + while (BlockLoad && !token.IsCancellationRequested) + Thread.Sleep(1); + + StoryboardEnabled = config.GetBindable(OsuSetting.ShowStoryboard); + ReplacesBackground.BindTo(Background.StoryboardReplacesBackground); + DrawableRuleset.IsPaused.BindTo(IsPaused); + } + } + + private class TestPlayerLoader : PlayerLoader + { + public VisualSettings VisualSettingsPos => VisualSettings; + public BackgroundScreen ScreenPos => Background; + + public TestPlayerLoader(Player player) + : base(() => player) + { + } + + public void TriggerOnHover() => OnHover(new HoverEvent(new InputState())); + + public bool IsBlurCorrect() => ((FadeAccessibleBackground)Background).CurrentBlur == new Vector2(BACKGROUND_BLUR); + + protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value); + } + + private class FadeAccessibleBackground : BackgroundScreenBeatmap + { + protected override UserDimContainer CreateFadeContainer() => fadeContainer = new TestUserDimContainer { RelativeSizeAxes = Axes.Both }; + + public Color4 CurrentColour => fadeContainer.CurrentColour; + + public float CurrentAlpha => fadeContainer.CurrentAlpha; + + public Vector2 CurrentBlur => Background.BlurSigma; + + private TestUserDimContainer fadeContainer; + + public FadeAccessibleBackground(WorkingBeatmap beatmap) + : base(beatmap) + { + } + } + + private class TestUserDimContainer : UserDimContainer + { + public Color4 CurrentColour => DimContainer.Colour; + public float CurrentAlpha => DimContainer.Alpha; + + public TestUserDimContainer(bool isStoryboard = false) + : base(isStoryboard) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs new file mode 100644 index 0000000000..55aaeed8bf --- /dev/null +++ b/osu.Game.Tests/Visual/Components/TestSceneIdleTracker.cs @@ -0,0 +1,173 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Input; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Components +{ + [TestFixture] + public class TestSceneIdleTracker : ManualInputManagerTestScene + { + private IdleTrackingBox box1; + private IdleTrackingBox box2; + private IdleTrackingBox box3; + private IdleTrackingBox box4; + + private IdleTrackingBox[] boxes; + + [SetUp] + public void SetUp() => Schedule(() => + { + InputManager.MoveMouseTo(Vector2.Zero); + + Children = boxes = new[] + { + box1 = new IdleTrackingBox(2000) + { + Name = "TopLeft", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + box2 = new IdleTrackingBox(4000) + { + Name = "TopRight", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + box3 = new IdleTrackingBox(6000) + { + Name = "BottomLeft", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + box4 = new IdleTrackingBox(8000) + { + Name = "BottomRight", + RelativeSizeAxes = Axes.Both, + Colour = Color4.Orange, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + }; + }); + + [Test] + public void TestNudge() + { + AddStep("move to top left", () => InputManager.MoveMouseTo(box1)); + + waitForAllIdle(); + + AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); + + checkIdleStatus(1, false); + checkIdleStatus(2, true); + checkIdleStatus(3, true); + checkIdleStatus(4, true); + } + + [Test] + public void TestMovement() + { + AddStep("move to top right", () => InputManager.MoveMouseTo(box2)); + + checkIdleStatus(1, true); + checkIdleStatus(2, false); + checkIdleStatus(3, true); + checkIdleStatus(4, true); + + AddStep("move to bottom left", () => InputManager.MoveMouseTo(box3)); + AddStep("move to bottom right", () => InputManager.MoveMouseTo(box4)); + + checkIdleStatus(1, true); + checkIdleStatus(2, false); + checkIdleStatus(3, false); + checkIdleStatus(4, false); + + waitForAllIdle(); + } + + [Test] + public void TestTimings() + { + AddStep("move to centre", () => InputManager.MoveMouseTo(Content)); + + checkIdleStatus(1, false); + checkIdleStatus(2, false); + checkIdleStatus(3, false); + checkIdleStatus(4, false); + + AddUntilStep("Wait for idle", () => box1.IsIdle); + + checkIdleStatus(1, true); + checkIdleStatus(2, false); + checkIdleStatus(3, false); + checkIdleStatus(4, false); + + AddUntilStep("Wait for idle", () => box2.IsIdle); + + checkIdleStatus(1, true); + checkIdleStatus(2, true); + checkIdleStatus(3, false); + checkIdleStatus(4, false); + + AddUntilStep("Wait for idle", () => box3.IsIdle); + + checkIdleStatus(1, true); + checkIdleStatus(2, true); + checkIdleStatus(3, true); + checkIdleStatus(4, false); + + waitForAllIdle(); + } + + private void checkIdleStatus(int box, bool expectedIdle) + { + AddAssert($"box {box} is {(expectedIdle ? "idle" : "active")}", () => boxes[box - 1].IsIdle == expectedIdle); + } + + private void waitForAllIdle() + { + AddUntilStep("Wait for all idle", () => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle); + } + + private class IdleTrackingBox : CompositeDrawable + { + private readonly IdleTracker idleTracker; + + public bool IsIdle => idleTracker.IsIdle.Value; + + public IdleTrackingBox(double timeToIdle) + { + Box box; + + Alpha = 0.6f; + Scale = new Vector2(0.6f); + + InternalChildren = new Drawable[] + { + idleTracker = new IdleTracker(timeToIdle), + box = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }; + + idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle.NewValue ? Color4.White : Color4.Black, true); + } + } + } +} diff --git a/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs new file mode 100644 index 0000000000..fb10015ef4 --- /dev/null +++ b/osu.Game.Tests/Visual/Components/TestScenePollingComponent.cs @@ -0,0 +1,147 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Threading.Tasks; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Game.Graphics.Sprites; +using osu.Game.Online; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Components +{ + public class TestScenePollingComponent : OsuTestScene + { + private Container pollBox; + private TestPoller poller; + + private const float safety_adjust = 1f; + private int count; + + [SetUp] + public void SetUp() => Schedule(() => + { + count = 0; + + Children = new Drawable[] + { + pollBox = new Container + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.4f), + Colour = Color4.LimeGreen, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Poll!", + } + } + } + }; + }); + + [Test] + [Ignore("polling is threaded, and it's very hard to hook into it correctly")] + public void TestInstantPolling() + { + createPoller(true); + + AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust); + checkCount(1); + checkCount(2); + checkCount(3); + + AddStep("set poll interval to 5", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5); + checkCount(4); + checkCount(4); + checkCount(4); + + skip(); + + checkCount(5); + checkCount(5); + + AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust); + checkCount(6); + checkCount(7); + } + + [Test] + [Ignore("i have no idea how to fix the timing of this one")] + public void TestSlowPolling() + { + createPoller(false); + + AddStep("set poll interval to 1", () => poller.TimeBetweenPolls = TimePerAction * safety_adjust * 5); + checkCount(0); + skip(); + checkCount(0); + skip(); + skip(); + checkCount(0); + skip(); + skip(); + checkCount(0); + } + + private void skip() => AddStep("skip", () => + { + // could be 4 or 5 at this point due to timing discrepancies (safety_adjust @ 0.2 * 5 ~= 1) + // easiest to just ignore the value at this point and move on. + }); + + private void checkCount(int checkValue) + { + AddAssert($"count is {checkValue}", () => + { + Logger.Log($"value is {count}"); + return count == checkValue; + }); + } + + private void createPoller(bool instant) => AddStep("create poller", () => + { + poller?.Expire(); + + Add(poller = instant ? new TestPoller() : new TestSlowPoller()); + poller.OnPoll += () => + { + pollBox.FadeOutFromOne(500); + count++; + }; + }); + + protected override double TimePerAction => 500; + + public class TestPoller : PollingComponent + { + public event Action OnPoll; + + protected override Task Poll() + { + Schedule(() => OnPoll?.Invoke()); + return base.Poll(); + } + } + + public class TestSlowPoller : TestPoller + { + protected override Task Poll() => Task.Delay((int)(TimeBetweenPolls / 2f / Clock.Rate)).ContinueWith(_ => base.Poll()); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs similarity index 86% rename from osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs rename to osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index 041fce6ce3..df6740421b 100644 --- a/osu.Game.Tests/Visual/TestCasePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers; using osu.Game.Audio; using osu.Game.Beatmaps; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Components { - public class TestCasePreviewTrackManager : OsuTestCase, IPreviewTrackOwner + public class TestScenePreviewTrackManager : OsuTestScene, IPreviewTrackOwner { private readonly PreviewTrackManager trackManager = new TestPreviewTrackManager(); @@ -111,16 +111,19 @@ namespace osu.Game.Tests.Visual private class TestPreviewTrackManager : PreviewTrackManager { - protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TestPreviewTrack(beatmapSetInfo, trackManager); + protected override TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TestPreviewTrack(beatmapSetInfo, trackStore); protected class TestPreviewTrack : TrackManagerPreviewTrack { - public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) + private readonly ITrackStore trackManager; + + public TestPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager) : base(beatmapSetInfo, trackManager) { + this.trackManager = trackManager; } - protected override Track GetTrack() => new TrackVirtual { Length = 100000 }; + protected override Track GetTrack() => trackManager.GetVirtual(100000); } } } diff --git a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs similarity index 71% rename from osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs rename to osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs index c11bb5d17b..7531a7be2c 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatDivisorControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneBeatDivisorControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -9,9 +9,9 @@ using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { - public class TestCaseBeatDivisorControl : OsuTestCase + public class TestSceneBeatDivisorControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(BindableBeatDivisor) }; diff --git a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs similarity index 54% rename from osu.Game.Tests/Visual/TestCaseEditorCompose.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs index 5c53fdfac4..608df1965e 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorCompose.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorCompose.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,19 +7,18 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit.Compose; -using osu.Game.Tests.Beatmaps; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorCompose : EditorClockTestCase + public class TestSceneEditorCompose : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(ComposeScreen) }; [BackgroundDependencyLoader] private void load() { - Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Child = new ComposeScreen(); } } diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs similarity index 79% rename from osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs index 9df36b0bc1..1709067d5d 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeRadioButtons.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeRadioButtons.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,14 +7,14 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Edit.Components.RadioButtons; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorComposeRadioButtons : OsuTestCase + public class TestSceneEditorComposeRadioButtons : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableRadioButton) }; - public TestCaseEditorComposeRadioButtons() + public TestSceneEditorComposeRadioButtons() { RadioButtonCollection collection; Add(collection = new RadioButtonCollection diff --git a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs similarity index 89% rename from osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index f548dfe666..a8c2362910 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osuTK; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,12 +14,13 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Compose.Components.Timeline; +using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorComposeTimeline : EditorClockTestCase + public class TestSceneEditorComposeTimeline : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -30,9 +31,9 @@ namespace osu.Game.Tests.Visual }; [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { - Beatmap.Value = new WaveformTestBeatmap(); + Beatmap.Value = new WaveformTestBeatmap(audio); Children = new Drawable[] { @@ -85,7 +86,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load(IAdjustableClock adjustableClock, IBindableBeatmap beatmap) + private void load(IAdjustableClock adjustableClock, IBindable beatmap) { this.adjustableClock = adjustableClock; this.beatmap.BindTo(beatmap); diff --git a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs similarity index 94% rename from osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs index eab799011d..53c2d62067 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorMenuBar.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorMenuBar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -10,14 +10,14 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Components.Menus; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorMenuBar : OsuTestCase + public class TestSceneEditorMenuBar : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(EditorMenuBar), typeof(ScreenSelectionTabControl) }; - public TestCaseEditorMenuBar() + public TestSceneEditorMenuBar() { Add(new Container { diff --git a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs index 8bbbe6b918..b997d6aaeb 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; @@ -10,16 +10,15 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorSeekSnapping : EditorClockTestCase + public class TestSceneEditorSeekSnapping : EditorClockTestScene { - public TestCaseEditorSeekSnapping() + public TestSceneEditorSeekSnapping() { BeatDivisor.Value = 4; } @@ -33,7 +32,7 @@ namespace osu.Game.Tests.Visual { TimingPoints = { - new TimingControlPoint { Time = 0, BeatLength = 200}, + new TimingControlPoint { Time = 0, BeatLength = 200 }, new TimingControlPoint { Time = 100, BeatLength = 400 }, new TimingControlPoint { Time = 175, BeatLength = 800 }, new TimingControlPoint { Time = 350, BeatLength = 200 }, @@ -48,7 +47,7 @@ namespace osu.Game.Tests.Visual } }; - Beatmap.Value = new TestWorkingBeatmap(testBeatmap); + Beatmap.Value = CreateWorkingBeatmap(testBeatmap); Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; } diff --git a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs similarity index 63% rename from osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs rename to osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs index ff28a0aad2..2e04eb50ca 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSummaryTimeline.cs @@ -1,27 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osuTK; -using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Game.Rulesets.Osu; -using osu.Game.Tests.Beatmaps; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseEditorSummaryTimeline : EditorClockTestCase + public class TestSceneEditorSummaryTimeline : EditorClockTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; [BackgroundDependencyLoader] private void load() { - Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Add(new SummaryTimeline { diff --git a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs similarity index 86% rename from osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs rename to osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs index 4109b72989..7accbe2fa8 100644 --- a/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneHitObjectComposer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,7 +7,6 @@ using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Timing; -using osuTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -19,17 +18,17 @@ using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components; -using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] [Cached(Type = typeof(IPlacementHandler))] - public class TestCaseHitObjectComposer : OsuTestCase, IPlacementHandler + public class TestSceneHitObjectComposer : OsuTestScene, IPlacementHandler { public override IReadOnlyList RequiredTypes => new[] { - typeof(SelectionBox), + typeof(SelectionHandler), typeof(DragBox), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), @@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - Beatmap.Value = new TestWorkingBeatmap(new Beatmap + Beatmap.Value = CreateWorkingBeatmap(new Beatmap { HitObjects = new List { diff --git a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs b/osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs similarity index 64% rename from osu.Game.Tests/Visual/TestCasePlaybackControl.cs rename to osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs index 4352b2bc3a..0d4fe4366d 100644 --- a/osu.Game.Tests/Visual/TestCasePlaybackControl.cs +++ b/osu.Game.Tests/Visual/Editor/TestScenePlaybackControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; @@ -7,13 +7,12 @@ using osu.Framework.Graphics; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Screens.Edit.Components; -using osu.Game.Tests.Beatmaps; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCasePlaybackControl : OsuTestCase + public class TestScenePlaybackControl : OsuTestScene { [BackgroundDependencyLoader] private void load() @@ -26,10 +25,10 @@ namespace osu.Game.Tests.Visual { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(200,100) + Size = new Vector2(200, 100) }; - Beatmap.Value = new TestWorkingBeatmap(new Beatmap()); + Beatmap.Value = CreateWorkingBeatmap(new Beatmap()); Child = playback; } diff --git a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs similarity index 82% rename from osu.Game.Tests/Visual/TestCaseWaveContainer.cs rename to osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs index 69f1e33579..de19727251 100644 --- a/osu.Game.Tests/Visual/TestCaseWaveContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneWaveContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; @@ -12,10 +12,10 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { [TestFixture] - public class TestCaseWaveContainer : OsuTestCase + public class TestSceneWaveContainer : OsuTestScene { [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual { Anchor = Anchor.Centre, Origin = Anchor.Centre, - TextSize = 20, + Font = OsuFont.GetFont(size: 20), Text = @"Wave Container", }, }, diff --git a/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs b/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs new file mode 100644 index 0000000000..e2762f3d5f --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestSceneWaveform.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Osu; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Editor +{ + [TestFixture] + public class TestSceneWaveform : OsuTestScene + { + private WorkingBeatmap waveformBeatmap; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + waveformBeatmap = new WaveformTestBeatmap(audio); + } + + [TestCase(1f)] + [TestCase(1f / 2)] + [TestCase(1f / 4)] + [TestCase(1f / 8)] + [TestCase(1f / 16)] + [TestCase(0f)] + public void TestResolution(float resolution) + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => + { + Child = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Children = new Drawable[] + { + graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Resolution = resolution, + Waveform = waveformBeatmap.Waveform, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.75f + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = $"Resolution: {resolution:0.00}" + } + } + } + } + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + [Test] + public void TestDefaultBeatmap() + { + TestWaveformGraph graph = null; + + AddStep("add graph", () => + { + Child = new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Child = graph = new TestWaveformGraph + { + RelativeSizeAxes = Axes.Both, + Waveform = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).Waveform, + }, + }; + }); + + AddUntilStep("wait for load", () => graph.ResampledWaveform != null); + } + + public class TestWaveformGraph : WaveformGraph + { + public new Waveform ResampledWaveform => base.ResampledWaveform; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs similarity index 70% rename from osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs rename to osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs index 7dca99685e..da8702209c 100644 --- a/osu.Game.Tests/Visual/TestCaseZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneZoomableScrollContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Graphics; @@ -8,48 +8,60 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.MathUtils; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Editor { - public class TestCaseZoomableScrollContainer : ManualInputManagerTestCase + public class TestSceneZoomableScrollContainer : ManualInputManagerTestScene { - private readonly ZoomableScrollContainer scrollContainer; - private readonly Drawable innerBox; + private ZoomableScrollContainer scrollContainer; + private Drawable innerBox; - public TestCaseZoomableScrollContainer() + [SetUpSteps] + public void SetUpSteps() { - Children = new Drawable[] + AddStep("Add new scroll container", () => { - new Container + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 250, - Width = 0.75f, - Children = new Drawable[] + new Container { - new Box + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 250, + Width = 0.75f, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(30) - }, - scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } - } - }, - new MenuCursor() - }; + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(30) + }, + scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both } + } + }, + new MenuCursor() + }; - scrollContainer.Add(innerBox = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + scrollContainer.Add(innerBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(new Color4(0.8f, 0.6f, 0.4f, 1f), new Color4(0.4f, 0.6f, 0.8f, 1f)) + }); }); + AddUntilStep("Scroll container is loaded", () => scrollContainer.LoadState >= LoadState.Loaded); + } + + [Test] + public void TestWidthInitialization() + { + AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0); } [Test] diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs new file mode 100644 index 0000000000..452ac859de --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; +using System.Linq; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [Description("Player instantiated with an autoplay mod.")] + public class TestSceneAutoplay : AllPlayersTestScene + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + return new ScoreAccessiblePlayer(); + } + + protected override void AddCheckSteps() + { + AddUntilStep("score above zero", () => ((ScoreAccessiblePlayer)Player).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessiblePlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); + } + + private class ScoreAccessiblePlayer : TestPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public ScoreAccessiblePlayer() + : base(false, false) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs similarity index 89% rename from osu.Game.Tests/Visual/TestCaseBreakOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs index 442ca1322a..3cd1b8307a 100644 --- a/osu.Game.Tests/Visual/TestCaseBreakOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBreakOverlay.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Game.Beatmaps.Timing; using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps.Timing; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseBreakOverlay : OsuTestCase + public class TestSceneBreakOverlay : OsuTestScene { private readonly BreakOverlay breakOverlay; - public TestCaseBreakOverlay() + public TestSceneBreakOverlay() { Child = breakOverlay = new BreakOverlay(true); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs new file mode 100644 index 0000000000..f4e8a68819 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailAnimation.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneFailAnimation : AllPlayersTestScene + { + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = Array.Empty(); + return new FailPlayer(); + } + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(AllPlayersTestScene), + typeof(TestPlayer), + typeof(Player), + }; + + protected override void AddCheckSteps() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("wait for fail overlay", () => ((FailPlayer)Player).FailOverlay.State.Value == Visibility.Visible); + } + + private class FailPlayer : TestPlayer + { + public new FailOverlay FailOverlay => base.FailOverlay; + + public FailPlayer() + : base(false, false) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + ScoreProcessor.FailConditions += (_, __) => true; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs new file mode 100644 index 0000000000..5eb71e92c2 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFrameStabilityContainer.cs @@ -0,0 +1,174 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneFrameStabilityContainer : OsuTestScene + { + private readonly ManualClock manualClock; + + private readonly Container mainContainer; + + private ClockConsumingChild consumer; + + public TestSceneFrameStabilityContainer() + { + Child = mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Clock = new FramedClock(manualClock = new ManualClock()), + }; + } + + [Test] + public void TestLargeJumps() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(100000); + + confirmSeek(100000); + checkFrameCount(6000); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(12000); + } + + [Test] + public void TestSmallJumps() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(40); + + confirmSeek(40); + checkFrameCount(3); + + seekManualTo(0); + + confirmSeek(0); + checkFrameCount(6); + } + + [Test] + public void TestSingleFrameJump() + { + seekManualTo(0); + createStabilityContainer(); + seekManualTo(8); + confirmSeek(8); + checkFrameCount(1); + + seekManualTo(16); + confirmSeek(16); + checkFrameCount(2); + } + + [Test] + public void TestInitialSeekWithGameplayStart() + { + seekManualTo(1000); + createStabilityContainer(30000); + + confirmSeek(1000); + checkFrameCount(0); + + seekManualTo(10000); + confirmSeek(10000); + + checkFrameCount(1); + + seekManualTo(130000); + confirmSeek(130000); + + checkFrameCount(6002); + } + + [Test] + public void TestInitialSeek() + { + seekManualTo(100000); + createStabilityContainer(); + + confirmSeek(100000); + checkFrameCount(0); + } + + private const int max_frames_catchup = 50; + + private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () => + mainContainer.Child = new FrameStabilityContainer(gameplayStartTime) { MaxCatchUpFrames = max_frames_catchup } + .WithChild(consumer = new ClockConsumingChild())); + + private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time); + + private void confirmSeek(double time) => AddUntilStep($"wait for seek to {time}", () => consumer.Clock.CurrentTime == time); + + private void checkFrameCount(int frames) => + AddAssert($"elapsed frames is {frames}", () => consumer.ElapsedFrames == frames); + + public class ClockConsumingChild : CompositeDrawable + { + private readonly OsuSpriteText text; + private readonly OsuSpriteText text2; + private readonly OsuSpriteText text3; + + public ClockConsumingChild() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text2 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + text3 = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + } + + public int ElapsedFrames; + + protected override void Update() + { + base.Update(); + + if (Clock.ElapsedFrameTime != 0) + ElapsedFrames++; + + text.Text = $"current time: {Clock.CurrentTime:F0}"; + if (Clock.ElapsedFrameTime != 0) + text2.Text = $"last elapsed frame time: {Clock.ElapsedFrameTime:F0}"; + text3.Text = $"total frames: {ElapsedFrames:F0}"; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs similarity index 81% rename from osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs index ee97ff7af7..4727140d99 100644 --- a/osu.Game.Tests/Visual/TestCaseGameplayMenuOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayMenuOverlay.cs @@ -1,42 +1,52 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using osuTK.Input; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; +using osu.Game.Input.Bindings; using osu.Game.Screens.Play; using osuTK; +using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("player pause/fail screens")] - public class TestCaseGameplayMenuOverlay : ManualInputManagerTestCase + public class TestSceneGameplayMenuOverlay : ManualInputManagerTestScene { - public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseContainer) }; + public override IReadOnlyList RequiredTypes => new[] { typeof(FailOverlay), typeof(PauseOverlay) }; private FailOverlay failOverlay; - private PauseContainer.PauseOverlay pauseOverlay; + private PauseOverlay pauseOverlay; + + private GlobalActionContainer globalActionContainer; [BackgroundDependencyLoader] - private void load() + private void load(OsuGameBase game) { - Add(pauseOverlay = new PauseContainer.PauseOverlay + Child = globalActionContainer = new GlobalActionContainer(game) { - OnResume = () => Logger.Log(@"Resume"), - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); + Children = new Drawable[] + { + pauseOverlay = new PauseOverlay + { + OnResume = () => Logger.Log(@"Resume"), + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + }, + failOverlay = new FailOverlay - Add(failOverlay = new FailOverlay - { - OnRetry = () => Logger.Log(@"Retry"), - OnQuit = () => Logger.Log(@"Quit"), - }); + { + OnRetry = () => Logger.Log(@"Retry"), + OnQuit = () => Logger.Log(@"Quit"), + } + } + }; var retryCount = 0; @@ -76,13 +86,7 @@ namespace osu.Game.Tests.Visual AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hide overlay", () => failOverlay.Hide()); - AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected)); - } - - private void press(Key key) - { - InputManager.PressKey(key); - InputManager.ReleaseKey(key); + AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); } /// @@ -92,8 +96,8 @@ namespace osu.Game.Tests.Visual { AddStep("Show overlay", () => pauseOverlay.Show()); - AddStep("Press enter", () => press(Key.Enter)); - AddAssert("Overlay still open", () => pauseOverlay.State == Visibility.Visible); + AddStep("Press select", () => press(GlobalAction.Select)); + AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible); AddStep("Hide overlay", () => pauseOverlay.Hide()); } @@ -106,7 +110,7 @@ namespace osu.Game.Tests.Visual AddStep("Show overlay", () => pauseOverlay.Show()); AddStep("Up arrow", () => press(Key.Up)); - AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value); AddStep("Hide overlay", () => pauseOverlay.Hide()); } @@ -119,7 +123,7 @@ namespace osu.Game.Tests.Visual AddStep("Show overlay", () => pauseOverlay.Show()); AddStep("Down arrow", () => press(Key.Down)); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); AddStep("Hide overlay", () => pauseOverlay.Hide()); } @@ -132,11 +136,11 @@ namespace osu.Game.Tests.Visual AddStep("Show overlay", () => failOverlay.Show()); AddStep("Up arrow", () => press(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddStep("Up arrow", () => press(Key.Up)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddStep("Up arrow", () => press(Key.Up)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddStep("Hide overlay", () => failOverlay.Hide()); } @@ -149,11 +153,11 @@ namespace osu.Game.Tests.Visual AddStep("Show overlay", () => failOverlay.Show()); AddStep("Down arrow", () => press(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddStep("Down arrow", () => press(Key.Down)); - AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected); + AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddStep("Down arrow", () => press(Key.Down)); - AddAssert("First button selected", () => failOverlay.Buttons.First().Selected); + AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddStep("Hide overlay", () => failOverlay.Hide()); } @@ -169,8 +173,8 @@ namespace osu.Game.Tests.Visual AddStep("Down arrow", () => press(Key.Down)); AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); - AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected); - AddAssert("Second button selected", () => secondButton.Selected); + AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value); + AddAssert("Second button selected", () => secondButton.Selected.Value); AddStep("Hide overlay", () => pauseOverlay.Hide()); } @@ -190,8 +194,8 @@ namespace osu.Game.Tests.Visual AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); AddStep("Up arrow", () => press(Key.Up)); - AddAssert("Second button not selected", () => !secondButton.Selected); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); + AddAssert("Second button not selected", () => !secondButton.Selected.Value); + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); AddStep("Hide overlay", () => pauseOverlay.Hide()); } @@ -208,7 +212,7 @@ namespace osu.Game.Tests.Visual AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton)); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Down arrow", () => press(Key.Down)); - AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected); // Initial state condition + AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition AddStep("Hide overlay", () => pauseOverlay.Hide()); } @@ -233,7 +237,7 @@ namespace osu.Game.Tests.Visual }); AddAssert("Action was triggered", () => triggered); - AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); + AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden); } /// @@ -265,9 +269,22 @@ namespace osu.Game.Tests.Visual pauseOverlay.OnRetry = lastAction; lastAction = null; } + return triggered; }); - AddAssert("Overlay is closed", () => pauseOverlay.State == Visibility.Hidden); + AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden); + } + + private void press(Key key) + { + InputManager.PressKey(key); + InputManager.ReleaseKey(key); + } + + private void press(GlobalAction action) + { + globalActionContainer.TriggerPressed(action); + globalActionContainer.TriggerReleased(action); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs new file mode 100644 index 0000000000..237fee1594 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -0,0 +1,109 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneGameplayRewinding : PlayerTestScene + { + private RulesetExposingPlayer player => (RulesetExposingPlayer)Player; + + [Resolved] + private AudioManager audioManager { get; set; } + + public TestSceneGameplayRewinding() + : base(new OsuRuleset()) + { + } + + private Track track; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + { + var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager); + track = working.Track; + return working; + } + + [Test] + public void TestNoJudgementsOnRewind() + { + AddUntilStep("wait for track to start running", () => track.IsRunning); + addSeekStep(3000); + AddAssert("all judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); + AddStep("clear results", () => player.AppliedResults.Clear()); + addSeekStep(0); + AddAssert("none judged", () => player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); + AddAssert("no results triggered", () => player.AppliedResults.Count == 0); + } + + private void addSeekStep(double time) + { + AddStep($"seek to {time}", () => track.Seek(time)); + + // Allow a few frames of lenience + AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, player.DrawableRuleset.FrameStableClock.CurrentTime, 100)); + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray(); + return new RulesetExposingPlayer(); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = { BaseDifficulty = { ApproachRate = 9 } }, + }; + + for (int i = 0; i < 15; i++) + { + beatmap.HitObjects.Add(new HitCircle + { + Position = new Vector2(256, 192), + StartTime = 1000 + 30 * i + }); + } + + return beatmap; + } + + private class RulesetExposingPlayer : Player + { + public readonly List AppliedResults = new List(); + + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new DrawableRuleset DrawableRuleset => base.DrawableRuleset; + + public RulesetExposingPlayer() + : base(false, false) + { + } + + [BackgroundDependencyLoader] + private void load() + { + ScoreProcessor.NewJudgement += r => AppliedResults.Add(r); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs similarity index 72% rename from osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs index 944921bc2e..d42b61ea55 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldForMenuButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHoldForMenuButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using NUnit.Framework; @@ -10,10 +10,10 @@ using osu.Game.Screens.Play.HUD; using osuTK; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [Description("'Hold to Quit' UI element")] - public class TestCaseHoldForMenuButton : ManualInputManagerTestCase + public class TestSceneHoldForMenuButton : ManualInputManagerTestScene { private bool exitAction; @@ -32,9 +32,9 @@ namespace osu.Game.Tests.Visual var text = holdForMenuButton.Children.OfType().First(); AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton)); - AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); + AddUntilStep("Text visible", () => text.IsPresent && !exitAction); AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One)); - AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible"); + AddUntilStep("Text is not visible", () => !text.IsPresent && !exitAction); AddStep("Trigger exit action", () => { @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual AddAssert("action not triggered", () => !exitAction); AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left)); - AddUntilStep(() => exitAction, $"{nameof(holdForMenuButton.Action)} was triggered"); + AddUntilStep($"{nameof(holdForMenuButton.Action)} was triggered", () => exitAction); } } } diff --git a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs similarity index 88% rename from osu.Game.Tests/Visual/TestCaseKeyCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs index 465b943651..18088a9a5b 100644 --- a/osu.Game.Tests/Visual/TestCaseKeyCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneKeyCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -11,22 +11,22 @@ using osu.Framework.Timing; using osu.Game.Screens.Play; using osuTK.Input; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseKeyCounter : ManualInputManagerTestCase + public class TestSceneKeyCounter : ManualInputManagerTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(KeyCounterKeyboard), typeof(KeyCounterMouse), - typeof(KeyCounterCollection) + typeof(KeyCounterDisplay) }; - public TestCaseKeyCounter() + public TestSceneKeyCounter() { KeyCounterKeyboard rewindTestKeyCounterKeyboard; - KeyCounterCollection kc = new KeyCounterCollection + KeyCounterDisplay kc = new KeyCounterDisplay { Origin = Anchor.Centre, Anchor = Anchor.Centre, @@ -39,7 +39,6 @@ namespace osu.Game.Tests.Visual }, }; - AddStep("Add random", () => { Key key = (Key)((int)Key.A + RNG.Next(26)); diff --git a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs similarity index 71% rename from osu.Game.Tests/Visual/TestCaseMedalOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs index c2df8d33ca..41722b430e 100644 --- a/osu.Game.Tests/Visual/TestCaseMedalOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneMedalOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,10 +8,10 @@ using osu.Game.Overlays; using osu.Game.Overlays.MedalSplash; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseMedalOverlay : OsuTestCase + public class TestSceneMedalOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual typeof(DrawableMedal), }; - public TestCaseMedalOverlay() + public TestSceneMedalOverlay() { AddStep(@"display", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs new file mode 100644 index 0000000000..5808a78056 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -0,0 +1,233 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePause : PlayerTestScene + { + protected new PausePlayer Player => (PausePlayer)base.Player; + + private readonly Container content; + + protected override Container Content => content; + + public TestScenePause() + : base(new OsuRuleset()) + { + base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); + } + + [SetUpSteps] + public override void SetUpSteps() + { + base.SetUpSteps(); + AddStep("resume player", () => Player.GameplayClockContainer.Start()); + confirmClockRunning(true); + } + + [Test] + public void TestPauseResume() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + pauseAndConfirm(); + resumeAndConfirm(); + } + + [Test] + public void TestResumeWithResumeOverlay() + { + AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + + pauseAndConfirm(); + resume(); + + confirmClockRunning(false); + confirmPauseOverlayShown(false); + + AddStep("click to resume", () => + { + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + }); + + confirmClockRunning(true); + } + + [Test] + public void TestResumeWithResumeOverlaySkipped() + { + AddStep("move cursor to button", () => + InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.Children.OfType().First().ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + + pauseAndConfirm(); + resumeAndConfirm(); + } + + [Test] + public void TestPauseTooSoon() + { + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + + resume(); + pause(); + + confirmClockRunning(true); + confirmPauseOverlayShown(false); + } + + [Test] + public void TestExitTooSoon() + { + pauseAndConfirm(); + + resume(); + + AddStep("exit too soon", () => Player.Exit()); + + confirmClockRunning(true); + confirmPauseOverlayShown(false); + + AddAssert("not exited", () => Player.IsCurrentScreen()); + } + + [Test] + public void TestPauseAfterFail() + { + AddUntilStep("wait for fail", () => Player.HasFailed); + AddUntilStep("fail overlay shown", () => Player.FailOverlayVisible); + + confirmClockRunning(false); + + pause(); + + confirmClockRunning(false); + confirmPauseOverlayShown(false); + + AddAssert("fail overlay still shown", () => Player.FailOverlayVisible); + + exitAndConfirm(); + } + + [Test] + public void TestExitFromGameplay() + { + AddStep("exit", () => Player.Exit()); + + confirmPaused(); + + exitAndConfirm(); + } + + [Test] + public void TestExitViaHoldToExit() + { + AddStep("exit", () => + { + InputManager.MoveMouseTo(Player.HUDOverlay.HoldToQuit.First(c => c is HoldToConfirmContainer)); + InputManager.PressButton(MouseButton.Left); + }); + + confirmPaused(); + + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + + exitAndConfirm(); + } + + [Test] + public void TestExitFromPause() + { + pauseAndConfirm(); + exitAndConfirm(); + } + + private void pauseAndConfirm() + { + pause(); + confirmPaused(); + } + + private void resumeAndConfirm() + { + resume(); + confirmResumed(); + } + + private void exitAndConfirm() + { + AddUntilStep("player not exited", () => Player.IsCurrentScreen()); + AddStep("exit", () => Player.Exit()); + confirmExited(); + } + + private void confirmPaused() + { + confirmClockRunning(false); + AddAssert("player not exited", () => Player.IsCurrentScreen()); + AddAssert("player not failed", () => !Player.HasFailed); + AddAssert("pause overlay shown", () => Player.PauseOverlayVisible); + } + + private void confirmResumed() + { + confirmClockRunning(true); + confirmPauseOverlayShown(false); + } + + private void confirmExited() + { + AddUntilStep("player exited", () => !Player.IsCurrentScreen()); + } + + private void pause() => AddStep("pause", () => Player.Pause()); + private void resume() => AddStep("resume", () => Player.Resume()); + + private void confirmPauseOverlayShown(bool isShown) => + AddAssert("pause overlay " + (isShown ? "shown" : "hidden"), () => Player.PauseOverlayVisible == isShown); + + private void confirmClockRunning(bool isRunning) => + AddUntilStep("clock " + (isRunning ? "running" : "stopped"), () => Player.GameplayClockContainer.GameplayClock.IsRunning == isRunning); + + protected override bool AllowFail => true; + + protected override Player CreatePlayer(Ruleset ruleset) => new PausePlayer(); + + protected class PausePlayer : TestPlayer + { + public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer; + + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + public new HUDOverlay HUDOverlay => base.HUDOverlay; + + public bool FailOverlayVisible => FailOverlay.State.Value == Visibility.Visible; + + public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible; + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + GameplayClockContainer.Stop(); + } + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs new file mode 100644 index 0000000000..daee3a520c --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -0,0 +1,131 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePlayerLoader : ManualInputManagerTestScene + { + private PlayerLoader loader; + private OsuScreenStack stack; + + [SetUp] + public void Setup() => Schedule(() => + { + InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }; + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + }); + + [Test] + public void TestLoadContinuation() + { + Player player = null; + SlowLoadPlayer slowPlayer = null; + + AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => player = new TestPlayer(false, false)))); + AddUntilStep("wait for current", () => loader.IsCurrentScreen()); + AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("load slow dummy beatmap", () => + { + stack.Push(loader = new PlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false))); + Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000); + }); + + AddUntilStep("wait for player to be current", () => slowPlayer.IsCurrentScreen()); + } + + [Test] + public void TestModReinstantiation() + { + TestPlayer player = null; + TestMod gameMod = null; + TestMod playerMod1 = null; + TestMod playerMod2 = null; + + AddStep("load player", () => + { + Mods.Value = new[] { gameMod = new TestMod() }; + stack.Push(loader = new PlayerLoader(() => player = new TestPlayer())); + }); + + AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); + AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod1 = (TestMod)player.Mods.Value.Single()); + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player mods applied", () => playerMod1.Applied); + + AddStep("restart player", () => + { + var lastPlayer = player; + player = null; + lastPlayer.Restart(); + }); + + AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); + AddStep("retrieve mods", () => playerMod2 = (TestMod)player.Mods.Value.Single()); + AddAssert("game mods not applied", () => gameMod.Applied == false); + AddAssert("player has different mods", () => playerMod1 != playerMod2); + AddAssert("player mods applied", () => playerMod2.Applied); + } + + private class TestMod : Mod, IApplicableToScoreProcessor + { + public override string Name => string.Empty; + public override string Acronym => string.Empty; + public override double ScoreMultiplier => 1; + + public bool Applied { get; private set; } + + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + Applied = true; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + } + + private class TestPlayer : Visual.TestPlayer + { + public new Bindable> Mods => base.Mods; + + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + } + + protected class SlowLoadPlayer : Visual.TestPlayer + { + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(false); + + public SlowLoadPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + + [BackgroundDependencyLoader] + private void load() + { + if (!AllowLoad.Wait(TimeSpan.FromSeconds(10))) + throw new TimeoutException(); + } + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs new file mode 100644 index 0000000000..65b56319e8 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerReferenceLeaking.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Lists; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePlayerReferenceLeaking : AllPlayersTestScene + { + private readonly WeakList workingWeakReferences = new WeakList(); + + private readonly WeakList playerWeakReferences = new WeakList(); + + protected override void AddCheckSteps() + { + AddUntilStep("no leaked beatmaps", () => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + foreach (var unused in workingWeakReferences) + count++; + + return count == 1; + }); + + AddUntilStep("no leaked players", () => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + int count = 0; + + foreach (var unused in playerWeakReferences) + count++; + + return count == 1; + }); + } + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) + { + var working = base.CreateWorkingBeatmap(beatmap); + workingWeakReferences.Add(working); + return working; + } + + protected override Player CreatePlayer(Ruleset ruleset) + { + var player = base.CreatePlayer(ruleset); + playerWeakReferences.Add(player); + return player; + } + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs new file mode 100644 index 0000000000..3fbce9d43c --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.ComponentModel; +using System.Linq; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [Description("Player instantiated with a replay.")] + public class TestSceneReplay : AllPlayersTestScene + { + protected override Player CreatePlayer(Ruleset ruleset) + { + var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); + + return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); + } + + protected override void AddCheckSteps() + { + AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); + AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0)); + } + + private class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + public new HUDOverlay HUDOverlay => base.HUDOverlay; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs similarity index 79% rename from osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs index a57cc5b79d..944480243d 100644 --- a/osu.Game.Tests/Visual/TestCaseReplaySettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplaySettingsOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Graphics; @@ -7,12 +7,12 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseReplaySettingsOverlay : OsuTestCase + public class TestSceneReplaySettingsOverlay : OsuTestScene { - public TestCaseReplaySettingsOverlay() + public TestSceneReplaySettingsOverlay() { ExampleContainer container; diff --git a/osu.Game.Tests/Visual/TestCaseResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs similarity index 71% rename from osu.Game.Tests/Visual/TestCaseResults.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index ee36fb0afc..f3c8f89db7 100644 --- a/osu.Game.Tests/Visual/TestCaseResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,23 +7,26 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Pages; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseResults : OsuTestCase + public class TestSceneResults : ScreenTestScene { private BeatmapManager beatmaps; public override IReadOnlyList RequiredTypes => new[] { - typeof(Score), + typeof(ScoreInfo), typeof(Results), typeof(ResultsPage), - typeof(ResultsPageScore), - typeof(ResultsPageRanking) + typeof(ScoreResultsPage), + typeof(LocalLeaderboardPage) }; [BackgroundDependencyLoader] @@ -40,14 +43,14 @@ namespace osu.Game.Tests.Visual if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); - Add(new Results(new Score + LoadScreen(new SoloResults(new ScoreInfo { TotalScore = 2845370, Accuracy = 0.98, MaxCombo = 123, Rank = ScoreRank.A, Date = DateTimeOffset.Now, - Statistics = new Dictionary + Statistics = new Dictionary { { HitResult.Great, 50 }, { HitResult.Good, 20 }, diff --git a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs similarity index 87% rename from osu.Game.Tests/Visual/TestCaseScoreCounter.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs index c32ca104ed..080a287b48 100644 --- a/osu.Game.Tests/Visual/TestCaseScoreCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScoreCounter.cs @@ -1,20 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseScoreCounter : OsuTestCase + public class TestSceneScoreCounter : OsuTestScene { - public TestCaseScoreCounter() + public TestSceneScoreCounter() { int numerator = 0, denominator = 0; @@ -53,7 +54,7 @@ namespace osu.Game.Tests.Visual }; Add(stars); - SpriteText starsLabel = new SpriteText + SpriteText starsLabel = new OsuSpriteText { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, @@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual AddStep(@"Hit! :D", delegate { - score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current > 0 ? comboCounter.Current - 1 : 0) / 25.0); + score.Current.Value += 300 + (ulong)(300.0 * (comboCounter.Current.Value > 0 ? comboCounter.Current.Value - 1 : 0) / 25.0); comboCounter.Increment(); numerator++; denominator++; diff --git a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs similarity index 92% rename from osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index 3ac85cd846..0a9cdc6a8e 100644 --- a/osu.Game.Tests/Visual/TestCaseScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -1,32 +1,37 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseScrollingHitObjects : OsuTestCase + public class TestSceneScrollingHitObjects : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(Playfield) }; + [Cached(typeof(IReadOnlyList))] + private IReadOnlyList mods { get; set; } = Array.Empty(); + private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4]; private readonly TestPlayfield[] playfields = new TestPlayfield[4]; - public TestCaseScrollingHitObjects() + public TestSceneScrollingHitObjects() { Add(new GridContainer { @@ -124,12 +129,15 @@ namespace osu.Game.Tests.Visual case ScrollingDirection.Up: obj.Anchor = Anchor.TopCentre; break; + case ScrollingDirection.Down: obj.Anchor = Anchor.BottomCentre; break; + case ScrollingDirection.Left: obj.Anchor = Anchor.CentreLeft; break; + case ScrollingDirection.Right: obj.Anchor = Anchor.CentreRight; break; @@ -170,12 +178,12 @@ namespace osu.Game.Tests.Visual { Origin = Anchor.Centre; - InternalChild = new Box + AddInternal(new Box { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both - }; + }); switch (direction) { @@ -184,6 +192,7 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.X; Height = 2; break; + case ScrollingDirection.Left: case ScrollingDirection.Right: RelativeSizeAxes = Axes.Y; @@ -205,7 +214,7 @@ namespace osu.Game.Tests.Visual Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; - InternalChild = new Box { Size = new Vector2(75) }; + AddInternal(new Box { Size = new Vector2(75) }); } protected override void UpdateState(ArmedState state) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs new file mode 100644 index 0000000000..c7a0df6e9f --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinReloadable.cs @@ -0,0 +1,145 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Skinning; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestSceneSkinReloadable : OsuTestScene + { + [Test] + public void TestInitialLoad() + { + var secondarySource = new SecondarySource(); + SkinConsumer consumer = null; + + AddStep("setup layout", () => + { + Child = new SkinSourceContainer + { + RelativeSizeAxes = Axes.Both, + Child = new LocalSkinOverrideContainer(secondarySource) + { + RelativeSizeAxes = Axes.Both, + Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true) + } + }; + }); + + AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); + AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); + } + + [Test] + public void TestOverride() + { + var secondarySource = new SecondarySource(); + + SkinConsumer consumer = null; + Container target = null; + + AddStep("setup layout", () => + { + Child = new SkinSourceContainer + { + RelativeSizeAxes = Axes.Both, + Child = target = new LocalSkinOverrideContainer(secondarySource) + { + RelativeSizeAxes = Axes.Both, + } + }; + }); + + AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true))); + AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox); + AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1); + } + + private class NamedBox : Container + { + public NamedBox(string name) + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Font = OsuFont.Default.With(size: 40), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = name + } + }; + } + } + + private class SkinConsumer : SkinnableDrawable + { + public new Drawable Drawable => base.Drawable; + public int SkinChangedCount { get; private set; } + + public SkinConsumer(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) + : base(name, defaultImplementation, allowFallback, restrictSize) + { + } + + protected override void SkinChanged(ISkinSource skin, bool allowFallback) + { + base.SkinChanged(skin, allowFallback); + SkinChangedCount++; + } + } + + private class BaseSourceBox : NamedBox + { + public BaseSourceBox() + : base("Base Source") + { + } + } + + private class SecondarySourceBox : NamedBox + { + public SecondarySourceBox() + : base("Secondary Source") + { + } + } + + private class SecondarySource : ISkin + { + public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox(); + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + } + + private class SkinSourceContainer : Container, ISkin + { + public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox(); + + public Texture GetTexture(string componentName) => throw new NotImplementedException(); + + public SampleChannel GetSample(string sampleName) => throw new NotImplementedException(); + + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => throw new NotImplementedException(); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs similarity index 50% rename from osu.Game.Tests/Visual/TestCaseSkipButton.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index 4f381fd7a8..0519660477 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Game.Screens.Play; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseSkipButton : OsuTestCase + public class TestSceneSkipOverlay : OsuTestScene { protected override void LoadComplete() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs new file mode 100644 index 0000000000..af21007efe --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Framework.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneSongProgress : OsuTestScene + { + private readonly SongProgress progress; + private readonly TestSongProgressGraph graph; + + private readonly StopwatchClock clock; + + [Cached] + private readonly GameplayClock gameplayClock; + + private readonly FramedClock framedClock; + + public TestSceneSongProgress() + { + clock = new StopwatchClock(true); + + gameplayClock = new GameplayClock(framedClock = new FramedClock(clock)); + + Add(progress = new SongProgress + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + + Add(graph = new TestSongProgressGraph + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + + AddWaitStep("wait some", 5); + AddAssert("ensure not created", () => graph.CreationCount == 0); + + AddStep("display values", displayNewValues); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); + + AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); + + AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); + AddWaitStep("wait some", 5); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); + AddRepeatStep("New Values", displayNewValues, 5); + + AddWaitStep("wait some", 5); + AddAssert("ensure debounced", () => graph.CreationCount == 2); + } + + private void displayNewValues() + { + List objects = new List(); + for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) + objects.Add(new HitObject { StartTime = i }); + + progress.Objects = objects; + graph.Objects = objects; + + progress.RequestSeek = pos => clock.Seek(pos); + } + + protected override void Update() + { + base.Update(); + framedClock.ProcessFrame(); + } + + private class TestSongProgressGraph : SongProgressGraph + { + public int CreationCount { get; private set; } + + protected override void RecreateGraph() + { + base.RecreateGraph(); + CreationCount++; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs similarity index 75% rename from osu.Game.Tests/Visual/TestCaseStoryboard.cs rename to osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 42c0134afa..ead7a4b7fc 100644 --- a/osu.Game.Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -12,15 +13,15 @@ using osu.Game.Overlays; using osu.Game.Storyboards.Drawables; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestCaseStoryboard : OsuTestCase + public class TestSceneStoryboard : OsuTestScene { private readonly Container storyboardContainer; private DrawableStoryboard storyboard; - public TestCaseStoryboard() + public TestSceneStoryboard() { Clock = new FramedClock(); @@ -45,11 +46,14 @@ namespace osu.Game.Tests.Visual { Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - State = Visibility.Visible, + State = { Value = Visibility.Visible }, }); AddStep("Restart", restart); - AddToggleStep("Passing", passing => { if (storyboard != null) storyboard.Passing = passing; }); + AddToggleStep("Passing", passing => + { + if (storyboard != null) storyboard.Passing = passing; + }); } [BackgroundDependencyLoader] @@ -58,15 +62,15 @@ namespace osu.Game.Tests.Visual Beatmap.ValueChanged += beatmapChanged; } - private void beatmapChanged(WorkingBeatmap working) - => loadStoryboard(working); + private void beatmapChanged(ValueChangedEvent e) + => loadStoryboard(e.NewValue); private void restart() { var track = Beatmap.Value.Track; track.Reset(); - loadStoryboard(Beatmap); + loadStoryboard(Beatmap.Value); track.Start(); } @@ -78,7 +82,7 @@ namespace osu.Game.Tests.Visual var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; storyboardContainer.Clock = decoupledClock; - storyboard = working.Storyboard.CreateDrawable(Beatmap); + storyboard = working.Storyboard.CreateDrawable(Beatmap.Value); storyboard.Passing = false; storyboardContainer.Add(storyboard); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs new file mode 100644 index 0000000000..f2718b8e80 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Screens.Menu; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Menus +{ + public class TestSceneDisclaimer : ScreenTestScene + { + [Cached(typeof(IAPIProvider))] + private readonly DummyAPIAccess api = new DummyAPIAccess(); + + [BackgroundDependencyLoader] + private void load() + { + Add(api); + + AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); + + AddStep("toggle support", () => + { + api.LocalUser.Value = new User + { + Username = api.LocalUser.Value.Username, + Id = api.LocalUser.Value.Id, + IsSupporter = !api.LocalUser.Value.IsSupporter, + }; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs similarity index 81% rename from osu.Game.Tests/Visual/TestCaseIntroSequence.cs rename to osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs index 9ce8a4ddd6..b59fb18428 100644 --- a/osu.Game.Tests/Visual/TestCaseIntroSequence.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroSequence.cs @@ -1,27 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using NUnit.Framework; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Timing; using osu.Game.Screens.Menu; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestCaseIntroSequence : OsuTestCase + public class TestSceneIntroSequence : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(OsuLogo), }; - public TestCaseIntroSequence() + public TestSceneIntroSequence() { OsuLogo logo; diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs new file mode 100644 index 0000000000..000832b784 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoaderAnimation.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Screens; +using osu.Game.Screens.Menu; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public class TestSceneLoaderAnimation : ScreenTestScene + { + private TestLoader loader; + + [Cached] + private OsuLogo logo; + + public TestSceneLoaderAnimation() + { + Child = logo = new OsuLogo + { + Alpha = 0, + Depth = float.MinValue + }; + } + + [Test] + public void TestInstantLoad() + { + bool logoVisible = false; + + AddStep("begin loading", () => + { + loader = new TestLoader(); + loader.AllowLoad.Set(); + + LoadScreen(loader); + }); + + AddUntilStep("loaded", () => + { + logoVisible = loader.Logo?.Alpha > 0; + return loader.Logo != null && loader.ScreenLoaded; + }); + + AddAssert("logo was not visible", () => !logoVisible); + } + + [Test] + public void TestDelayedLoad() + { + AddStep("begin loading", () => LoadScreen(loader = new TestLoader())); + AddUntilStep("wait for logo visible", () => loader.Logo?.Alpha > 0); + AddStep("finish loading", () => loader.AllowLoad.Set()); + AddAssert("loaded", () => loader.Logo != null && loader.ScreenLoaded); + AddUntilStep("logo gone", () => loader.Logo?.Alpha == 0); + } + + private class TestLoader : Loader + { + public readonly ManualResetEventSlim AllowLoad = new ManualResetEventSlim(); + + public OsuLogo Logo; + private TestScreen screen; + + public bool ScreenLoaded => screen.IsCurrentScreen(); + + 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(AllowLoad); + + private class TestShaderPrecompiler : ShaderPrecompiler + { + private readonly ManualResetEventSlim allowLoad; + + public TestShaderPrecompiler(ManualResetEventSlim allowLoad) + { + this.allowLoad = allowLoad; + } + + protected override bool AllLoaded => allowLoad.IsSet; + } + + private class TestScreen : OsuScreen + { + public TestScreen() + { + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.DarkSlateGray, + Alpha = 0, + }; + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + InternalChild.FadeInFromZero(200); + } + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs similarity index 57% rename from osu.Game.Tests/Visual/TestCaseToolbar.cs rename to osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 96f14e6b32..f24589ed35 100644 --- a/osu.Game.Tests/Visual/TestCaseToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,26 +8,29 @@ using NUnit.Framework; using osu.Framework.Graphics.Containers; using osu.Game.Overlays.Toolbar; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestCaseToolbar : OsuTestCase + public class TestSceneToolbar : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(ToolbarButton), typeof(ToolbarRulesetSelector), - typeof(ToolbarRulesetButton), + typeof(ToolbarRulesetTabButton), typeof(ToolbarNotificationButton), }; - public TestCaseToolbar() + public TestSceneToolbar() { - var toolbar = new Toolbar { State = Visibility.Visible }; + var toolbar = new Toolbar { State = { Value = Visibility.Visible } }; + ToolbarNotificationButton notificationButton = null; - Add(toolbar); - - var notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); + AddStep("create toolbar", () => + { + Add(toolbar); + notificationButton = toolbar.Children.OfType().Last().Children.OfType().First(); + }); void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs new file mode 100644 index 0000000000..eb4dc909df --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi; +using osu.Game.Screens.Multi.Lounge.Components; +using osu.Game.Users; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneLoungeRoomsContainer : MultiplayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RoomsContainer), + typeof(DrawableRoom) + }; + + [Cached(Type = typeof(IRoomManager))] + private TestRoomManager roomManager = new TestRoomManager(); + + [BackgroundDependencyLoader] + private void load() + { + RoomsContainer container; + + Child = container = new RoomsContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + JoinRequested = joinRequested + }; + + AddStep("clear rooms", () => roomManager.Rooms.Clear()); + + AddStep("add rooms", () => + { + for (int i = 0; i < 3; i++) + { + roomManager.Rooms.Add(new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) } + }); + } + }); + + AddAssert("has 2 rooms", () => container.Rooms.Count == 3); + AddStep("remove first room", () => roomManager.Rooms.Remove(roomManager.Rooms.FirstOrDefault())); + AddAssert("has 2 rooms", () => container.Rooms.Count == 2); + AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); + + AddStep("select first room", () => container.Rooms.First().Action?.Invoke()); + AddAssert("first room selected", () => Room == roomManager.Rooms.First()); + + AddStep("join first room", () => container.Rooms.First().Action?.Invoke()); + AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus); + } + + private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); + + private class TestRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) + { + } + + public void PartRoom() + { + } + } + + private class JoinedRoomStatus : RoomStatus + { + public override string Message => "Joined"; + + public override Color4 GetAppropriateColour(OsuColour colours) => colours.Yellow; + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs new file mode 100644 index 0000000000..e42042f2ea --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.GameTypes; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Multi.Match.Components; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMatchHeader : MultiplayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Header) + }; + + public TestSceneMatchHeader() + { + Room.Playlist.Add(new PlaylistItem + { + Beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Title = "Title", + Artist = "Artist", + AuthorString = "Author", + }, + Version = "Version", + Ruleset = new OsuRuleset().RulesetInfo + }, + RequiredMods = + { + new OsuModDoubleTime(), + new OsuModNoFail(), + new OsuModRelax(), + } + }); + + Room.Type.Value = new GameTypeTimeshift(); + + Child = new Header(); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHostInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHostInfo.cs new file mode 100644 index 0000000000..808a45cdf0 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHostInfo.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMatchHostInfo : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HostInfo) + }; + + private readonly Bindable host = new Bindable(new User { Username = "SomeHost" }); + + public TestSceneMatchHostInfo() + { + HostInfo hostInfo; + + Child = hostInfo = new HostInfo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + + hostInfo.Host.BindTo(host); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs new file mode 100644 index 0000000000..3f0c0b07b7 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchInfo.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.RoomStatuses; +using osu.Game.Rulesets; +using osu.Game.Screens.Multi.Match.Components; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + [TestFixture] + public class TestSceneMatchInfo : MultiplayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Info), + typeof(HeaderButton), + typeof(ReadyButton), + typeof(ViewBeatmapButton) + }; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Add(new Info()); + + AddStep(@"set name", () => Room.Name.Value = @"Room Name?"); + AddStep(@"set availability", () => Room.Availability.Value = RoomAvailability.FriendsOnly); + AddStep(@"set status", () => Room.Status.Value = new RoomStatusPlaying()); + AddStep(@"set beatmap", () => + { + Room.Playlist.Clear(); + Room.Playlist.Add(new PlaylistItem + { + Beatmap = new BeatmapInfo + { + StarDifficulty = 2.4, + Ruleset = rulesets.GetRuleset(0), + Metadata = new BeatmapMetadata + { + Title = @"My Song", + Artist = @"VisualTests", + AuthorString = @"osu!lazer", + }, + } + }); + }); + + AddStep(@"change name", () => Room.Name.Value = @"Room Name!"); + AddStep(@"change availability", () => Room.Availability.Value = RoomAvailability.InviteOnly); + AddStep(@"change status", () => Room.Status.Value = new RoomStatusOpen()); + AddStep(@"null beatmap", () => Room.Playlist.Clear()); + AddStep(@"change beatmap", () => + { + Room.Playlist.Clear(); + Room.Playlist.Add(new PlaylistItem + { + Beatmap = new BeatmapInfo + { + StarDifficulty = 4.2, + Ruleset = rulesets.GetRuleset(3), + Metadata = new BeatmapMetadata + { + Title = @"Your Song", + Artist = @"Tester", + AuthorString = @"Someone", + }, + } + }); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs new file mode 100644 index 0000000000..fa3c392b2e --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Online.API; +using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMatchLeaderboard : MultiplayerTestScene + { + public TestSceneMatchLeaderboard() + { + Room.RoomID.Value = 3; + + Add(new MatchLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, + }); + } + + [Resolved] + private IAPIProvider api { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + var req = new GetRoomScoresRequest(); + req.Success += v => { }; + req.Failure += _ => { }; + + api.Queue(req); + } + + private class GetRoomScoresRequest : APIRequest> + { + protected override string Target => "rooms/3/leaderboard"; + } + + private class RoomScore + { + [JsonProperty("user")] + public User User { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty("total_score")] + public int TotalScore { get; set; } + + [JsonProperty("pp")] + public double PP { get; set; } + + [JsonProperty("attempts")] + public int TotalAttempts { get; set; } + + [JsonProperty("completed")] + public int CompletedAttempts { get; set; } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs similarity index 59% rename from osu.Game.Tests/Visual/TestCaseMatchParticipants.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs index d6ae07252b..50df4022dc 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchParticipants.cs @@ -1,26 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Screens.Multi.Screens.Match; +using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Multiplayer { [TestFixture] - public class TestCaseMatchParticipants : OsuTestCase + public class TestSceneMatchParticipants : MultiplayerTestScene { - public TestCaseMatchParticipants() + public TestSceneMatchParticipants() { - Participants participants; - Add(participants = new Participants - { - RelativeSizeAxes = Axes.Both, - }); + Add(new Participants { RelativeSizeAxes = Axes.Both }); - AddStep(@"set max to null", () => participants.Max = null); - AddStep(@"set users", () => participants.Users = new[] + AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); + AddStep(@"set users", () => Room.Participants.Value = new[] { new User { @@ -48,9 +44,9 @@ namespace osu.Game.Tests.Visual }, }); - AddStep(@"set max", () => participants.Max = 10); - AddStep(@"clear users", () => participants.Users = new User[] { }); - AddStep(@"set max to null", () => participants.Max = null); + AddStep(@"set max", () => Room.MaxParticipants.Value = 10); + AddStep(@"clear users", () => Room.Participants.Value = new User[] { }); + AddStep(@"set max to null", () => Room.MaxParticipants.Value = null); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs new file mode 100644 index 0000000000..7915a981dd --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchResults.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; +using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Screens.Multi.Ranking; +using osu.Game.Screens.Multi.Ranking.Pages; +using osu.Game.Screens.Multi.Ranking.Types; +using osu.Game.Screens.Ranking; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMatchResults : MultiplayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(MatchResults), + typeof(RoomLeaderboardPageInfo), + typeof(RoomLeaderboardPage) + }; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); + if (beatmapInfo != null) + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); + + Room.RoomID.Value = 1; + Room.Name.Value = "an awesome room"; + + LoadScreen(new TestMatchResults(new ScoreInfo + { + User = new User { Id = 10 }, + })); + } + + private class TestMatchResults : MatchResults + { + public TestMatchResults(ScoreInfo score) + : base(score) + { + } + + protected override IEnumerable CreateResultPages() => new[] { new TestRoomLeaderboardPageInfo(Score, Beatmap.Value) }; + } + + private class TestRoomLeaderboardPageInfo : RoomLeaderboardPageInfo + { + private readonly ScoreInfo score; + private readonly WorkingBeatmap beatmap; + + public TestRoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap) + : base(score, beatmap) + { + this.score = score; + this.beatmap = beatmap; + } + + public override ResultsPage CreatePage() => new TestRoomLeaderboardPage(score, beatmap); + } + + private class TestRoomLeaderboardPage : RoomLeaderboardPage + { + public TestRoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap) + : base(score, beatmap) + { + } + + protected override MatchLeaderboard CreateLeaderboard() => new TestMatchLeaderboard(); + } + + private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard + { + protected override APIRequest FetchScores(Action> scoresCallback) + { + var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray(); + + scoresCallback?.Invoke(scores); + ScoresLoaded?.Invoke(scores); + + return null; + } + + private APIRoomScoreInfo createRoomScore(int id) => new APIRoomScoreInfo + { + User = new User { Id = id, Username = $"User {id}" }, + Accuracy = 0.98, + TotalScore = 987654, + TotalAttempts = 13, + CompletedBeatmaps = 5 + }; + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs new file mode 100644 index 0000000000..8d842fc865 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs @@ -0,0 +1,156 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi; +using osu.Game.Screens.Multi.Match.Components; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneMatchSettingsOverlay : MultiplayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(MatchSettingsOverlay) + }; + + [Cached(Type = typeof(IRoomManager))] + private TestRoomManager roomManager = new TestRoomManager(); + + private TestRoomSettings settings; + + [SetUp] + public void Setup() => Schedule(() => + { + Room = new Room(); + + settings = new TestRoomSettings + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + }; + + Child = settings; + }); + + [Test] + public void TestButtonEnabledOnlyWithNameAndBeatmap() + { + AddStep("clear name and beatmap", () => + { + Room.Name.Value = ""; + Room.Playlist.Clear(); + }); + + AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); + + AddStep("set name", () => Room.Name.Value = "Room name"); + AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); + + AddStep("set beatmap", () => Room.Playlist.Add(new PlaylistItem { Beatmap = CreateBeatmap(Ruleset.Value).BeatmapInfo })); + AddAssert("button enabled", () => settings.ApplyButton.Enabled.Value); + + AddStep("clear name", () => Room.Name.Value = ""); + AddAssert("button disabled", () => !settings.ApplyButton.Enabled.Value); + } + + [Test] + public void TestCorrectSettingsApplied() + { + const string expected_name = "expected name"; + TimeSpan expectedDuration = TimeSpan.FromMinutes(15); + + Room createdRoom = null; + + AddStep("setup", () => + { + settings.NameField.Current.Value = expected_name; + settings.DurationField.Current.Value = expectedDuration; + + roomManager.CreateRequested = r => + { + createdRoom = r; + return true; + }; + }); + + AddStep("create room", () => settings.ApplyButton.Action.Invoke()); + AddAssert("has correct name", () => createdRoom.Name.Value == expected_name); + AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration); + } + + [Test] + public void TestCreationFailureDisplaysError() + { + bool fail; + + AddStep("setup", () => + { + fail = true; + roomManager.CreateRequested = _ => !fail; + }); + AddAssert("error not displayed", () => !settings.ErrorText.IsPresent); + + AddStep("create room", () => settings.ApplyButton.Action.Invoke()); + AddAssert("error displayed", () => settings.ErrorText.IsPresent); + AddAssert("error has correct text", () => settings.ErrorText.Text == TestRoomManager.FAILED_TEXT); + + AddStep("create room no fail", () => + { + fail = false; + settings.ApplyButton.Action.Invoke(); + }); + + AddUntilStep("error not displayed", () => !settings.ErrorText.IsPresent); + } + + private class TestRoomSettings : MatchSettingsOverlay + { + public TriangleButton ApplyButton => Settings.ApplyButton; + + public OsuTextBox NameField => Settings.NameField; + public OsuDropdown DurationField => Settings.DurationField; + + public OsuSpriteText ErrorText => Settings.ErrorText; + } + + private class TestRoomManager : IRoomManager + { + public const string FAILED_TEXT = "failed"; + + public Func CreateRequested; + + public event Action RoomsUpdated + { + add { } + remove { } + } + + public IBindableList Rooms { get; } = null; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + if (CreateRequested == null) + return; + + if (!CreateRequested.Invoke(room)) + onError?.Invoke(FAILED_TEXT); + else + onSuccess?.Invoke(room); + } + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) => throw new NotImplementedException(); + + public void PartRoom() => throw new NotImplementedException(); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiHeader.cs new file mode 100644 index 0000000000..3f89f636b1 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiHeader.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Screens; +using osu.Game.Screens.Multi; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + [TestFixture] + public class TestSceneMultiHeader : OsuTestScene + { + public TestSceneMultiHeader() + { + int index = 0; + + OsuScreenStack screenStack = new OsuScreenStack(new TestMultiplayerSubScreen(index)) { RelativeSizeAxes = Axes.Both }; + + Children = new Drawable[] + { + screenStack, + new Header(screenStack) + }; + + AddStep("push multi screen", () => screenStack.CurrentScreen.Push(new TestMultiplayerSubScreen(++index))); + } + + private class TestMultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen + { + private readonly int index; + + public string ShortTitle => $"Screen {index}"; + + public TestMultiplayerSubScreen(int index) + { + this.index = index; + } + + public override string ToString() => ShortTitle; + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs new file mode 100644 index 0000000000..069e133c2b --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiScreen.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Screens.Multi.Lounge; +using osu.Game.Screens.Multi.Lounge.Components; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + [TestFixture] + public class TestSceneMultiScreen : ScreenTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Screens.Multi.Multiplayer), + typeof(LoungeSubScreen), + typeof(FilterControl) + }; + + public TestSceneMultiScreen() + { + Screens.Multi.Multiplayer multi = new Screens.Multi.Multiplayer(); + + AddStep(@"show", () => LoadScreen(multi)); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs new file mode 100644 index 0000000000..74d1645f6d --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneRoomStatus.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.RoomStatuses; +using osu.Game.Screens.Multi.Lounge.Components; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneRoomStatus : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(RoomStatusEnded), + typeof(RoomStatusOpen), + typeof(RoomStatusPlaying) + }; + + public TestSceneRoomStatus() + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Children = new Drawable[] + { + new DrawableRoom(new Room + { + Name = { Value = "Room 1" }, + Status = { Value = new RoomStatusOpen() } + }), + new DrawableRoom(new Room + { + Name = { Value = "Room 2" }, + Status = { Value = new RoomStatusPlaying() } + }), + new DrawableRoom(new Room + { + Name = { Value = "Room 3" }, + Status = { Value = new RoomStatusEnded() } + }), + } + }; + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs new file mode 100644 index 0000000000..35449f5687 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneAccountCreationOverlay.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Overlays; +using osu.Game.Overlays.AccountCreation; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneAccountCreationOverlay : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ErrorTextFlowContainer), + typeof(AccountCreationBackground), + typeof(ScreenEntry), + typeof(ScreenWarning), + typeof(ScreenWelcome), + typeof(AccountCreationScreen), + }; + + [Cached(typeof(IAPIProvider))] + private DummyAPIAccess api = new DummyAPIAccess(); + + public TestSceneAccountCreationOverlay() + { + Container userPanelArea; + AccountCreationOverlay accountCreation; + + Children = new Drawable[] + { + api, + accountCreation = new AccountCreationOverlay(), + userPanelArea = new Container + { + Padding = new MarginPadding(10), + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }; + + api.Logout(); + api.LocalUser.BindValueChanged(user => { userPanelArea.Child = new UserPanel(user.NewValue) { Width = 200 }; }, true); + + AddStep("show", () => accountCreation.Show()); + AddStep("logout", () => api.Logout()); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs new file mode 100644 index 0000000000..fe94165777 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Overlays.BeatmapSet; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneBeatmapAvailability : OsuTestScene + { + private readonly BeatmapAvailability container; + + public TestSceneBeatmapAvailability() + { + Add(container = new BeatmapAvailability()); + } + + [Test] + public void TestUndownloadableWithLink() + { + AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + ExternalLink = @"https://osu.ppy.sh", + }, + }, + }); + + visiblityAssert(true); + } + + [Test] + public void TestUndownloadableNoLink() + { + AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + }, + }, + }); + + visiblityAssert(true); + } + + [Test] + public void TestPartsRemovedWithLink() + { + AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = false, + ExternalLink = @"https://osu.ppy.sh", + }, + }, + }); + + visiblityAssert(true); + } + + [Test] + public void TestNormal() + { + AddStep("set normal beatmapset", () => container.BeatmapSet = new BeatmapSetInfo + { + OnlineInfo = new BeatmapSetOnlineInfo + { + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = false, + }, + }, + }); + + visiblityAssert(false); + } + + private void visiblityAssert(bool shown) + { + AddAssert($"is container {(shown ? "visible" : "hidden")}", () => container.Alpha == (shown ? 1 : 0)); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs new file mode 100644 index 0000000000..c494f5ef33 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -0,0 +1,235 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Overlays.BeatmapSet.Buttons; +using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Rulesets; +using osu.Game.Users; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneBeatmapSetOverlay : OsuTestScene + { + private readonly TestBeatmapSetOverlay overlay; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Header), + typeof(ScoreTable), + typeof(ScoreTableRowBackground), + typeof(DrawableTopScore), + typeof(ScoresContainer), + typeof(AuthorInfo), + typeof(BasicStats), + typeof(BeatmapPicker), + typeof(Details), + typeof(DownloadButton), + typeof(FavouriteButton), + typeof(Header), + typeof(HeaderButton), + typeof(Info), + typeof(PreviewButton), + typeof(SuccessRate), + typeof(BeatmapAvailability), + }; + + private RulesetInfo taikoRuleset; + private RulesetInfo maniaRuleset; + + public TestSceneBeatmapSetOverlay() + { + Add(overlay = new TestBeatmapSetOverlay()); + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + taikoRuleset = rulesets.GetRuleset(1); + maniaRuleset = rulesets.GetRuleset(3); + } + + [Test] + public void TestLoading() + { + AddStep(@"show loading", () => overlay.ShowBeatmapSet(null)); + } + + [Test] + public void TestOnline() + { + AddStep(@"show online", () => overlay.FetchAndShowBeatmapSet(55)); + } + + [Test] + public void TestLocalBeatmaps() + { + AddStep(@"show first", () => + { + overlay.ShowBeatmapSet(new BeatmapSetInfo + { + OnlineBeatmapSetID = 1235, + Metadata = new BeatmapMetadata + { + Title = @"an awesome beatmap", + Artist = @"naru narusegawa", + Source = @"hinata sou", + Tags = @"test tag tag more tag", + Author = new User + { + Username = @"BanchoBot", + Id = 3, + }, + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Preview = @"https://b.ppy.sh/preview/12345.mp3", + PlayCount = 123, + FavouriteCount = 456, + Submitted = DateTime.Now, + Ranked = DateTime.Now, + BPM = 111, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + }, + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, + Beatmaps = new List + { + new BeatmapInfo + { + StarDifficulty = 9.99, + Version = @"TEST", + Ruleset = maniaRuleset, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 1, + DrainRate = 2.3f, + OverallDifficulty = 4.5f, + ApproachRate = 6, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 456000, + CircleCount = 111, + SliderCount = 12, + PlayCount = 222, + PassCount = 21, + }, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + }, + }, + }); + }); + + downloadAssert(true); + } + + [Test] + public void TestAvailability() + { + AddStep(@"show undownloadable", () => + { + overlay.ShowBeatmapSet(new BeatmapSetInfo + { + OnlineBeatmapSetID = 1234, + Metadata = new BeatmapMetadata + { + Title = @"undownloadable beatmap", + Artist = @"no one", + Source = @"some source", + Tags = @"another test tag tag more test tags", + Author = new User + { + Username = @"BanchoBot", + Id = 3, + }, + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + ExternalLink = "https://osu.ppy.sh", + }, + Preview = @"https://b.ppy.sh/preview/1234.mp3", + PlayCount = 123, + FavouriteCount = 456, + Submitted = DateTime.Now, + Ranked = DateTime.Now, + BPM = 111, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + }, + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, + Beatmaps = new List + { + new BeatmapInfo + { + StarDifficulty = 5.67, + Version = @"ANOTHER TEST", + Ruleset = taikoRuleset, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 9, + DrainRate = 8, + OverallDifficulty = 7, + ApproachRate = 6, + }, + OnlineInfo = new BeatmapOnlineInfo + { + Length = 123000, + CircleCount = 123, + SliderCount = 45, + PlayCount = 567, + PassCount = 89, + }, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + }, + }, + }); + }); + + downloadAssert(false); + } + + [Test] + public void TestHide() + { + AddStep(@"hide", overlay.Hide); + } + + [Test] + public void TestShowWithNoReload() + { + AddStep(@"show without reload", overlay.Show); + } + + private void downloadAssert(bool shown) + { + AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.DownloadButtonsVisible == shown); + } + + private class TestBeatmapSetOverlay : BeatmapSetOverlay + { + public bool DownloadButtonsVisible => Header.DownloadButtonsVisible; + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs new file mode 100644 index 0000000000..2a45e68c0a --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Screens.Select.Details; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneBeatmapSetOverlayDetails : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Details) + }; + + private RatingsExposingDetails details; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = details = new RatingsExposingDetails + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + }); + + [Test] + public void TestMetrics() + { + var firstSet = createSet(); + var secondSet = createSet(); + + AddStep("set first set", () => details.BeatmapSet = firstSet); + AddAssert("ratings set", () => details.Ratings.Metrics == firstSet.Metrics); + + AddStep("set second set", () => details.BeatmapSet = secondSet); + AddAssert("ratings set", () => details.Ratings.Metrics == secondSet.Metrics); + + BeatmapSetInfo createSet() => new BeatmapSetInfo + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray() }, + Beatmaps = new List + { + new BeatmapInfo + { + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), + } + } + } + }; + } + + private class RatingsExposingDetails : Details + { + public new UserRatings Ratings => base.Ratings; + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs new file mode 100644 index 0000000000..05f5c117e4 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Screens.Select.Details; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneBeatmapSetOverlaySuccessRate : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(Details) + }; + + private GraphExposingSuccessRate successRate; + + [SetUp] + public void Setup() => Schedule(() => + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(275, 220), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Gray, + }, + successRate = new GraphExposingSuccessRate + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(275, 220), + Padding = new MarginPadding(20) + } + } + }; + }); + + [Test] + public void TestMetrics() + { + var firstBeatmap = createBeatmap(); + var secondBeatmap = createBeatmap(); + + AddStep("set first set", () => successRate.Beatmap = firstBeatmap); + AddAssert("ratings set", () => successRate.Graph.Metrics == firstBeatmap.Metrics); + + AddStep("set second set", () => successRate.Beatmap = secondBeatmap); + AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics); + + BeatmapInfo createBeatmap() => new BeatmapInfo + { + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), + } + }; + } + + private class GraphExposingSuccessRate : SuccessRate + { + public new FailRetryGraph Graph => base.Graph; + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs new file mode 100644 index 0000000000..0655611230 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Changelog; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneChangelogOverlay : OsuTestScene + { + private ChangelogOverlay changelog; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(UpdateStreamBadgeArea), + typeof(UpdateStreamBadge), + typeof(ChangelogHeader), + typeof(ChangelogContent), + typeof(ChangelogListing), + typeof(ChangelogSingleBuild), + typeof(ChangelogBuild), + }; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(changelog = new ChangelogOverlay()); + AddStep(@"Show", changelog.Show); + AddStep(@"Hide", changelog.Hide); + + AddWaitStep("wait for hide", 3); + + AddStep(@"Show with Lazer 2018.712.0", () => + { + changelog.ShowBuild(new APIChangelogBuild + { + Version = "2018.712.0", + DisplayVersion = "2018.712.0", + UpdateStream = new APIUpdateStream { Name = OsuGameBase.CLIENT_STREAM_NAME }, + ChangelogEntries = new List + { + new APIChangelogEntry + { + Category = "Test", + Title = "Title", + MessageHtml = "Message", + } + } + }); + changelog.Show(); + }); + + AddWaitStep("wait for show", 3); + AddStep(@"Hide", changelog.Hide); + AddWaitStep("wait for hide", 3); + + AddStep(@"Show with listing", () => + { + changelog.ShowListing(); + changelog.Show(); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs similarity index 57% rename from osu.Game.Tests/Visual/TestCaseChannelTabControl.cs rename to osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs index 447337bef0..364c986723 100644 --- a/osu.Game.Tests/Visual/TestCaseChannelTabControl.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChannelTabControl.cs @@ -1,26 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.MathUtils; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; +using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { - public class TestCaseChannelTabControl : OsuTestCase + public class TestSceneChannelTabControl : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -29,7 +27,7 @@ namespace osu.Game.Tests.Visual private readonly ChannelTabControl channelTabControl; - public TestCaseChannelTabControl() + public TestSceneChannelTabControl() { SpriteText currentText; Add(new Container @@ -64,7 +62,7 @@ namespace osu.Game.Tests.Visual Anchor = Anchor.TopLeft, Children = new Drawable[] { - currentText = new SpriteText + currentText = new OsuSpriteText { Text = "Currently selected channel:" } @@ -72,52 +70,52 @@ namespace osu.Game.Tests.Visual }); channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel); - channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.ToString(); + channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.NewValue.ToString(); - AddStep("Add random private channel", addRandomUser); + AddStep("Add random private channel", addRandomPrivateChannel); AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2); - AddRepeatStep("Add 3 random private channels", addRandomUser, 3); + AddRepeatStep("Add 3 random private channels", addRandomPrivateChannel, 3); AddAssert("There are four channels", () => channelTabControl.Items.Count() == 5); AddStep("Add random public channel", () => addChannel(RNG.Next().ToString())); - AddRepeatStep("Select a random channel", () => channelTabControl.Current.Value = channelTabControl.Items.ElementAt(RNG.Next(channelTabControl.Items.Count())), 20); - } + AddRepeatStep("Select a random channel", () => channelTabControl.Current.Value = channelTabControl.Items.ElementAt(RNG.Next(channelTabControl.Items.Count() - 1)), 20); - private List users; + Channel channelBefore = channelTabControl.Items.First(); + AddStep("set first channel", () => channelTabControl.Current.Value = channelBefore); - private void addRandomUser() - { - channelTabControl.AddChannel(new Channel + AddStep("select selector tab", () => channelTabControl.Current.Value = channelTabControl.Items.Last()); + AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value); + + AddAssert("check channel unchanged", () => channelBefore == channelTabControl.Current.Value); + + AddStep("set second channel", () => channelTabControl.Current.Value = channelTabControl.Items.Skip(1).First()); + AddAssert("selector tab is inactive", () => !channelTabControl.ChannelSelectorActive.Value); + + AddUntilStep("remove all channels", () => { - Users = - { - users?.Count > 0 - ? users[RNG.Next(0, users.Count - 1)] - : new User - { - Id = RNG.Next(), - Username = "testuser" + RNG.Next(1000) - } - } + var first = channelTabControl.Items.First(); + if (first is ChannelSelectorTabItem.ChannelSelectorTabChannel) + return true; + + channelTabControl.RemoveChannel(first); + return false; }); + + AddAssert("selector tab is active", () => channelTabControl.ChannelSelectorActive.Value); } - private void addChannel(string name) - { + private void addRandomPrivateChannel() => + channelTabControl.AddChannel(new Channel(new User + { + Id = RNG.Next(1000, 10000000), + Username = "Test User " + RNG.Next(1000) + })); + + private void addChannel(string name) => channelTabControl.AddChannel(new Channel { Type = ChannelType.Public, Name = name }); - } - - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { - GetUsersRequest req = new GetUsersRequest(); - req.Success += list => users = list.Select(e => e.User).ToList(); - - api.Queue(req); - } } } diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs similarity index 93% rename from osu.Game.Tests/Visual/TestCaseChatLink.cs rename to osu.Game.Tests/Visual/Online/TestSceneChatLink.cs index eddcd16b93..c18e0e3064 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatLink.cs @@ -1,26 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Online.Chat; -using osu.Game.Overlays.Chat; -using osu.Game.Users; using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; using osu.Game.Overlays; +using osu.Game.Overlays.Chat; +using osu.Game.Users; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseChatLink : OsuTestCase + public class TestSceneChatLink : OsuTestScene { private readonly TestChatLineContainer textContainer; private readonly DialogOverlay dialogOverlay; @@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual typeof(MessageFormatter) }; - public TestCaseChatLink() + public TestSceneChatLink() { Add(dialogOverlay = new DialogOverlay { Depth = float.MinValue }); Add(textContainer = new TestChatLineContainer @@ -54,8 +55,9 @@ namespace osu.Game.Tests.Visual linkColour = colours.Blue; var chatManager = new ChannelManager(); - chatManager.AvailableChannels.Add(new Channel { Name = "#english"}); - chatManager.AvailableChannels.Add(new Channel { Name = "#japanese" }); + BindableList availableChannels = (BindableList)chatManager.AvailableChannels; + availableChannels.Add(new Channel { Name = "#english" }); + availableChannels.Add(new Channel { Name = "#japanese" }); Dependencies.Cache(chatManager); Dependencies.Cache(new ChatOverlay()); @@ -94,7 +96,7 @@ namespace osu.Game.Tests.Visual return true; } - bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font == "Exo2.0-MediumItalic"); + bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font.Italics); bool isShowingLinks() { @@ -157,7 +159,7 @@ namespace osu.Game.Tests.Visual Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); }); - AddUntilStep(() => textContainer.All(line => line.Message is DummyMessage), $"wait for msg #{echoCounter}"); + AddUntilStep($"wait for msg #{echoCounter}", () => textContainer.All(line => line.Message is DummyMessage)); } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs new file mode 100644 index 0000000000..4d3992ce13 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat; +using osu.Game.Overlays.Chat.Selection; +using osu.Game.Overlays.Chat.Tabs; +using osu.Game.Users; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneChatOverlay : ManualInputManagerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ChatLine), + typeof(DrawableChannel), + typeof(ChannelSelectorTabItem), + typeof(ChannelTabControl), + typeof(ChannelTabItem), + typeof(PrivateChannelTabItem), + typeof(TabCloseButton) + }; + + private TestChatOverlay chatOverlay; + private ChannelManager channelManager; + + private readonly Channel channel1 = new Channel(new User()) { Name = "test1" }; + private readonly Channel channel2 = new Channel(new User()) { Name = "test2" }; + + [SetUp] + public void Setup() + { + Schedule(() => + { + ChannelManagerContainer container; + + Child = container = new ChannelManagerContainer(new List { channel1, channel2 }) + { + RelativeSizeAxes = Axes.Both, + }; + + chatOverlay = container.ChatOverlay; + channelManager = container.ChannelManager; + }); + } + + [Test] + public void TestHideOverlay() + { + AddAssert("Chat overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); + AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + + AddStep("Close chat overlay", () => chatOverlay.Hide()); + + AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden); + AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + } + + [Test] + public void TestSelectingChannelClosesSelector() + { + AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1); + AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + } + + [Test] + public void TestCloseChannelWhileSelectorClosed() + { + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + + AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.TabMap[channel2])); + AddStep("Close channel 2", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel2]).CloseButton.Child)); + + AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1); + + AddStep("Close channel 1", () => clickDrawable(((TestChannelTabItem)chatOverlay.TabMap[channel1]).CloseButton.Child)); + + AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + } + + private void clickDrawable(Drawable d) + { + InputManager.MoveMouseTo(d); + InputManager.Click(MouseButton.Left); + } + + private class ChannelManagerContainer : Container + { + public TestChatOverlay ChatOverlay { get; private set; } + + [Cached] + public ChannelManager ChannelManager { get; } = new ChannelManager(); + + private readonly List channels; + + public ChannelManagerContainer(List channels) + { + this.channels = channels; + } + + [BackgroundDependencyLoader] + private void load() + { + ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); + + Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }; + ChatOverlay.Show(); + } + } + + private class TestChatOverlay : ChatOverlay + { + public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value; + + public new ChannelSelectionOverlay ChannelSelectionOverlay => base.ChannelSelectionOverlay; + + protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl(); + + public IReadOnlyDictionary> TabMap => ((TestTabControl)ChannelTabControl).TabMap; + } + + private class TestTabControl : ChannelTabControl + { + protected override TabItem CreateTabItem(Channel value) => new TestChannelTabItem(value); + + public new IReadOnlyDictionary> TabMap => base.TabMap; + } + + private class TestChannelTabItem : PrivateChannelTabItem + { + public TestChannelTabItem(Channel channel) + : base(channel) + { + } + + public new ClickableContainer CloseButton => base.CloseButton; + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs new file mode 100644 index 0000000000..5a5833feb6 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -0,0 +1,158 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneDirectDownloadButton : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DownloadButton) + }; + + private TestDownloadButton downloadButton; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [Test] + public void TestDownloadableBeatmap() + { + createButton(true); + assertEnabled(true); + } + + [Test] + public void TestUndownloadableBeatmap() + { + createButton(false); + assertEnabled(false); + } + + [Test] + public void TestDownloadState() + { + AddUntilStep("ensure manager loaded", () => beatmaps != null); + ensureSoleilyRemoved(); + createButtonWithBeatmap(createSoleily()); + AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); + AddStep("import soleily", () => beatmaps.Import(new[] { TestResources.GetTestBeatmapForImport() })); + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + createButtonWithBeatmap(createSoleily()); + AddAssert("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); + ensureSoleilyRemoved(); + AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); + } + + private void ensureSoleilyRemoved() + { + AddStep("remove soleily", () => + { + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); + + if (beatmap != null) beatmaps.Delete(beatmap); + }); + } + + private void assertEnabled(bool enabled) + { + AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => downloadButton.DownloadEnabled == enabled); + } + + private BeatmapSetInfo createSoleily() + { + return new BeatmapSetInfo + { + ID = 1, + OnlineBeatmapSetID = 241526, + OnlineInfo = new BeatmapSetOnlineInfo + { + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = false, + ExternalLink = string.Empty, + }, + }, + }; + } + + private void createButtonWithBeatmap(BeatmapSetInfo beatmap) + { + AddStep("create button", () => + { + Child = downloadButton = new TestDownloadButton(beatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(75, 50), + }; + }); + } + + private void createButton(bool downloadable) + { + AddStep("create button", () => + { + Child = downloadButton = new TestDownloadButton(downloadable ? getDownloadableBeatmapSet() : getUndownloadableBeatmapSet()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(75, 50), + }; + }); + } + + private BeatmapSetInfo getDownloadableBeatmapSet() + { + var normal = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo; + normal.OnlineInfo.HasVideo = true; + normal.OnlineInfo.HasStoryboard = true; + + return normal; + } + + private BeatmapSetInfo getUndownloadableBeatmapSet() + { + var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo; + beatmap.Metadata.Artist = "test"; + beatmap.Metadata.Title = "undownloadable"; + beatmap.Metadata.AuthorString = "test"; + + beatmap.OnlineInfo.HasVideo = true; + beatmap.OnlineInfo.HasStoryboard = true; + + beatmap.OnlineInfo.Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + ExternalLink = "http://osu.ppy.sh", + }; + + return beatmap; + } + + private class TestDownloadButton : DownloadButton + { + public new bool DownloadEnabled => base.DownloadEnabled; + + public DownloadState DownloadState => State.Value; + + public TestDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) + : base(beatmapSet, noVideo) + { + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseDirect.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs similarity index 97% rename from osu.Game.Tests/Visual/TestCaseDirect.cs rename to osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs index 2f45c87161..efc12c5fdd 100644 --- a/osu.Game.Tests/Visual/TestCaseDirect.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using NUnit.Framework; @@ -8,10 +8,10 @@ using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Rulesets; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseDirect : OsuTestCase + public class TestSceneDirectOverlay : OsuTestScene { private DirectOverlay direct; private RulesetStore rulesets; diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs new file mode 100644 index 0000000000..53dbaeddda --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneDirectPanel : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DirectGridPanel), + typeof(DirectListPanel), + typeof(IconPill) + }; + + private BeatmapSetInfo getUndownloadableBeatmapSet(RulesetInfo ruleset) => new BeatmapSetInfo + { + OnlineBeatmapSetID = 123, + Metadata = new BeatmapMetadata + { + Title = "undownloadable beatmap", + Artist = "test", + Source = "more tests", + Author = new User + { + Username = "BanchoBot", + Id = 3, + }, + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + }, + Preview = @"https://b.ppy.sh/preview/12345.mp3", + PlayCount = 123, + FavouriteCount = 456, + BPM = 111, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + }, + Beatmaps = new List + { + new BeatmapInfo + { + Ruleset = ruleset, + Version = "Test", + StarDifficulty = 6.42, + } + } + }; + + [BackgroundDependencyLoader] + private void load() + { + var ruleset = new OsuRuleset().RulesetInfo; + + var normal = CreateWorkingBeatmap(ruleset).BeatmapSetInfo; + normal.OnlineInfo.HasVideo = true; + normal.OnlineInfo.HasStoryboard = true; + + var undownloadable = getUndownloadableBeatmapSet(ruleset); + + Child = new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new DirectGridPanel(normal), + new DirectListPanel(normal), + new DirectGridPanel(undownloadable), + new DirectListPanel(undownloadable), + }, + }, + }; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs similarity index 55% rename from osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs rename to osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs index 07bc59b29c..637b577021 100644 --- a/osu.Game.Tests/Visual/TestCaseExternalLinkButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { - public class TestCaseExternalLinkButton : OsuTestCase + public class TestSceneExternalLinkButton : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(ExternalLinkButton) }; - public TestCaseExternalLinkButton() + public TestSceneExternalLinkButton() { Child = new ExternalLinkButton("https://osu.ppy.sh/home") { diff --git a/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs new file mode 100644 index 0000000000..fe8437be17 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneFullscreenOverlay.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneFullscreenOverlay : OsuTestScene + { + private FullscreenOverlay overlay; + + protected override void LoadComplete() + { + base.LoadComplete(); + + int fireCount = 0; + + Add(overlay = new TestFullscreenOverlay()); + + overlay.State.ValueChanged += _ => fireCount++; + + AddStep(@"show", overlay.Show); + + AddAssert("fire count 1", () => fireCount == 1); + + AddStep(@"show again", overlay.Show); + + // this logic is specific to FullscreenOverlay + AddAssert("fire count 2", () => fireCount == 2); + + AddStep(@"hide", overlay.Hide); + + AddAssert("fire count 3", () => fireCount == 3); + } + + private class TestFullscreenOverlay : FullscreenOverlay + { + public TestFullscreenOverlay() + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs similarity index 81% rename from osu.Game.Tests/Visual/TestCaseGraph.cs rename to osu.Game.Tests/Visual/Online/TestSceneGraph.cs index 0b0a80648b..fa433571cf 100644 --- a/osu.Game.Tests/Visual/TestCaseGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using NUnit.Framework; @@ -7,12 +7,12 @@ using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseGraph : OsuTestCase + public class TestSceneGraph : OsuTestScene { - public TestCaseGraph() + public TestSceneGraph() { BarGraph graph; diff --git a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs similarity index 75% rename from osu.Game.Tests/Visual/TestCaseHistoricalSection.cs rename to osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index a576ac84e7..883f0c5e3f 100644 --- a/osu.Game.Tests/Visual/TestCaseHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -1,21 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Historical; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseHistoricalSection : OsuTestCase + public class TestSceneHistoricalSection : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual typeof(DrawableProfileRow) }; - public TestCaseHistoricalSection() + public TestSceneHistoricalSection() { HistoricalSection section; @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual Colour = OsuColour.Gray(0.2f) }); - Add(new ScrollContainer + Add(new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Child = section = new HistoricalSection(), diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs new file mode 100644 index 0000000000..c344cb9598 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Overlays.Profile.Header.Components; +using System; +using System.Collections.Generic; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Taiko; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneProfileRulesetSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileRulesetSelector), + typeof(ProfileRulesetTabItem), + }; + + public TestSceneProfileRulesetSelector() + { + ProfileRulesetSelector selector; + + Child = selector = new ProfileRulesetSelector + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + AddStep("set osu! as default", () => selector.SetDefaultRuleset(new OsuRuleset().RulesetInfo)); + AddStep("set mania as default", () => selector.SetDefaultRuleset(new ManiaRuleset().RulesetInfo)); + AddStep("set taiko as default", () => selector.SetDefaultRuleset(new TaikoRuleset().RulesetInfo)); + AddStep("set catch as default", () => selector.SetDefaultRuleset(new CatchRuleset().RulesetInfo)); + AddStep("select default ruleset", selector.SelectDefaultRuleset); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs similarity index 76% rename from osu.Game.Tests/Visual/TestCaseRankGraph.cs rename to osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index f8eea7d63c..709e75ab13 100644 --- a/osu.Game.Tests/Visual/TestCaseRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osuTK; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using System.Collections.Generic; -using System; -using NUnit.Framework; using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Users; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseRankGraph : OsuTestCase + public class TestSceneRankGraph : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -24,13 +24,14 @@ namespace osu.Game.Tests.Visual typeof(LineGraph) }; - public TestCaseRankGraph() + public TestSceneRankGraph() { RankGraph graph; var data = new int[89]; var dataWithZeros = new int[89]; var smallData = new int[89]; + var edgyData = new int[89]; for (int i = 0; i < 89; i++) data[i] = dataWithZeros[i] = (i + 1) * 1000; @@ -41,6 +42,14 @@ namespace osu.Game.Tests.Visual for (int i = 79; i < 89; i++) smallData[i] = 100000 - i * 1000; + bool edge = true; + + for (int i = 0; i < 20; i++) + { + edgyData[i] = 100000 + (edge ? 1000 : -1000) * (i + 1); + edge = !edge; + } + Add(new Container { Anchor = Anchor.Centre, @@ -120,6 +129,22 @@ namespace osu.Game.Tests.Visual } }; }); + + AddStep("graph with edges", () => + { + graph.User.Value = new User + { + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 12000 }, + PP = 12345, + }, + RankHistory = new User.RankHistoryData + { + Data = edgyData, + } + }; + }); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs new file mode 100644 index 0000000000..06414af865 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -0,0 +1,187 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.MathUtils; +using osu.Game.Graphics; +using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneScoresContainer : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(DrawableTopScore), + typeof(TopScoreUserSection), + typeof(TopScoreStatisticsSection), + typeof(ScoreTable), + typeof(ScoreTableRowBackground), + }; + + private readonly Box background; + + public TestSceneScoresContainer() + { + ScoresContainer scoresContainer; + + Child = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Width = 0.8f, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + scoresContainer = new ScoresContainer(), + } + }; + + var scores = new List + { + new ScoreInfo + { + User = new User + { + Id = 6602580, + Username = @"waaiiru", + Country = new Country + { + FullName = @"Spain", + FlagName = @"ES", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + new OsuModHardRock(), + }, + Rank = ScoreRank.XH, + PP = 200, + MaxCombo = 1234, + TotalScore = 1234567890, + Accuracy = 1, + }, + new ScoreInfo + { + User = new User + { + Id = 4608074, + Username = @"Skycries", + Country = new Country + { + FullName = @"Brazil", + FlagName = @"BR", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + new OsuModFlashlight(), + }, + Rank = ScoreRank.S, + PP = 190, + MaxCombo = 1234, + TotalScore = 1234789, + Accuracy = 0.9997, + }, + new ScoreInfo + { + User = new User + { + Id = 1014222, + Username = @"eLy", + Country = new Country + { + FullName = @"Japan", + FlagName = @"JP", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + new OsuModHidden(), + }, + Rank = ScoreRank.B, + PP = 180, + MaxCombo = 1234, + TotalScore = 12345678, + Accuracy = 0.9854, + }, + new ScoreInfo + { + User = new User + { + Id = 1541390, + Username = @"Toukai", + Country = new Country + { + FullName = @"Canada", + FlagName = @"CA", + }, + }, + Mods = new Mod[] + { + new OsuModDoubleTime(), + }, + Rank = ScoreRank.C, + PP = 170, + MaxCombo = 1234, + TotalScore = 1234567, + Accuracy = 0.8765, + }, + new ScoreInfo + { + User = new User + { + Id = 7151382, + Username = @"Mayuri Hana", + Country = new Country + { + FullName = @"Thailand", + FlagName = @"TH", + }, + }, + Rank = ScoreRank.D, + PP = 160, + MaxCombo = 1234, + TotalScore = 123456, + Accuracy = 0.6543, + }, + }; + + foreach (var s in scores) + { + s.Statistics.Add(HitResult.Great, RNG.Next(2000)); + s.Statistics.Add(HitResult.Good, RNG.Next(2000)); + s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); + s.Statistics.Add(HitResult.Miss, RNG.Next(2000)); + } + + AddStep("Load all scores", () => scoresContainer.Scores = scores); + AddStep("Load null scores", () => scoresContainer.Scores = null); + AddStep("Load only one score", () => scoresContainer.Scores = new[] { scores.First() }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray2; + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs new file mode 100644 index 0000000000..bccb263600 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneShowMoreButton.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Overlays.Profile.Sections; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneShowMoreButton : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ShowMoreButton), + }; + + public TestSceneShowMoreButton() + { + ShowMoreButton button = null; + + int fireCount = 0; + + Add(button = new ShowMoreButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = () => + { + fireCount++; + // ReSharper disable once AccessToModifiedClosure + // ReSharper disable once PossibleNullReferenceException + Scheduler.AddDelayed(() => button.IsLoading = false, 2000); + } + }); + + AddStep("click button", () => button.Click()); + + AddAssert("action fired once", () => fireCount == 1); + AddAssert("is in loading state", () => button.IsLoading); + + AddStep("click button", () => button.Click()); + + AddAssert("action not fired", () => fireCount == 1); + AddAssert("is in loading state", () => button.IsLoading); + + AddUntilStep("wait for loaded", () => !button.IsLoading); + + AddStep("click button", () => button.Click()); + + AddAssert("action fired twice", () => fireCount == 2); + AddAssert("is in loading state", () => button.IsLoading); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseSocial.cs b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs similarity index 91% rename from osu.Game.Tests/Visual/TestCaseSocial.cs rename to osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs index 03f8fccae6..5cb96c7ed2 100644 --- a/osu.Game.Tests/Visual/TestCaseSocial.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSocialOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,22 +8,21 @@ using osu.Game.Overlays; using osu.Game.Overlays.Social; using osu.Game.Users; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseSocial : OsuTestCase + public class TestSceneSocialOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(UserPanel), typeof(SocialPanel), typeof(FilterControl), - typeof(SocialOverlay), typeof(SocialGridPanel), typeof(SocialListPanel) }; - public TestCaseSocial() + public TestSceneSocialOverlay() { SocialOverlay s = new SocialOverlay { diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs new file mode 100644 index 0000000000..91006bc0d9 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneStandAloneChatDisplay : OsuTestScene + { + private readonly Channel testChannel = new Channel(); + + private readonly User admin = new User + { + Username = "HappyStick", + Id = 2, + Colour = "f2ca34" + }; + + private readonly User redUser = new User + { + Username = "BanchoBot", + Id = 3, + }; + + private readonly User blueUser = new User + { + Username = "Zallius", + Id = 4, + }; + + [Cached] + private ChannelManager channelManager = new ChannelManager(); + + private readonly StandAloneChatDisplay chatDisplay; + private readonly StandAloneChatDisplay chatDisplay2; + + public TestSceneStandAloneChatDisplay() + { + Add(channelManager); + + Add(chatDisplay = new StandAloneChatDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding(20), + Size = new Vector2(400, 80) + }); + + Add(chatDisplay2 = new StandAloneChatDisplay(true) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding(20), + Size = new Vector2(400, 150) + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + channelManager.CurrentChannel.Value = testChannel; + + chatDisplay.Channel.Value = testChannel; + chatDisplay2.Channel.Value = testChannel; + + int sequence = 0; + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = admin, + Content = "I am a wang!" + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = redUser, + Content = "I am team red." + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = redUser, + Content = "I plan to win!" + })); + + AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = blueUser, + Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." + })); + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = admin, + Content = "Okay okay, calm down guys. Let's do this!" + })); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs new file mode 100644 index 0000000000..54f06d6ad2 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneUserPanel : OsuTestScene + { + private readonly UserPanel peppy; + + public TestSceneUserPanel() + { + UserPanel flyte; + + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10f), + Children = new[] + { + flyte = new UserPanel(new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }) { Width = 300 }, + peppy = new UserPanel(new User + { + Username = @"peppy", + Id = 2, + Country = new Country { FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + IsSupporter = true, + SupportLevel = 3, + }) { Width = 300 }, + }, + }); + + flyte.Status.Value = new UserStatusOnline(); + peppy.Status.Value = null; + } + + [Test] + public void UserStatusesTests() + { + AddStep("online", () => { peppy.Status.Value = new UserStatusOnline(); }); + AddStep(@"do not disturb", () => { peppy.Status.Value = new UserStatusDoNotDisturb(); }); + AddStep(@"offline", () => { peppy.Status.Value = new UserStatusOffline(); }); + AddStep(@"null status", () => { peppy.Status.Value = null; }); + } + + [Test] + public void UserActivitiesTests() + { + Bindable activity = new Bindable(); + + peppy.Activity.BindTo(activity); + + AddStep("idle", () => { activity.Value = null; }); + AddStep("spectating", () => { activity.Value = new UserActivity.Spectating(); }); + AddStep("solo", () => { activity.Value = new UserActivity.SoloGame(null, null); }); + AddStep("choosing", () => { activity.Value = new UserActivity.ChoosingBeatmap(); }); + AddStep("editing", () => { activity.Value = new UserActivity.Editing(null); }); + AddStep("modding", () => { activity.Value = new UserActivity.Modding(); }); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs new file mode 100644 index 0000000000..2285c9b799 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneUserProfileHeader : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(RankGraph), + typeof(LineGraph), + typeof(OverlayHeaderTabControl), + typeof(CentreHeaderContainer), + typeof(BottomHeaderContainer), + typeof(DetailHeaderContainer), + typeof(ProfileHeaderButton) + }; + + [Resolved] + private IAPIProvider api { get; set; } + + private readonly ProfileHeader header; + + public TestSceneUserProfileHeader() + { + header = new ProfileHeader(); + Add(header); + + AddStep("Show test dummy", () => header.User.Value = TestSceneUserProfileOverlay.TEST_USER); + + AddStep("Show null dummy", () => header.User.Value = new User + { + Username = "Null" + }); + + AddStep("Show online dummy", () => header.User.Value = new User + { + Username = "IAmOnline", + LastVisit = DateTimeOffset.Now, + IsOnline = true, + }); + + AddStep("Show offline dummy", () => header.User.Value = new User + { + Username = "IAmOffline", + LastVisit = DateTimeOffset.Now, + IsOnline = false, + }); + + addOnlineStep("Show ppy", new User + { + Username = @"peppy", + Id = 2, + IsSupporter = true, + Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" + }); + + addOnlineStep("Show flyte", new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }); + } + + private void addOnlineStep(string name, User fallback) + { + AddStep(name, () => + { + if (api.IsLoggedIn) + { + var request = new GetUserRequest(fallback.Id); + request.Success += user => header.User.Value = user; + api.Queue(request); + } + else + header.User.Value = fallback; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs new file mode 100644 index 0000000000..c2376aa153 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -0,0 +1,117 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneUserProfileOverlay : OsuTestScene + { + private readonly TestUserProfileOverlay profile; + + [Resolved] + private IAPIProvider api { get; set; } + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ProfileHeader), + typeof(RankGraph), + typeof(LineGraph), + typeof(SectionsContainer<>), + typeof(SupporterIcon) + }; + + public static readonly User TEST_USER = new User + { + Username = @"Somebody", + Id = 1, + Country = new Country { FullName = @"Alien" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + JoinDate = DateTimeOffset.Now.AddDays(-1), + LastVisit = DateTimeOffset.Now, + ProfileOrder = new[] { "me" }, + Statistics = new UserStatistics + { + Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, + PP = 4567.89m, + Level = new UserStatistics.LevelInfo + { + Current = 727, + Progress = 69, + } + }, + RankHistory = new User.RankHistoryData + { + Mode = @"osu", + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + }, + Badges = new[] + { + new Badge + { + AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), + Description = "Outstanding help by being a voluntary test subject.", + ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" + } + }, + Title = "osu!volunteer", + Colour = "ff0000", + Achievements = new User.UserAchievement[0], + }; + + public TestSceneUserProfileOverlay() + { + Add(profile = new TestUserProfileOverlay()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep("Show offline dummy", () => profile.ShowUser(TEST_USER, false)); + + AddStep("Show null dummy", () => profile.ShowUser(new User + { + Username = @"Null", + Id = 1, + }, false)); + + AddStep("Show ppy", () => profile.ShowUser(new User + { + Username = @"peppy", + Id = 2, + IsSupporter = true, + Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" + }, api.IsLoggedIn)); + + AddStep("Show flyte", () => profile.ShowUser(new User + { + Username = @"flyte", + Id = 3103765, + Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" + }, api.IsLoggedIn)); + + AddStep("Hide", profile.Hide); + AddStep("Show without reload", profile.Show); + } + + private class TestUserProfileOverlay : UserProfileOverlay + { + public new ProfileHeader Header => base.Header; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs similarity index 93% rename from osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs index b14606780d..f022425bf6 100644 --- a/osu.Game.Tests/Visual/TestCaseUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs @@ -1,23 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Recent; -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Online.API.Requests.Responses; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseUserProfileRecentSection : OsuTestCase + public class TestSceneUserProfileRecentSection : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -27,7 +28,7 @@ namespace osu.Game.Tests.Visual typeof(MedalIcon) }; - public TestCaseUserProfileRecentSection() + public TestSceneUserProfileRecentSection() { Children = new Drawable[] { @@ -36,7 +37,7 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f) }, - new ScrollContainer + new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Child = new FillFlowContainer diff --git a/osu.Game.Tests/Visual/TestCaseUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs similarity index 76% rename from osu.Game.Tests/Visual/TestCaseUserRanks.cs rename to osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs index 8329ad705a..9f0a8c769a 100644 --- a/osu.Game.Tests/Visual/TestCaseUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs @@ -1,25 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.Profile.Sections; using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Users; -using System; -using System.Collections.Generic; -using NUnit.Framework; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestCaseUserRanks : OsuTestCase + public class TestSceneUserRanks : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(DrawableProfileScore), typeof(RanksSection) }; - public TestCaseUserRanks() + public TestSceneUserRanks() { RanksSection ranks; @@ -33,7 +34,7 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f) }, - new ScrollContainer + new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Child = ranks = new RanksSection(), diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs new file mode 100644 index 0000000000..426ff988c4 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Overlays; +using osu.Game.Overlays.KeyBinding; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneKeyBindingPanel : OsuTestScene + { + private readonly KeyBindingPanel panel; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(KeyBindingRow), + typeof(GlobalKeyBindingsSection), + typeof(KeyBindingRow), + typeof(KeyBindingsSubsection), + typeof(RulesetBindingsSection), + typeof(VariantBindingsSubsection), + }; + + public TestSceneKeyBindingPanel() + { + Child = panel = new KeyBindingPanel(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + panel.Show(); + } + } +} diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs new file mode 100644 index 0000000000..668fdf2c20 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tests.Visual.Settings +{ + [TestFixture] + public class TestSceneSettingsPanel : OsuTestScene + { + private readonly SettingsPanel settings; + private readonly DialogOverlay dialogOverlay; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(SettingsFooter), + typeof(SettingsOverlay), + }; + + public TestSceneSettingsPanel() + { + settings = new SettingsOverlay + { + State = { Value = Visibility.Visible } + }; + Add(dialogOverlay = new DialogOverlay + { + Depth = -1 + }); + } + + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(dialogOverlay); + + Add(settings); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs similarity index 96% rename from osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index db66c01814..7c9b7c7815 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -17,10 +17,10 @@ using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestCaseBeatmapCarousel : OsuTestCase + public class TestSceneBeatmapCarousel : OsuTestScene { private TestBeatmapCarousel carousel; private RulesetStore rulesets; @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual typeof(DrawableCarouselBeatmapSet), }; - private readonly Stack selectedSets = new Stack(); private readonly HashSet eagerSelectedIDs = new HashSet(); @@ -88,7 +87,7 @@ namespace osu.Game.Tests.Visual carousel.BeatmapSetsChanged = () => changed = true; carousel.BeatmapSets = beatmapSets; }); - AddUntilStep(() => changed, "Wait for load"); + AddUntilStep("Wait for load", () => changed); } private void ensureRandomFetchSuccess() => @@ -148,9 +147,10 @@ namespace osu.Game.Tests.Visual private bool selectedBeatmapVisible() { - var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected); + var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected); if (currentlySelected == null) return true; + return currentlySelected.Item.Visible; } @@ -167,8 +167,7 @@ namespace osu.Game.Tests.Visual carousel.Filter(new FilterCriteria { SearchText = "Dingo" }, false); carousel.Filter(new FilterCriteria(), false); eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); - } - ); + }); } /// @@ -215,7 +214,7 @@ namespace osu.Game.Tests.Visual checkSelected(3, 2); AddStep("Un-filter (debounce)", () => carousel.Filter(new FilterCriteria())); - AddUntilStep(() => !carousel.PendingFilterTask, "Wait for debounce"); + AddUntilStep("Wait for debounce", () => !carousel.PendingFilterTask); checkVisibleItemCount(diff: false, count: set_count); checkVisibleItemCount(diff: true, count: 3); @@ -328,13 +327,13 @@ namespace osu.Game.Tests.Visual AddStep("Remove first", () => carousel.RemoveBeatmapSet(carousel.BeatmapSets.First())); checkSelected(1); - AddUntilStep(() => + AddUntilStep("Remove all", () => { if (!carousel.BeatmapSets.Any()) return true; carousel.RemoveBeatmapSet(carousel.BeatmapSets.Last()); return false; - }, "Remove all"); + }); checkNoSelection(); } @@ -509,6 +508,7 @@ namespace osu.Game.Tests.Visual }, Beatmaps = new List(), }; + for (int b = 1; b < 101; b++) { toReturn.Beatmaps.Add(new BeatmapInfo @@ -523,6 +523,7 @@ namespace osu.Game.Tests.Visual } }); } + return toReturn; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs new file mode 100644 index 0000000000..7b97a27732 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetailArea.cs @@ -0,0 +1,168 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select; +using osuTK; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [TestFixture] + [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] + public class TestSceneBeatmapDetailArea : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] { typeof(BeatmapDetails) }; + + [BackgroundDependencyLoader] + private void load(OsuGameBase game) + { + BeatmapDetailArea detailsArea; + Add(detailsArea = new BeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(550f, 450f), + }); + + AddStep("all metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + { + BeatmapSetInfo = + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + } + } + ); + + AddStep("all except source", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + { + BeatmapSetInfo = + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, + BeatmapInfo = + { + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + } + }); + + AddStep("ratings", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + { + BeatmapSetInfo = + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, + BeatmapInfo = + { + Version = "Only Ratings", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has ratings metrics but not retries or fails", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 6, + DrainRate = 9, + OverallDifficulty = 6, + ApproachRate = 6, + }, + StarDifficulty = 4.8f + } + }); + + AddStep("fails+retries", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + { + BeatmapInfo = + { + Version = "Only Retries and Fails", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has retries and fails but no ratings", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 3.7f, + DrainRate = 6, + OverallDifficulty = 6, + ApproachRate = 7, + }, + StarDifficulty = 2.91f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + } + }); + + AddStep("null metrics", () => detailsArea.Beatmap = new DummyWorkingBeatmap(null, null) + { + BeatmapInfo = + { + Version = "No Metrics", + Metadata = new BeatmapMetadata + { + Source = "osu!lazer", + Tags = "this beatmap has no metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5.5f, + ApproachRate = 6.5f, + }, + StarDifficulty = 1.97f, + } + }); + + AddStep("null beatmap", () => detailsArea.Beatmap = null); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs similarity index 51% rename from osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index 0f7d871e5e..acf037198f 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -1,28 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.ComponentModel; using System.Linq; +using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Screens.Select; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { - [Description("PlaySongSelect beatmap details")] - public class TestCaseBeatmapDetails : OsuTestCase + [System.ComponentModel.Description("PlaySongSelect beatmap details")] + public class TestSceneBeatmapDetails : OsuTestScene { - public TestCaseBeatmapDetails() + private BeatmapDetails details; + + [SetUp] + public void Setup() => Schedule(() => { - BeatmapDetails details; - Add(details = new BeatmapDetails + Child = details = new BeatmapDetails { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(150), - }); + }; + }); - AddStep("beatmap all metrics", () => details.Beatmap = new BeatmapInfo + [Test] + public void TestAllMetrics() + { + AddStep("all metrics", () => details.Beatmap = new BeatmapInfo { + BeatmapSet = new BeatmapSetInfo + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, Version = "All Metrics", Metadata = new BeatmapMetadata { @@ -39,14 +49,51 @@ namespace osu.Game.Tests.Visual StarDifficulty = 5.3f, Metrics = new BeatmapMetrics { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, }); + } - AddStep("beatmap ratings", () => details.Beatmap = new BeatmapInfo + [Test] + public void TestAllMetricsExceptSource() + { + AddStep("all except source", () => details.Beatmap = new BeatmapInfo { + BeatmapSet = new BeatmapSetInfo + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, + Version = "All Metrics", + Metadata = new BeatmapMetadata + { + Tags = "this beatmap has all the metrics", + }, + BaseDifficulty = new BeatmapDifficulty + { + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + }, + StarDifficulty = 5.3f, + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + }); + } + + [Test] + public void TestOnlyRatings() + { + AddStep("ratings", () => details.Beatmap = new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo + { + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() } + }, Version = "Only Ratings", Metadata = new BeatmapMetadata { @@ -61,13 +108,13 @@ namespace osu.Game.Tests.Visual ApproachRate = 6, }, StarDifficulty = 4.8f, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - }, }); + } - AddStep("beatmap fails retries", () => details.Beatmap = new BeatmapInfo + [Test] + public void TestOnlyFailsAndRetries() + { + AddStep("fails retries", () => details.Beatmap = new BeatmapInfo { Version = "Only Retries and Fails", Metadata = new BeatmapMetadata @@ -85,12 +132,16 @@ namespace osu.Game.Tests.Visual StarDifficulty = 2.91f, Metrics = new BeatmapMetrics { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, }); + } - AddStep("beatmap no metrics", () => details.Beatmap = new BeatmapInfo + [Test] + public void TestNoMetrics() + { + AddStep("no metrics", () => details.Beatmap = new BeatmapInfo { Version = "No Metrics", Metadata = new BeatmapMetadata @@ -106,10 +157,22 @@ namespace osu.Game.Tests.Visual ApproachRate = 6.5f, }, StarDifficulty = 1.97f, - Metrics = new BeatmapMetrics(), }); + } + [Test] + public void TestNullBeatmap() + { AddStep("null beatmap", () => details.Beatmap = null); } + + [Test] + public void TestOnlineMetrics() + { + AddStep("online ratings/retries/fails", () => details.Beatmap = new BeatmapInfo + { + OnlineBeatmapID = 162, + }); + } } } diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs similarity index 88% rename from osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 35cdfda92c..932e114580 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -1,14 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; @@ -19,12 +17,12 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Select; -using osu.Game.Tests.Beatmaps; +using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestCaseBeatmapInfoWedge : OsuTestCase + public class TestSceneBeatmapInfoWedge : OsuTestScene { private RulesetStore rulesets; private TestBeatmapInfoWedge infoWedge; @@ -49,20 +47,20 @@ namespace osu.Game.Tests.Visual AddStep("show", () => { - infoWedge.State = Visibility.Visible; - infoWedge.Beatmap = Beatmap; + infoWedge.Show(); + infoWedge.Beatmap = Beatmap.Value; }); // select part is redundant, but wait for load isn't selectBeatmap(Beatmap.Value.Beatmap); - AddWaitStep(3); + AddWaitStep("wait for select", 3); - AddStep("hide", () => { infoWedge.State = Visibility.Hidden; }); + AddStep("hide", () => { infoWedge.Hide(); }); - AddWaitStep(3); + AddWaitStep("wait for hide", 3); - AddStep("show", () => { infoWedge.State = Visibility.Visible; }); + AddStep("show", () => { infoWedge.Show(); }); foreach (var rulesetInfo in rulesets.AvailableRulesets) { @@ -83,15 +81,19 @@ namespace osu.Game.Tests.Visual case OsuRuleset _: testInfoLabels(5); break; + case TaikoRuleset _: testInfoLabels(5); break; + case CatchRuleset _: testInfoLabels(5); break; + case ManiaRuleset _: testInfoLabels(4); break; + default: testInfoLabels(2); break; @@ -132,10 +134,10 @@ namespace osu.Game.Tests.Visual AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () => { infoBefore = infoWedge.Info; - infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : new TestWorkingBeatmap(b); + infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b); }); - AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load"); + AddUntilStep("wait for async load", () => infoWedge.Info != infoBefore); } private IBeatmap createTestBeatmap(RulesetInfo ruleset) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs new file mode 100644 index 0000000000..ecdc484887 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapOptionsOverlay.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; +using osu.Framework.Graphics.Sprites; +using osu.Game.Screens.Select.Options; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [Description("bottom beatmap details")] + public class TestSceneBeatmapOptionsOverlay : OsuTestScene + { + public TestSceneBeatmapOptionsOverlay() + { + var overlay = new BeatmapOptionsOverlay(); + + overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, Color4.Purple, null, Key.Number1); + overlay.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, Color4.Purple, null, Key.Number2); + overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.Solid.PencilAlt, Color4.Yellow, null, Key.Number3); + overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.Solid.Trash, Color4.Pink, null, Key.Number4, float.MaxValue); + + Add(overlay); + + AddStep(@"Toggle", overlay.ToggleVisibility); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs similarity index 90% rename from osu.Game.Tests/Visual/TestCaseLeaderboard.cs rename to osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs index d4a2fcc62c..157e572606 100644 --- a/osu.Game.Tests/Visual/TestCaseLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneLeaderboard.cs @@ -1,25 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Beatmaps; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; -using osu.Framework.Allocation; using osuTK; -using System.Linq; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [Description("PlaySongSelect leaderboard")] - public class TestCaseLeaderboard : OsuTestCase + public class TestSceneLeaderboard : OsuTestScene { - public override IReadOnlyList RequiredTypes => new[] { + public override IReadOnlyList RequiredTypes => new[] + { typeof(Placeholder), typeof(MessagePlaceholder), typeof(RetrievalFailurePlaceholder), @@ -29,14 +31,14 @@ namespace osu.Game.Tests.Visual private readonly FailableLeaderboard leaderboard; - public TestCaseLeaderboard() + public TestSceneLeaderboard() { Add(leaderboard = new FailableLeaderboard { Origin = Anchor.Centre, Anchor = Anchor.Centre, Size = new Vector2(550f, 450f), - Scope = LeaderboardScope.Global, + Scope = BeatmapLeaderboardScope.Global, }); AddStep(@"New Scores", newScores); @@ -58,7 +60,7 @@ namespace osu.Game.Tests.Visual { var scores = new[] { - new Score + new ScoreInfo { Rank = ScoreRank.XH, Accuracy = 1, @@ -76,7 +78,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.X, Accuracy = 1, @@ -94,7 +96,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.SH, Accuracy = 1, @@ -112,7 +114,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.S, Accuracy = 1, @@ -130,7 +132,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.A, Accuracy = 1, @@ -148,7 +150,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.B, Accuracy = 0.9826, @@ -166,7 +168,7 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { Rank = ScoreRank.C, Accuracy = 0.9654, @@ -184,9 +186,9 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { - Rank = ScoreRank.F, + Rank = ScoreRank.D, Accuracy = 0.6025, MaxCombo = 244, TotalScore = 1707827, @@ -202,9 +204,9 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { - Rank = ScoreRank.F, + Rank = ScoreRank.D, Accuracy = 0.5140, MaxCombo = 244, TotalScore = 1707827, @@ -220,9 +222,9 @@ namespace osu.Game.Tests.Visual }, }, }, - new Score + new ScoreInfo { - Rank = ScoreRank.F, + Rank = ScoreRank.D, Accuracy = 0.4222, MaxCombo = 244, TotalScore = 1707827, @@ -268,14 +270,13 @@ namespace osu.Game.Tests.Visual }, Metrics = new BeatmapMetrics { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, }; } - private class FailableLeaderboard : Leaderboard + private class FailableLeaderboard : BeatmapLeaderboard { public void SetRetrievalState(PlaceholderState state) { diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs similarity index 60% rename from osu.Game.Tests/Visual/TestCasePlaySongSelect.cs rename to osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 888bf6250f..962e0fb362 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,23 +8,27 @@ using System.Linq; using System.Text; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.MathUtils; +using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestCasePlaySongSelect : OsuTestCase + public class TestScenePlaySongSelect : ScreenTestScene { private BeatmapManager manager; @@ -33,13 +37,9 @@ namespace osu.Game.Tests.Visual private WorkingBeatmap defaultBeatmap; private DatabaseContextFactory factory; - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); - public override IReadOnlyList RequiredTypes => new[] { - typeof(SongSelect), + typeof(Screens.Select.SongSelect), typeof(BeatmapCarousel), typeof(CarouselItem), @@ -57,11 +57,19 @@ namespace osu.Game.Tests.Visual private class TestSongSelect : PlaySongSelect { + public Action StartRequested; + public new Bindable Ruleset => base.Ruleset; public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; + + protected override bool OnStart() + { + StartRequested?.Invoke(); + return base.OnStart(); + } } private TestSongSelect songSelect; @@ -73,7 +81,7 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load() + private void load(GameHost host, AudioManager audio) { factory = new DatabaseContextFactory(LocalStorage); factory.ResetDatabase(); @@ -87,30 +95,28 @@ namespace osu.Game.Tests.Visual usage.Migrate(); Dependencies.Cache(rulesets = new RulesetStore(factory)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null) - { - DefaultBeatmap = defaultBeatmap = Beatmap.Default - }); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, audio, host, defaultBeatmap = Beatmap.Default)); Beatmap.SetDefault(); } [SetUp] - public virtual void SetUp() + public virtual void SetUp() => Schedule(() => { + Ruleset.Value = new OsuRuleset().RulesetInfo; manager?.Delete(manager.GetAllUsableBeatmapSets()); - Child = songSelect = new TestSongSelect(); - } + }); [Test] public void TestDummy() { + createSongSelect(); AddAssert("dummy selected", () => songSelect.CurrentBeatmap == defaultBeatmap); - AddAssert("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); + AddUntilStep("dummy shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap == defaultBeatmap); addManyTestMaps(); - AddWaitStep(3); + AddWaitStep("wait for select", 3); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); } @@ -118,8 +124,9 @@ namespace osu.Game.Tests.Visual [Test] public void TestSorting() { + createSongSelect(); addManyTestMaps(); - AddWaitStep(3); + AddWaitStep("wait for add", 3); AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); @@ -133,29 +140,32 @@ namespace osu.Game.Tests.Visual [Ignore("needs fixing")] public void TestImportUnderDifferentRuleset() { + createSongSelect(); changeRuleset(2); - importForRuleset(0); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection"); + addRulesetImportStep(0); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } [Test] public void TestImportUnderCurrentRuleset() { + createSongSelect(); changeRuleset(2); - importForRuleset(2); - importForRuleset(1); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 2, "has selection"); + addRulesetImportStep(2); + addRulesetImportStep(1); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); changeRuleset(1); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap.RulesetID == 1, "has selection"); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 1); changeRuleset(0); - AddUntilStep(() => songSelect.Carousel.SelectedBeatmap == null, "no selection"); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); } [Test] public void TestRulesetChangeResetsMods() { + createSongSelect(); changeRuleset(0); changeMods(new OsuModHardRock()); @@ -166,31 +176,73 @@ namespace osu.Game.Tests.Visual AddStep("change ruleset", () => { - songSelect.CurrentBeatmap.Mods.ValueChanged += onModChange; + Mods.ValueChanged += onModChange; songSelect.Ruleset.ValueChanged += onRulesetChange; Ruleset.Value = new TaikoRuleset().RulesetInfo; - songSelect.CurrentBeatmap.Mods.ValueChanged -= onModChange; + Mods.ValueChanged -= onModChange; songSelect.Ruleset.ValueChanged -= onRulesetChange; }); AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex); - AddAssert("empty mods", () => !selectedMods.Value.Any()); + AddAssert("empty mods", () => !Mods.Value.Any()); - void onModChange(IEnumerable mods) => modChangeIndex = actionIndex++; - void onRulesetChange(RulesetInfo ruleset) => rulesetChangeIndex = actionIndex--; + void onModChange(ValueChangedEvent> e) => modChangeIndex = actionIndex++; + void onRulesetChange(ValueChangedEvent e) => rulesetChangeIndex = actionIndex++; } - private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray()))); + [Test] + public void TestStartAfterUnMatchingFilterDoesNotStart() + { + createSongSelect(); + addManyTestMaps(); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); + + bool startRequested = false; + + AddStep("set filter and finalize", () => + { + songSelect.StartRequested = () => startRequested = true; + + songSelect.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); + songSelect.FinaliseSelection(); + + songSelect.StartRequested = null; + }); + + AddAssert("start not requested", () => !startRequested); + } + + [Test] + public void TestHideSetSelectsCorrectBeatmap() + { + int? previousID = null; + createSongSelect(); + addRulesetImportStep(0); + AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last())); + AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID); + AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First())); + AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID); + } + + private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id)); + + private void importForRuleset(int id) => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray())).Wait(); private static int importId; private int getImportId() => ++importId; - private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.ShortenedName))}", () => selectedMods.Value = mods); + private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods); private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id)); + private void createSongSelect() + { + AddStep("create song select", () => LoadScreen(songSelect = new TestSongSelect())); + AddUntilStep("wait for present", () => songSelect.IsCurrentScreen()); + } + private void addManyTestMaps() { AddStep("import test maps", () => @@ -198,7 +250,7 @@ namespace osu.Game.Tests.Visual var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray(); for (int i = 0; i < 100; i += 10) - manager.Import(createTestBeatmapSet(i, usableRulesets)); + manager.Import(createTestBeatmapSet(i, usableRulesets)).Wait(); }); } diff --git a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs b/osu.Game.Tests/Visual/TestCaseAllPlayers.cs deleted file mode 100644 index 4134ce3634..0000000000 --- a/osu.Game.Tests/Visual/TestCaseAllPlayers.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseAllPlayers : TestCasePlayer - { - } -} diff --git a/osu.Game.Tests/Visual/TestCaseAutoplay.cs b/osu.Game.Tests/Visual/TestCaseAutoplay.cs deleted file mode 100644 index 4abfec4371..0000000000 --- a/osu.Game.Tests/Visual/TestCaseAutoplay.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using System.Linq; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [Description("Player instantiated with an autoplay mod.")] - public class TestCaseAutoplay : TestCasePlayer - { - protected override Player CreatePlayer(Ruleset 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/TestCaseBadgeContainer.cs b/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs deleted file mode 100644 index 8177e2e272..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBadgeContainer.cs +++ /dev/null @@ -1,62 +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.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Overlays.Profile.Header; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBadgeContainer : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] { typeof(BadgeContainer) }; - - public TestCaseBadgeContainer() - { - BadgeContainer badgeContainer; - - Child = badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.Both - }; - - AddStep("Show 1 badge", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show 2 badges", () => badgeContainer.ShowBadges(new[] - { - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Contributed to osu!lazer testing", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.png", - }, - new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = "Appreciates compasses", - ImageUrl = "https://assets.ppy.sh/profile-badges/mg2018-1star.png", - } - })); - - AddStep("Show many badges", () => badgeContainer.ShowBadges(Enumerable.Range(1, 20).Select(i => new Badge - { - AwardedAt = DateTimeOffset.Now, - Description = $"Contributed to osu!lazer testing {i} times", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg", - }).ToArray())); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs b/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs deleted file mode 100644 index 4d19667926..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBeatmapDetailArea.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Game.Screens.Select; -using osuTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - [System.ComponentModel.Description("PlaySongSelect leaderboard/details area")] - public class TestCaseBeatmapDetailArea : OsuTestCase - { - public TestCaseBeatmapDetailArea() - { - Add(new BeatmapDetailArea - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(550f, 450f), - }); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs deleted file mode 100644 index a56b82d887..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBeatmapOptionsOverlay.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Game.Graphics; -using osu.Game.Screens.Select.Options; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Tests.Visual -{ - [Description("bottom beatmap details")] - public class TestCaseBeatmapOptionsOverlay : OsuTestCase - { - public TestCaseBeatmapOptionsOverlay() - { - var overlay = new BeatmapOptionsOverlay(); - - overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, Color4.Purple, null, Key.Number1); - overlay.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, Color4.Purple, null, Key.Number2); - overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.fa_pencil, Color4.Yellow, null, Key.Number3); - overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, Color4.Pink, null, Key.Number4, float.MaxValue); - - Add(overlay); - - AddStep(@"Toggle", overlay.ToggleVisibility); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs b/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs deleted file mode 100644 index d3098864f4..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBeatmapScoresContainer.cs +++ /dev/null @@ -1,315 +0,0 @@ -// 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.Shapes; -using osu.Framework.MathUtils; -using osu.Game.Graphics; -using osu.Game.Overlays.BeatmapSet.Scores; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Users; -using System.Collections.Generic; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets.Osu; - -namespace osu.Game.Tests.Visual -{ - [System.ComponentModel.Description("in BeatmapOverlay")] - public class TestCaseBeatmapScoresContainer : OsuTestCase - { - private readonly IEnumerable scores; - private readonly IEnumerable anotherScores; - private readonly APIScore topScore; - private readonly Box background; - - public TestCaseBeatmapScoresContainer() - { - Container container; - ScoresContainer scoresContainer; - - Child = container = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0.8f, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - scoresContainer = new ScoresContainer(), - } - }; - - AddStep("scores pack 1", () => scoresContainer.Scores = scores); - AddStep("scores pack 2", () => scoresContainer.Scores = anotherScores); - AddStep("only top score", () => scoresContainer.Scores = new[] { topScore }); - AddStep("remove scores", () => scoresContainer.Scores = null); - AddStep("resize to big", () => container.ResizeWidthTo(1, 300)); - AddStep("resize to normal", () => container.ResizeWidthTo(0.8f, 300)); - AddStep("online scores", () => scoresContainer.Beatmap = new BeatmapInfo { OnlineBeatmapID = 75, Ruleset = new OsuRuleset().RulesetInfo }); - - - scores = new[] - { - new APIScore - { - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - TotalScore = 1234567890, - Accuracy = 1, - }, - new APIScore - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new APIScore - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new APIScore - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - new APIScore - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.F, - TotalScore = 123456, - Accuracy = 0.6543, - }, - }; - foreach(var s in scores) - { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - anotherScores = new[] - { - new APIScore - { - User = new User - { - Id = 4608074, - Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - }, - Rank = ScoreRank.S, - TotalScore = 1234789, - Accuracy = 0.9997, - }, - new APIScore - { - User = new User - { - Id = 6602580, - Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.XH, - TotalScore = 1234567890, - Accuracy = 1, - }, - new APIScore - { - User = new User - { - Id = 7151382, - Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, - }, - Rank = ScoreRank.F, - TotalScore = 123456, - Accuracy = 0.6543, - }, - new APIScore - { - User = new User - { - Id = 1014222, - Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModHidden(), - }, - Rank = ScoreRank.B, - TotalScore = 12345678, - Accuracy = 0.9854, - }, - new APIScore - { - User = new User - { - Id = 1541390, - Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - }, - Rank = ScoreRank.C, - TotalScore = 1234567, - Accuracy = 0.8765, - }, - }; - foreach (var s in anotherScores) - { - s.Statistics.Add(HitResult.Great, RNG.Next(2000)); - s.Statistics.Add(HitResult.Good, RNG.Next(2000)); - s.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - topScore = new APIScore - { - User = new User - { - Id = 2705430, - Username = @"Mooha", - Country = new Country - { - FullName = @"France", - FlagName = @"FR", - }, - }, - Mods = new Mod[] - { - new OsuModDoubleTime(), - new OsuModFlashlight(), - new OsuModHardRock(), - }, - Rank = ScoreRank.B, - TotalScore = 987654321, - Accuracy = 0.8487, - }; - topScore.Statistics.Add(HitResult.Great, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Good, RNG.Next(2000)); - topScore.Statistics.Add(HitResult.Meh, RNG.Next(2000)); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray2; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs deleted file mode 100644 index 025562f75f..0000000000 --- a/osu.Game.Tests/Visual/TestCaseBeatmapSetOverlay.cs +++ /dev/null @@ -1,406 +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.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Overlays; -using osu.Game.Overlays.BeatmapSet; -using osu.Game.Overlays.BeatmapSet.Buttons; -using osu.Game.Overlays.BeatmapSet.Scores; -using osu.Game.Rulesets; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseBeatmapSetOverlay : OsuTestCase - { - private readonly BeatmapSetOverlay overlay; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(Header), - typeof(ClickableUsername), - typeof(DrawableScore), - typeof(DrawableTopScore), - typeof(ScoresContainer), - typeof(AuthorInfo), - typeof(BasicStats), - typeof(BeatmapPicker), - typeof(Details), - typeof(DownloadButton), - typeof(FavouriteButton), - typeof(Header), - typeof(HeaderButton), - typeof(Info), - typeof(PreviewButton), - typeof(SuccessRate), - }; - - public TestCaseBeatmapSetOverlay() - { - Add(overlay = new BeatmapSetOverlay()); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - var mania = rulesets.GetRuleset(3); - var taiko = rulesets.GetRuleset(1); - - AddStep(@"show loading", () => overlay.ShowBeatmapSet(null)); - - AddStep(@"show online", () => overlay.FetchAndShowBeatmapSet(55)); - - AddStep(@"show first", () => - { - overlay.ShowBeatmapSet(new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = @"Lachryma ", - Artist = @"Kaneko Chiharu", - Source = @"SOUND VOLTEX III GRAVITY WARS", - Tags = @"sdvx grace the 5th kac original song contest konami bemani", - Author = new User - { - Username = @"Fresh Chicken", - Id = 3984370, - }, - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Preview = @"https://b.ppy.sh/preview/415886.mp3", - PlayCount = 681380, - FavouriteCount = 356, - Submitted = new DateTime(2016, 2, 10), - Ranked = new DateTime(2016, 6, 19), - Status = BeatmapSetOnlineStatus.Ranked, - BPM = 236, - HasVideo = true, - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/415886/covers/cover.jpg?1465651778", - }, - }, - Beatmaps = new List - { - new BeatmapInfo - { - StarDifficulty = 1.36, - Version = @"BASIC", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 6.5f, - OverallDifficulty = 6.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 115000, - CircleCount = 265, - SliderCount = 71, - PlayCount = 47906, - PassCount = 19899, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 2.22, - Version = @"NOVICE", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 7, - OverallDifficulty = 7, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 592, - SliderCount = 62, - PlayCount = 162021, - PassCount = 72116, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 3.49, - Version = @"ADVANCED", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 7.5f, - OverallDifficulty = 7.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 1042, - SliderCount = 79, - PlayCount = 225178, - PassCount = 73001, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 4.24, - Version = @"EXHAUST", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 8, - OverallDifficulty = 8, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 1352, - SliderCount = 69, - PlayCount = 131545, - PassCount = 42703, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 5.26, - Version = @"GRAVITY", - Ruleset = mania, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 4, - DrainRate = 8.5f, - OverallDifficulty = 8.5f, - ApproachRate = 5, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 118000, - CircleCount = 1730, - SliderCount = 115, - PlayCount = 117673, - PassCount = 24241, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - }, - }); - }); - - AddStep(@"show second", () => - { - overlay.ShowBeatmapSet(new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = @"Soumatou Labyrinth", - Artist = @"Yunomi with Momobako&miko", - Tags = @"mmbk.com yuzu__rinrin charlotte", - Author = new User - { - Username = @"komasy", - Id = 1980256, - }, - }, - OnlineInfo = new BeatmapSetOnlineInfo - { - Preview = @"https://b.ppy.sh/preview/625493.mp3", - PlayCount = 22996, - FavouriteCount = 58, - Submitted = new DateTime(2016, 6, 11), - Ranked = new DateTime(2016, 7, 12), - Status = BeatmapSetOnlineStatus.Pending, - BPM = 160, - HasVideo = false, - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/625493/covers/cover.jpg?1499167472", - }, - }, - Beatmaps = new List - { - new BeatmapInfo - { - StarDifficulty = 1.40, - Version = @"yzrin's Kantan", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 2, - DrainRate = 7, - OverallDifficulty = 3, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 262, - SliderCount = 0, - PlayCount = 3952, - PassCount = 1373, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 2.23, - Version = @"Futsuu", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 2, - DrainRate = 6, - OverallDifficulty = 4, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 464, - SliderCount = 0, - PlayCount = 4833, - PassCount = 920, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 3.19, - Version = @"Muzukashii", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 2, - DrainRate = 6, - OverallDifficulty = 5, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 712, - SliderCount = 0, - PlayCount = 4405, - PassCount = 854, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 3.97, - Version = @"Charlotte's Oni", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 6, - OverallDifficulty = 5.5f, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 943, - SliderCount = 0, - PlayCount = 3950, - PassCount = 693, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - new BeatmapInfo - { - StarDifficulty = 5.08, - Version = @"Labyrinth Oni", - Ruleset = taiko, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 5, - OverallDifficulty = 6, - ApproachRate = 10, - }, - OnlineInfo = new BeatmapOnlineInfo - { - Length = 193000, - CircleCount = 1068, - SliderCount = 0, - PlayCount = 5856, - PassCount = 1207, - }, - Metrics = new BeatmapMetrics - { - Ratings = Enumerable.Range(0, 11), - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6), - }, - }, - }, - }); - }); - - AddStep(@"hide", overlay.Hide); - AddStep(@"show without reload", overlay.Show); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs deleted file mode 100644 index e3bd4026b3..0000000000 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ /dev/null @@ -1,45 +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.ComponentModel; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Online.Chat; -using osu.Game.Overlays; -using osu.Game.Overlays.Chat; -using osu.Game.Overlays.Chat.Tabs; - -namespace osu.Game.Tests.Visual -{ - [Description("Testing chat api and overlay")] - public class TestCaseChatDisplay : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(ChatOverlay), - typeof(ChatLine), - typeof(DrawableChannel), - typeof(ChannelSelectorTabItem), - typeof(ChannelTabControl), - typeof(ChannelTabItem), - typeof(PrivateChannelTabItem), - typeof(TabCloseButton) - }; - - [Cached] - private readonly ChannelManager channelManager = new ChannelManager(); - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - channelManager, - new ChatOverlay { State = Visibility.Visible } - }; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseDisclaimer.cs b/osu.Game.Tests/Visual/TestCaseDisclaimer.cs deleted file mode 100644 index b6b9a7adb4..0000000000 --- a/osu.Game.Tests/Visual/TestCaseDisclaimer.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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Screens.Menu; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - public class TestCaseDisclaimer : OsuTestCase - { - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - new Disclaimer() - }; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs b/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs deleted file mode 100644 index 8fd8880fd6..0000000000 --- a/osu.Game.Tests/Visual/TestCaseDrawableRoom.cs +++ /dev/null @@ -1,135 +0,0 @@ -// 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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; -using osu.Game.Screens.Multi.Components; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseDrawableRoom : OsuTestCase - { - private RulesetStore rulesets; - - protected override void LoadComplete() - { - base.LoadComplete(); - - DrawableRoom first; - Add(new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Y, - Width = 580f, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - first = new DrawableRoom(new Room - { - Name = { Value = @"Great Room Right Here" }, - Host = { Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" } } }, - Status = { Value = new RoomStatusOpen() }, - Type = { Value = new GameTypeTeamVersus() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 4.65, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata - { - Title = @"Critical Crystal", - Artist = @"Seiryu", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh//beatmaps/376340/covers/cover.jpg?1456478455", - }, - }, - }, - }, - }, - Participants = - { - Value = new[] - { - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1355 } } }, - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 8756 } } }, - }, - }, - }), - new DrawableRoom(new Room - { - Name = { Value = @"Relax It's The Weekend" }, - Host = { Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" } } }, - Status = { Value = new RoomStatusPlaying() }, - Type = { Value = new GameTypeTagTeam() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 1.96, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata - { - Title = @"Serendipity", - Artist = @"ZAQ", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh//beatmaps/526839/covers/cover.jpg?1493815706", - }, - }, - }, - }, - }, - Participants = - { - Value = new[] - { - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 578975 } } }, - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24554 } } }, - }, - }, - }), - } - }); - - AddStep(@"select", () => first.State = SelectionState.Selected); - AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name"); - AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } }); - AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying()); - AddStep(@"change type", () => first.Room.Type.Value = new GameTypeVersus()); - AddStep(@"change beatmap", () => first.Room.Beatmap.Value = null); - AddStep(@"change participants", () => first.Room.Participants.Value = new[] - { - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } }, - new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } }, - }); - AddStep(@"deselect", () => first.State = SelectionState.NotSelected); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseDrawings.cs b/osu.Game.Tests/Visual/TestCaseDrawings.cs deleted file mode 100644 index a6a3ef6747..0000000000 --- a/osu.Game.Tests/Visual/TestCaseDrawings.cs +++ /dev/null @@ -1,83 +0,0 @@ -// 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.ComponentModel; -using osu.Game.Screens.Tournament; -using osu.Game.Screens.Tournament.Teams; - -namespace osu.Game.Tests.Visual -{ - [Description("for tournament use")] - public class TestCaseDrawings : OsuTestCase - { - public TestCaseDrawings() - { - Add(new Drawings - { - TeamList = new TestTeamList(), - }); - } - - private class TestTeamList : ITeamList - { - public IEnumerable Teams { get; } = new[] - { - new DrawingsTeam - { - FlagName = "GB", - FullName = "United Kingdom", - Acronym = "UK" - }, - new DrawingsTeam - { - FlagName = "FR", - FullName = "France", - Acronym = "FRA" - }, - new DrawingsTeam - { - FlagName = "CN", - FullName = "China", - Acronym = "CHN" - }, - new DrawingsTeam - { - FlagName = "AU", - FullName = "Australia", - Acronym = "AUS" - }, - new DrawingsTeam - { - FlagName = "JP", - FullName = "Japan", - Acronym = "JPN" - }, - new DrawingsTeam - { - FlagName = "RO", - FullName = "Romania", - Acronym = "ROM" - }, - new DrawingsTeam - { - FlagName = "IT", - FullName = "Italy", - Acronym = "PIZZA" - }, - new DrawingsTeam - { - FlagName = "VE", - FullName = "Venezuela", - Acronym = "VNZ" - }, - new DrawingsTeam - { - FlagName = "US", - FullName = "United States of America", - Acronym = "USA" - }, - }; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs b/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs deleted file mode 100644 index fe88b46414..0000000000 --- a/osu.Game.Tests/Visual/TestCaseKeyConfiguration.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseKeyConfiguration : OsuTestCase - { - private readonly KeyBindingOverlay overlay; - - public TestCaseKeyConfiguration() - { - Child = overlay = new KeyBindingOverlay(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - overlay.Show(); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs b/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs deleted file mode 100644 index da2427ff6f..0000000000 --- a/osu.Game.Tests/Visual/TestCaseLoaderAnimation.cs +++ /dev/null @@ -1,115 +0,0 @@ -// 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 osuTK.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 deleted file mode 100644 index e0e6332ef0..0000000000 --- a/osu.Game.Tests/Visual/TestCaseLounge.cs +++ /dev/null @@ -1,216 +0,0 @@ -// 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; -using osu.Game.Screens.Backgrounds; -using osu.Game.Screens.Multi.Components; -using osu.Game.Screens.Multi.Screens.Lounge; -using osu.Game.Users; -using osuTK.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)); - AddUntilStep(() => lounge.ChildScreen?.IsCurrentScreen == true, "wait until room current"); - 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 - { - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); - - 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/TestCaseMatch.cs b/osu.Game.Tests/Visual/TestCaseMatch.cs deleted file mode 100644 index bb22358425..0000000000 --- a/osu.Game.Tests/Visual/TestCaseMatch.cs +++ /dev/null @@ -1,142 +0,0 @@ -// 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.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; -using osu.Game.Screens.Multi.Screens.Match; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMatch : OsuTestCase - { - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Room room = new Room - { - Name = { Value = @"One Awesome Room" }, - Status = { Value = new RoomStatusOpen() }, - Availability = { Value = RoomAvailability.Public }, - Type = { Value = new GameTypeTeamVersus() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 5.02, - Ruleset = rulesets.GetRuleset(1), - Metadata = new BeatmapMetadata - { - Title = @"Paradigm Shift", - Artist = @"Morimori Atsushi", - AuthorString = @"eiri-", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/765055/covers/cover.jpg?1526955337", - }, - }, - }, - }, - }, - MaxParticipants = { Value = 5 }, - Participants = - { - Value = new[] - { - new User - { - Username = @"eiri-", - Id = 3388410, - Country = new Country { FlagName = @"US" }, - CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3388410/00a8486a247831e1cc4375db519f611ac970bda8bc0057d78b0f540ea38c3e58.jpeg", - IsSupporter = true, - }, - new User - { - Username = @"Nepuri", - Id = 6637817, - Country = new Country { FlagName = @"DE" }, - CoverUrl = @"https://assets.ppy.sh/user-profile-covers/6637817/9085fc60248b6b5327a72c1dcdecf2dbedba810ae0ab6bcf7224e46b1339632a.jpeg", - IsSupporter = true, - }, - new User - { - Username = @"goheegy", - Id = 8057655, - Country = new Country { FlagName = @"GB" }, - CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8057655/21cec27c25a11dc197a4ec6a74253dbabb495949b0e0697113352f12007018c5.jpeg", - }, - new User - { - Username = @"Alumetri", - Id = 5371497, - Country = new Country { FlagName = @"RU" }, - CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5371497/e023b8c7fbe3613e64bd4856703517ea50fbed8a5805dc9acda9efe9897c67e2.jpeg", - }, - } - }, - }; - - Match match = new Match(room); - - AddStep(@"show", () => Add(match)); - AddStep(@"null beatmap", () => room.Beatmap.Value = null); - AddStep(@"change name", () => room.Name.Value = @"Two Awesome Rooms"); - AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying()); - AddStep(@"change availability", () => room.Availability.Value = RoomAvailability.FriendsOnly); - AddStep(@"change type", () => room.Type.Value = new GameTypeTag()); - AddStep(@"change beatmap", () => room.Beatmap.Value = new BeatmapInfo - { - StarDifficulty = 4.33, - Ruleset = rulesets.GetRuleset(2), - Metadata = new BeatmapMetadata - { - Title = @"Yasashisa no Riyuu", - Artist = @"ChouCho", - AuthorString = @"celerih", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/685391/covers/cover.jpg?1524597970", - }, - }, - }, - }); - - AddStep(@"null max participants", () => room.MaxParticipants.Value = null); - AddStep(@"change participants", () => room.Participants.Value = new[] - { - new User - { - Username = @"Spectator", - Id = 702598, - Country = new Country { FlagName = @"KR" }, - CoverUrl = @"https://assets.ppy.sh/user-profile-covers/702598/3bbf4cb8b8d2cf8b03145000a975ff27e191ab99b0920832e7dd67386280e288.jpeg", - IsSupporter = true, - }, - new User - { - Username = @"celerih", - Id = 4696296, - Country = new Country { FlagName = @"CA" }, - CoverUrl = @"https://assets.ppy.sh/user-profile-covers/4696296/7f8500731d0ac66d5472569d146a7be07d9460273361913f22c038867baddaef.jpeg", - }, - }); - - AddStep(@"exit", match.Exit); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs b/osu.Game.Tests/Visual/TestCaseMatchHeader.cs deleted file mode 100644 index 34f98f97c2..0000000000 --- a/osu.Game.Tests/Visual/TestCaseMatchHeader.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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.Game.Beatmaps; -using osu.Game.Screens.Multi.Screens.Match; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMatchHeader : OsuTestCase - { - public TestCaseMatchHeader() - { - Header header = new Header(); - Add(header); - - AddStep(@"set beatmap set", () => header.BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/760757/covers/cover.jpg?1526944540", - }, - }, - }); - - AddStep(@"change beatmap set", () => header.BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/761883/covers/cover.jpg?1525557400", - }, - }, - }); - - AddStep(@"null beatmap set", () => header.BeatmapSet = null); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs b/osu.Game.Tests/Visual/TestCaseMatchInfo.cs deleted file mode 100644 index 205da6932f..0000000000 --- a/osu.Game.Tests/Visual/TestCaseMatchInfo.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; -using osu.Game.Screens.Multi.Screens.Match; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMatchInfo : OsuTestCase - { - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Info info = new Info(); - Add(info); - - AddStep(@"set name", () => info.Name = @"Room Name?"); - AddStep(@"set availability", () => info.Availability = RoomAvailability.FriendsOnly); - AddStep(@"set status", () => info.Status = new RoomStatusPlaying()); - AddStep(@"set beatmap", () => info.Beatmap = new BeatmapInfo - { - StarDifficulty = 2.4, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata - { - Title = @"My Song", - Artist = @"VisualTests", - AuthorString = @"osu!lazer", - }, - }); - - AddStep(@"set type", () => info.Type = new GameTypeTagTeam()); - - AddStep(@"change name", () => info.Name = @"Room Name!"); - AddStep(@"change availability", () => info.Availability = RoomAvailability.InviteOnly); - AddStep(@"change status", () => info.Status = new RoomStatusOpen()); - AddStep(@"null beatmap", () => info.Beatmap = null); - AddStep(@"change type", () => info.Type = new GameTypeTeamVersus()); - AddStep(@"change beatmap", () => info.Beatmap = new BeatmapInfo - { - StarDifficulty = 4.2, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata - { - Title = @"Your Song", - Artist = @"Tester", - AuthorString = @"Someone", - }, - }); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs b/osu.Game.Tests/Visual/TestCaseMultiHeader.cs deleted file mode 100644 index d27a447077..0000000000 --- a/osu.Game.Tests/Visual/TestCaseMultiHeader.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.Game.Screens.Multi; -using osu.Game.Screens.Multi.Screens.Lounge; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMultiHeader : OsuTestCase - { - public TestCaseMultiHeader() - { - Lounge lounge; - Children = new Drawable[] - { - lounge = new Lounge - { - Padding = new MarginPadding { Top = Header.HEIGHT }, - }, - new Header(lounge), - }; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs b/osu.Game.Tests/Visual/TestCaseMultiScreen.cs deleted file mode 100644 index 6c22fb020f..0000000000 --- a/osu.Game.Tests/Visual/TestCaseMultiScreen.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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.Game.Screens.Multi; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMultiScreen : OsuTestCase - { - public TestCaseMultiScreen() - { - Multiplayer multi = new Multiplayer(); - - AddStep(@"show", () => Add(multi)); - AddWaitStep(5); - AddStep(@"exit", multi.Exit); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseMusicController.cs b/osu.Game.Tests/Visual/TestCaseMusicController.cs deleted file mode 100644 index 5ba0167f12..0000000000 --- a/osu.Game.Tests/Visual/TestCaseMusicController.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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.Containers; -using osu.Framework.Timing; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseMusicController : OsuTestCase - { - public TestCaseMusicController() - { - Clock = new FramedClock(); - - var mc = new MusicController - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre - }; - Add(mc); - - AddToggleStep(@"toggle visibility", state => mc.State = state ? Visibility.Visible : Visibility.Hidden); - AddStep(@"show", () => mc.State = Visibility.Visible); - AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs deleted file mode 100644 index 2c56f08f42..0000000000 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ /dev/null @@ -1,166 +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.Linq; -using NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.MathUtils; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseNotificationOverlay : OsuTestCase - { - private readonly NotificationOverlay manager; - private readonly List progressingNotifications = new List(); - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(NotificationSection), - typeof(SimpleNotification), - typeof(ProgressNotification), - typeof(ProgressCompletionNotification), - typeof(IHasCompletionTarget), - typeof(Notification) - }; - - public TestCaseNotificationOverlay() - { - progressingNotifications.Clear(); - - Content.Add(manager = new NotificationOverlay - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight - }); - - SpriteText displayedCount = new SpriteText(); - - Content.Add(displayedCount); - - void setState(Visibility state) => AddStep(state.ToString(), () => manager.State = state); - void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected); - - manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; }; - - - setState(Visibility.Visible); - AddStep(@"simple #1", sendHelloNotification); - AddStep(@"simple #2", sendAmazingNotification); - AddStep(@"progress #1", sendUploadProgress); - AddStep(@"progress #2", sendDownloadProgress); - - checkProgressingCount(2); - - setState(Visibility.Hidden); - - AddRepeatStep(@"add many simple", sendManyNotifications, 3); - AddWaitStep(5); - - checkProgressingCount(0); - - AddStep(@"progress #3", sendUploadProgress); - - checkProgressingCount(1); - - AddAssert("Displayed count is 33", () => manager.UnreadCount.Value == 33); - - AddWaitStep(10); - - checkProgressingCount(0); - - - setState(Visibility.Visible); - - //AddStep(@"barrage", () => sendBarrage()); - } - - private void sendBarrage(int remaining = 10) - { - switch (RNG.Next(0, 4)) - { - case 0: - sendHelloNotification(); - break; - case 1: - sendAmazingNotification(); - break; - case 2: - sendUploadProgress(); - break; - case 3: - sendDownloadProgress(); - break; - } - - if (remaining > 0) - Scheduler.AddDelayed(() => sendBarrage(remaining - 1), 80); - } - - protected override void Update() - { - base.Update(); - - progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed); - - if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) - { - var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued); - if (p != null) - p.State = ProgressNotificationState.Active; - } - - foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) - { - if (n.Progress < 1) - n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle(); - else - n.State = ProgressNotificationState.Completed; - } - } - - private void sendDownloadProgress() - { - var n = new ProgressNotification - { - Text = @"Downloading Haitai...", - CompletionText = "Downloaded Haitai!", - }; - manager.Post(n); - progressingNotifications.Add(n); - } - - private void sendUploadProgress() - { - var n = new ProgressNotification - { - Text = @"Uploading to BSS...", - CompletionText = "Uploaded to BSS!", - }; - manager.Post(n); - progressingNotifications.Add(n); - } - - private void sendAmazingNotification() - { - manager.Post(new SimpleNotification { Text = @"You are amazing" }); - } - - private void sendHelloNotification() - { - manager.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); - } - - private void sendManyNotifications() - { - for (int i = 0; i < 10; i++) - manager.Post(new SimpleNotification { Text = @"Spam incoming!!" }); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseOsuGame.cs b/osu.Game.Tests/Visual/TestCaseOsuGame.cs deleted file mode 100644 index 924a2b5e15..0000000000 --- a/osu.Game.Tests/Visual/TestCaseOsuGame.cs +++ /dev/null @@ -1,36 +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 NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Screens; -using osu.Game.Screens.Menu; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseOsuGame : OsuTestCase - { - public override IReadOnlyList RequiredTypes => new[] - { - typeof(OsuLogo), - }; - - public TestCaseOsuGame() - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - new Loader() - }; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs b/osu.Game.Tests/Visual/TestCasePlayerLoader.cs deleted file mode 100644 index de839a21af..0000000000 --- a/osu.Game.Tests/Visual/TestCasePlayerLoader.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Threading; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - public class TestCasePlayerLoader : ManualInputManagerTestCase - { - private PlayerLoader loader; - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - Beatmap.Value = new DummyWorkingBeatmap(game); - - AddStep("load dummy beatmap", () => Add(loader = new PlayerLoader(new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }))); - - AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); - - AddUntilStep(() => !loader.IsCurrentScreen, "wait for no longer current"); - - AddStep("load slow dummy beatmap", () => - { - SlowLoadPlayer slow; - - Add(loader = new PlayerLoader(slow = new SlowLoadPlayer - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - })); - - Scheduler.AddDelayed(() => slow.Ready = true, 5000); - }); - - AddUntilStep(() => !loader.IsCurrentScreen, "wait for no longer current"); - } - - protected class SlowLoadPlayer : Player - { - public bool Ready; - - [BackgroundDependencyLoader] - private void load() - { - while (!Ready) - Thread.Sleep(1); - } - } - } - -} diff --git a/osu.Game.Tests/Visual/TestCaseReplay.cs b/osu.Game.Tests/Visual/TestCaseReplay.cs deleted file mode 100644 index 1f2d99a7d8..0000000000 --- a/osu.Game.Tests/Visual/TestCaseReplay.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using System.Linq; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [Description("Player instantiated with a replay.")] - public class TestCaseReplay : TestCasePlayer - { - 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.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.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); - - return new ReplayPlayer(replay); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs b/osu.Game.Tests/Visual/TestCaseRoomInspector.cs deleted file mode 100644 index 06b9c4a6f9..0000000000 --- a/osu.Game.Tests/Visual/TestCaseRoomInspector.cs +++ /dev/null @@ -1,147 +0,0 @@ -// 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.Allocation; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; -using osu.Game.Screens.Multi.Components; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseRoomInspector : OsuTestCase - { - private RulesetStore rulesets; - - protected override void LoadComplete() - { - base.LoadComplete(); - - Room room = new Room - { - Name = { Value = @"My Awesome Room" }, - Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } }, - Status = { Value = new RoomStatusOpen() }, - Type = { Value = new GameTypeTeamVersus() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 3.7, - Ruleset = rulesets.GetRuleset(3), - Metadata = new BeatmapMetadata - { - Title = @"Platina", - Artist = @"Maaya Sakamoto", - AuthorString = @"uwutm8", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/560573/covers/cover.jpg?1492722343", - }, - }, - }, - } - }, - MaxParticipants = { Value = 200 }, - 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 } } }, - } - } - }; - - RoomInspector inspector; - Add(inspector = new RoomInspector - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - }); - - AddStep(@"set room", () => inspector.Room = room); - AddStep(@"null room", () => inspector.Room = null); - AddStep(@"set room", () => inspector.Room = room); - AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above"); - AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } }); - AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying()); - AddStep(@"change type", () => room.Type.Value = new GameTypeTag()); - AddStep(@"change beatmap", () => room.Beatmap.Value = null); - AddStep(@"change max participants", () => room.MaxParticipants.Value = null); - AddStep(@"change participants", () => room.Participants.Value = new[] - { - new User { Username = @"filsdelama", Id = 2831793, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 854 } } }, - new User { Username = @"_index", Id = 652457, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 150 } } } - }); - - AddStep(@"change room", () => - { - Room newRoom = new Room - { - Name = { Value = @"My New, Better Than Ever Room" }, - Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } }, - Status = { Value = new RoomStatusOpen() }, - Type = { Value = new GameTypeTagTeam() }, - Beatmap = - { - Value = new BeatmapInfo - { - StarDifficulty = 7.07, - Ruleset = rulesets.GetRuleset(0), - Metadata = new BeatmapMetadata - { - Title = @"FREEDOM DIVE", - Artist = @"xi", - AuthorString = @"Nakagawa-Kanon", - }, - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new BeatmapSetOnlineInfo - { - Covers = new BeatmapSetOnlineCovers - { - Cover = @"https://assets.ppy.sh/beatmaps/39804/covers/cover.jpg?1456506845", - }, - }, - }, - }, - }, - MaxParticipants = { Value = 10 }, - Participants = - { - Value = new[] - { - new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 4 } } }, - new User { Username = @"HappyStick", Id = 256802, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 752 } } }, - new User { Username = @"-Konpaku-", Id = 2258797, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 571 } } } - } - } - }; - - inspector.Room = newRoom; - }); - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - this.rulesets = rulesets; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseSettings.cs b/osu.Game.Tests/Visual/TestCaseSettings.cs deleted file mode 100644 index c942342168..0000000000 --- a/osu.Game.Tests/Visual/TestCaseSettings.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseSettings : OsuTestCase - { - private readonly SettingsOverlay settings; - private readonly DialogOverlay dialogOverlay; - - public TestCaseSettings() - { - settings = new MainSettings - { - State = Visibility.Visible - }; - Add(dialogOverlay = new DialogOverlay - { - Depth = -1 - }); - } - - [BackgroundDependencyLoader] - private void load() - { - Dependencies.Cache(dialogOverlay); - - Add(settings); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseSongProgress.cs b/osu.Game.Tests/Visual/TestCaseSongProgress.cs deleted file mode 100644 index 1eb40a9486..0000000000 --- a/osu.Game.Tests/Visual/TestCaseSongProgress.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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 NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.MathUtils; -using osu.Framework.Timing; -using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Play; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseSongProgress : OsuTestCase - { - private readonly SongProgress progress; - private readonly SongProgressGraph graph; - - private readonly StopwatchClock clock; - - public TestCaseSongProgress() - { - clock = new StopwatchClock(true); - - Add(progress = new SongProgress - { - RelativeSizeAxes = Axes.X, - AudioClock = new StopwatchClock(true), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }); - - Add(graph = new SongProgressGraph - { - RelativeSizeAxes = Axes.X, - Height = 200, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }); - - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(5); - AddStep("Toggle Bar", () => progress.AllowSeeking = !progress.AllowSeeking); - AddWaitStep(2); - AddRepeatStep("New Values", displayNewValues, 5); - - displayNewValues(); - } - - private void displayNewValues() - { - List objects = new List(); - for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) - objects.Add(new HitObject { StartTime = i }); - - progress.Objects = objects; - graph.Objects = objects; - - progress.AudioClock = clock; - progress.OnSeek = pos => clock.Seek(pos); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs b/osu.Game.Tests/Visual/TestCaseTextAwesome.cs deleted file mode 100644 index 50ebd3a6e5..0000000000 --- a/osu.Game.Tests/Visual/TestCaseTextAwesome.cs +++ /dev/null @@ -1,55 +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 NUnit.Framework; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseTextAwesome : OsuTestCase - { - public TestCaseTextAwesome() - { - FillFlowContainer flow; - - Add(new ScrollContainer - { - RelativeSizeAxes = Axes.Both, - Child = flow = new FillFlowContainer - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - }, - }); - - foreach (FontAwesome fa in Enum.GetValues(typeof(FontAwesome))) - flow.Add(new Icon(fa)); - } - - private class Icon : Container, IHasTooltip - { - public string TooltipText { get; } - - public Icon(FontAwesome fa) - { - TooltipText = fa.ToString(); - - AutoSizeAxes = Axes.Both; - Child = new SpriteIcon - { - Icon = fa, - Size = new Vector2(60), - }; - } - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs b/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs deleted file mode 100644 index 8079566947..0000000000 --- a/osu.Game.Tests/Visual/TestCaseTwoLayerButton.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.ComponentModel; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Tests.Visual -{ - [Description("mostly back button")] - public class TestCaseTwoLayerButton : OsuTestCase - { - public TestCaseTwoLayerButton() - { - Add(new BackButton()); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseUserPanel.cs b/osu.Game.Tests/Visual/TestCaseUserPanel.cs deleted file mode 100644 index a53af247f3..0000000000 --- a/osu.Game.Tests/Visual/TestCaseUserPanel.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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.Containers; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseUserPanel : OsuTestCase - { - public TestCaseUserPanel() - { - UserPanel flyte; - UserPanel peppy; - Add(new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(10f), - Children = new[] - { - flyte = new UserPanel(new User - { - Username = @"flyte", - Id = 3103765, - Country = new Country { FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - }) { Width = 300 }, - peppy = new UserPanel(new User - { - Username = @"peppy", - Id = 2, - Country = new Country { FlagName = @"AU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - IsSupporter = true, - }) { Width = 300 }, - }, - }); - - flyte.Status.Value = new UserStatusOnline(); - peppy.Status.Value = new UserStatusSoloGame(); - - AddStep(@"spectating", () => { flyte.Status.Value = new UserStatusSpectating(); }); - AddStep(@"multiplaying", () => { flyte.Status.Value = new UserStatusMultiplayerGame(); }); - AddStep(@"modding", () => { flyte.Status.Value = new UserStatusModding(); }); - AddStep(@"offline", () => { flyte.Status.Value = new UserStatusOffline(); }); - AddStep(@"null status", () => { flyte.Status.Value = null; }); - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseUserProfile.cs b/osu.Game.Tests/Visual/TestCaseUserProfile.cs deleted file mode 100644 index cb281d045b..0000000000 --- a/osu.Game.Tests/Visual/TestCaseUserProfile.cs +++ /dev/null @@ -1,123 +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.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Overlays; -using osu.Game.Overlays.Profile; -using osu.Game.Overlays.Profile.Header; -using osu.Game.Users; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseUserProfile : OsuTestCase - { - private readonly TestUserProfileOverlay profile; - private APIAccess api; - - public override IReadOnlyList RequiredTypes => new[] - { - typeof(ProfileHeader), - typeof(UserProfileOverlay), - typeof(RankGraph), - typeof(LineGraph), - typeof(BadgeContainer) - }; - - public TestCaseUserProfile() - { - Add(profile = new TestUserProfileOverlay()); - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - AddStep("Show offline dummy", () => profile.ShowUser(new User - { - Username = @"Somebody", - Id = 1, - Country = new Country { FullName = @"Alien" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - JoinDate = DateTimeOffset.Now.AddDays(-1), - LastVisit = DateTimeOffset.Now, - ProfileOrder = new[] { "me" }, - Statistics = new UserStatistics - { - Ranks = new UserStatistics.UserRanks { Global = 2148, Country = 1 }, - PP = 4567.89m, - }, - RankHistory = new User.RankHistoryData - { - Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() - }, - Badges = new[] - { - new Badge - { - AwardedAt = DateTimeOffset.FromUnixTimeSeconds(1505741569), - Description = "Outstanding help by being a voluntary test subject.", - ImageUrl = "https://assets.ppy.sh/profile-badges/contributor.jpg" - } - } - }, false)); - - checkSupporterTag(false); - - AddStep("Show null dummy", () => profile.ShowUser(new User - { - Username = @"Null", - Id = 1, - }, false)); - - AddStep("Show ppy", () => profile.ShowUser(new User - { - Username = @"peppy", - Id = 2, - IsSupporter = true, - Country = new Country { FullName = @"Australia", FlagName = @"AU" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" - }, api.IsLoggedIn)); - - checkSupporterTag(true); - - AddStep("Show flyte", () => profile.ShowUser(new User - { - Username = @"flyte", - Id = 3103765, - Country = new Country { FullName = @"Japan", FlagName = @"JP" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" - }, api.IsLoggedIn)); - - AddStep("Hide", profile.Hide); - AddStep("Show without reload", profile.Show); - } - - private void checkSupporterTag(bool isSupporter) - { - AddUntilStep(() => profile.Header.User != null, "wait for load"); - if (isSupporter) - AddAssert("is supporter", () => profile.Header.SupporterTag.Alpha == 1); - else - AddAssert("no supporter", () => profile.Header.SupporterTag.Alpha == 0); - } - - private class TestUserProfileOverlay : UserProfileOverlay - { - public new ProfileHeader Header => base.Header; - } - } -} diff --git a/osu.Game.Tests/Visual/TestCaseWaveform.cs b/osu.Game.Tests/Visual/TestCaseWaveform.cs deleted file mode 100644 index f97091e58d..0000000000 --- a/osu.Game.Tests/Visual/TestCaseWaveform.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using NUnit.Framework; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Audio; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Tests.Visual -{ - [TestFixture] - public class TestCaseWaveform : OsuTestCase - { - [BackgroundDependencyLoader] - private void load() - { - Beatmap.Value = new WaveformTestBeatmap(); - - FillFlowContainer flow; - Child = flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - }; - - for (int i = 1; i <= 16; i *= 2) - { - var newDisplay = new WaveformGraph - { - RelativeSizeAxes = Axes.Both, - Resolution = 1f / i, - Waveform = Beatmap.Value.Waveform, - }; - - flow.Add(new Container - { - RelativeSizeAxes = Axes.X, - Height = 100, - Children = new Drawable[] - { - newDisplay, - new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.75f - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = $"Resolution: {1f / i:0.00}" - } - } - } - } - }); - } - } - } -} diff --git a/osu.Game.Tests/Visual/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/TestSceneOsuGame.cs new file mode 100644 index 0000000000..fcc3a3596f --- /dev/null +++ b/osu.Game.Tests/Visual/TestSceneOsuGame.cs @@ -0,0 +1,127 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Framework.Platform; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Input; +using osu.Game.Input.Bindings; +using osu.Game.IO; +using osu.Game.Online.API; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; +using osu.Game.Screens.Menu; +using osu.Game.Skinning; +using osu.Game.Utils; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestSceneOsuGame : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuLogo), + }; + + private IReadOnlyList requiredGameDependencies => new[] + { + typeof(OsuGame), + typeof(RavenLogger), + typeof(OsuLogo), + typeof(IdleTracker), + typeof(OnScreenDisplay), + typeof(NotificationOverlay), + typeof(DirectOverlay), + typeof(SocialOverlay), + typeof(ChannelManager), + typeof(ChatOverlay), + typeof(SettingsOverlay), + typeof(UserProfileOverlay), + typeof(BeatmapSetOverlay), + typeof(LoginOverlay), + typeof(MusicController), + typeof(AccountCreationOverlay), + typeof(DialogOverlay), + typeof(ScreenshotManager) + }; + + private IReadOnlyList requiredGameBaseDependencies => new[] + { + typeof(OsuGameBase), + typeof(DatabaseContextFactory), + typeof(Bindable), + typeof(IBindable), + typeof(Bindable>), + typeof(IBindable>), + typeof(LargeTextureStore), + typeof(OsuConfigManager), + typeof(SkinManager), + typeof(ISkinSource), + typeof(IAPIProvider), + typeof(RulesetStore), + typeof(FileStore), + typeof(ScoreManager), + typeof(BeatmapManager), + typeof(KeyBindingStore), + typeof(SettingsStore), + typeof(RulesetConfigCache), + typeof(OsuColour), + typeof(IBindable), + typeof(Bindable), + typeof(GlobalActionContainer), + typeof(PreviewTrackManager), + }; + + [BackgroundDependencyLoader] + private void load(GameHost host, OsuGameBase gameBase) + { + OsuGame game = new OsuGame(); + game.SetHost(host); + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + game + }; + + AddUntilStep("wait for load", () => game.IsLoaded); + + AddAssert("check OsuGame DI members", () => + { + foreach (var type in requiredGameDependencies) + if (game.Dependencies.Get(type) == null) + throw new Exception($"{type} has not been cached"); + + return true; + }); + AddAssert("check OsuGameBase DI members", () => + { + foreach (var type in requiredGameBaseDependencies) + if (gameBase.Dependencies.Get(type) == null) + throw new Exception($"{type} has not been cached"); + + return true; + }); + } + } +} diff --git a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs new file mode 100644 index 0000000000..a68fd0ef40 --- /dev/null +++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs @@ -0,0 +1,82 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens; +using osu.Game.Screens.Play; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestSceneOsuScreenStack : OsuTestScene + { + private TestOsuScreenStack stack; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create new screen stack", () => { Child = stack = new TestOsuScreenStack { RelativeSizeAxes = Axes.Both }; }); + } + + [Test] + public void ParallaxAssignmentTest() + { + NoParallaxTestScreen noParallaxScreen = null; + TestScreen parallaxScreen = null; + + AddStep("Push no parallax", () => stack.Push(noParallaxScreen = new NoParallaxTestScreen("NO PARALLAX"))); + AddUntilStep("Wait for current", () => noParallaxScreen.IsLoaded); + AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); + + AddStep("Push parallax", () => noParallaxScreen.Push(parallaxScreen = new TestScreen("PARALLAX"))); + AddUntilStep("Wait for current", () => parallaxScreen.IsLoaded); + AddAssert("Parallax is on", () => stack.ParallaxAmount > 0); + + AddStep("Exit from new screen", () => { noParallaxScreen.MakeCurrent(); }); + AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); + } + + private class TestScreen : ScreenWithBeatmapBackground + { + private readonly string screenText; + + public TestScreen(string screenText) + { + this.screenText = screenText; + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new OsuSpriteText + { + Text = screenText, + Colour = Color4.White, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + } + + private class NoParallaxTestScreen : TestScreen + { + public NoParallaxTestScreen(string screenText) + : base(screenText) + { + } + + public override float BackgroundParallaxAmount => 0.0f; + } + + private class TestOsuScreenStack : OsuScreenStack + { + public new float ParallaxAmount => base.ParallaxAmount; + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs b/osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs new file mode 100644 index 0000000000..096ac951de --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/OsuGridTestScene.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Tests.Visual.UserInterface +{ + /// + /// An abstract test case which exposes small cells arranged in a grid. + /// Useful for displaying multiple configurations of a tested component at a glance. + /// + public abstract class OsuGridTestScene : OsuTestScene + { + private readonly Drawable[,] cells; + + /// + /// The amount of rows in the grid. + /// + protected readonly int Rows; + + /// + /// The amount of columns in the grid. + /// + protected readonly int Cols; + + /// + /// Constructs a grid test case with the given dimensions. + /// + protected OsuGridTestScene(int rows, int cols) + { + Rows = rows; + Cols = cols; + + GridContainer testContainer; + Add(testContainer = new GridContainer { RelativeSizeAxes = Axes.Both }); + + cells = new Drawable[rows, cols]; + for (int r = 0; r < rows; r++) + for (int c = 0; c < cols; c++) + cells[r, c] = new Container { RelativeSizeAxes = Axes.Both }; + + testContainer.Content = cells.ToJagged(); + } + + protected Container Cell(int index) => (Container)cells[index / Cols, index % Cols]; + protected Container Cell(int row, int col) => (Container)cells[row, col]; + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs new file mode 100644 index 0000000000..867b3130c9 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBackButton.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneBackButton : OsuTestScene + { + private readonly BackButton button; + + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TwoLayerButton) + }; + + public TestSceneBackButton() + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300), + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + button = new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = () => button.Hide(), + } + } + }; + + AddStep("show button", () => button.Show()); + AddStep("hide button", () => button.Hide()); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs similarity index 92% rename from osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index ff383b6723..28f0cc027e 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using NUnit.Framework; @@ -8,22 +8,23 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osuTK.Graphics; -using osu.Framework.Lists; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseBeatSyncedContainer : OsuTestCase + public class TestSceneBeatSyncedContainer : OsuTestScene { private readonly MusicController mc; - public TestCaseBeatSyncedContainer() + public TestSceneBeatSyncedContainer() { Clock = new FramedClock(); Clock.ProcessFrame(); @@ -140,6 +141,7 @@ namespace osu.Game.Tests.Visual } private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { if (timingPoints[timingPoints.Count - 1] == current) @@ -189,15 +191,15 @@ namespace osu.Game.Tests.Visual public double Value { - set { valueText.Text = $"{value:G}"; } + set => valueText.Text = $"{value:G}"; } public InfoString(string header) { AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; - Add(new OsuSpriteText { Text = header + @": ", TextSize = text_size }); - Add(valueText = new OsuSpriteText { TextSize = text_size }); + Add(new OsuSpriteText { Text = header + @": ", Font = OsuFont.GetFont(size: text_size) }); + Add(valueText = new OsuSpriteText { Font = OsuFont.GetFont(size: text_size) }); Margin = new MarginPadding(margin); } } diff --git a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs similarity index 78% rename from osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs index 73a97c6269..554696765e 100644 --- a/osu.Game.Tests/Visual/TestCaseBreadcrumbs.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBreadcrumbs.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; @@ -7,16 +7,15 @@ using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseBreadcrumbs : OsuTestCase + public class TestSceneBreadcrumbs : OsuTestScene { private readonly BreadcrumbControl breadcrumbs; - public TestCaseBreadcrumbs() + public TestSceneBreadcrumbs() { - Add(breadcrumbs = new BreadcrumbControl { Anchor = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs similarity index 74% rename from osu.Game.Tests/Visual/TestCaseButtonSystem.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs index 0506783310..c8cc864089 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonSystem.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -11,10 +11,10 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Screens.Menu; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseButtonSystem : OsuTestCase + public class TestSceneButtonSystem : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual typeof(Button) }; - public TestCaseButtonSystem() + public TestSceneButtonSystem() { OsuLogo logo; ButtonSystem buttons; @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual RelativeSizeAxes = Axes.Both, }, buttons = new ButtonSystem(), - logo = new OsuLogo() + logo = new OsuLogo { RelativePositionAxes = Axes.Both } }; buttons.SetOsuLogo(logo); diff --git a/osu.Game.Tests/Visual/TestCaseContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs similarity index 84% rename from osu.Game.Tests/Visual/TestCaseContextMenu.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs index a0d0eaf015..53693d1b70 100644 --- a/osu.Game.Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Graphics; @@ -7,22 +7,22 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; -using osu.Game.Graphics.Cursor; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseContextMenu : OsuTestCase + public class TestSceneContextMenu : OsuTestScene { private const int start_time = 0; private const int duration = 1000; private readonly Container container; - public TestCaseContextMenu() + public TestSceneContextMenu() { Add(new OsuContextMenuContainer { @@ -61,10 +61,10 @@ namespace osu.Game.Tests.Visual // Move box along a square trajectory container.Loop(c => c - .MoveTo(new Vector2(0, 100), duration).Then() - .MoveTo(new Vector2(100, 100), duration).Then() - .MoveTo(new Vector2(100, 0), duration).Then() - .MoveTo(Vector2.Zero, duration) + .MoveTo(new Vector2(0, 100), duration).Then() + .MoveTo(new Vector2(100, 100), duration).Then() + .MoveTo(new Vector2(100, 0), duration).Then() + .MoveTo(Vector2.Zero, duration) ); } diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs similarity index 95% rename from osu.Game.Tests/Visual/TestCaseCursors.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs index 293ca392ab..23d9112b25 100644 --- a/osu.Game.Tests/Visual/TestCaseCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Extensions.IEnumerableExtensions; @@ -14,15 +14,15 @@ using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseCursors : ManualInputManagerTestCase + public class TestSceneCursors : ManualInputManagerTestScene { private readonly MenuCursorContainer menuCursorContainer; private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; - public TestCaseCursors() + public TestSceneCursors() { Child = menuCursorContainer = new MenuCursorContainer { @@ -84,7 +84,6 @@ namespace osu.Game.Tests.Visual testLocalCursor(); testUserCursorOverride(); testMultipleLocalCursors(); - ReturnUserInput(); } /// @@ -177,7 +176,7 @@ namespace osu.Game.Tests.Visual /// Checks if a cursor is visible. /// /// The cursor to check. - private bool checkVisible(CursorContainer cursorContainer) => cursorContainer.State == Visibility.Visible; + private bool checkVisible(CursorContainer cursorContainer) => cursorContainer.State.Value == Visibility.Visible; /// /// Checks if a cursor is at the current inputmanager screen position. @@ -193,7 +192,7 @@ namespace osu.Game.Tests.Visual public CursorContainer Cursor { get; } public bool ProvidingUserCursor { get; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || SmoothTransition && !ProvidingUserCursor; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor); private readonly Box background; @@ -219,7 +218,7 @@ namespace osu.Game.Tests.Visual }, Cursor = new TestCursorContainer { - State = providesUserCursor ? Visibility.Hidden : Visibility.Visible, + State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible }, } }; } diff --git a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs similarity index 83% rename from osu.Game.Tests/Visual/TestCaseDialogOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index f0907ed28c..a6ff3462d4 100644 --- a/osu.Game.Tests/Visual/TestCaseDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseDialogOverlay : OsuTestCase + public class TestSceneDialogOverlay : OsuTestScene { - public TestCaseDialogOverlay() + public TestSceneDialogOverlay() { DialogOverlay overlay; @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual AddStep("dialog #1", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.fa_trash_o, + Icon = FontAwesome.Regular.TrashAlt, HeaderText = @"Confirm deletion of", BodyText = @"Ayase Rie - Yuima-ru*World TVver.", Buttons = new PopupDialogButton[] @@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual AddStep("dialog #2", () => overlay.Push(new PopupDialog { - Icon = FontAwesome.fa_gear, + Icon = FontAwesome.Solid.Cog, HeaderText = @"What do you want to do with", BodyText = "Camellia as \"Bang Riot\" - Blastix Riotz", Buttons = new PopupDialogButton[] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDrawableDate.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDrawableDate.cs new file mode 100644 index 0000000000..19097f33bb --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDrawableDate.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneDrawableDate : OsuTestScene + { + public TestSceneDrawableDate() + { + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Children = new Drawable[] + { + new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(60))), + new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(55))), + new PokeyDrawableDate(DateTimeOffset.Now.Subtract(TimeSpan.FromSeconds(50))), + new PokeyDrawableDate(DateTimeOffset.Now), + new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(60))), + new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(65))), + new PokeyDrawableDate(DateTimeOffset.Now.Add(TimeSpan.FromSeconds(70))), + } + }; + } + + private class PokeyDrawableDate : CompositeDrawable + { + public PokeyDrawableDate(DateTimeOffset date) + { + const float box_size = 10; + + DrawableDate drawableDate; + Box flash; + + AutoSizeAxes = Axes.Both; + InternalChildren = new Drawable[] + { + flash = new Box + { + Colour = Color4.Yellow, + Size = new Vector2(box_size), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Alpha = 0 + }, + drawableDate = new DrawableDate(date) + { + X = box_size + 2, + } + }; + + drawableDate.Current.ValueChanged += _ => flash.FadeOutFromOne(500); + } + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingBar.cs new file mode 100644 index 0000000000..f92aae43d2 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneExpandingBar.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneExpandingBar : OsuTestScene + { + public TestSceneExpandingBar() + { + Container container; + ExpandingBar expandingBar; + + Add(container = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Gray, + Alpha = 0.5f, + RelativeSizeAxes = Axes.Both, + }, + expandingBar = new ExpandingBar + { + Anchor = Anchor.Centre, + ExpandedSize = 10, + CollapsedSize = 2, + Colour = Color4.DeepSkyBlue, + } + } + }); + + AddStep(@"Collapse", () => expandingBar.Collapse()); + AddStep(@"Expand", () => expandingBar.Expand()); + AddSliderStep(@"Resize container", 1, 300, 150, value => container.ResizeTo(value)); + AddStep(@"Horizontal", () => expandingBar.RelativeSizeAxes = Axes.X); + AddStep(@"Anchor top", () => expandingBar.Anchor = Anchor.TopCentre); + AddStep(@"Vertical", () => expandingBar.RelativeSizeAxes = Axes.Y); + AddStep(@"Anchor left", () => expandingBar.Anchor = Anchor.CentreLeft); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs similarity index 75% rename from osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs index 6fa49c4edb..7e6cf1285e 100644 --- a/osu.Game.Tests/Visual/TestCaseHoldToConfirmOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHoldToConfirmOverlay.cs @@ -1,19 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Menu; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseHoldToConfirmOverlay : OsuTestCase + public class TestSceneHoldToConfirmOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(ExitConfirmOverlay) }; - public TestCaseHoldToConfirmOverlay() + public TestSceneHoldToConfirmOverlay() { bool fired = false; @@ -22,7 +23,7 @@ namespace osu.Game.Tests.Visual Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Fired!", - TextSize = 50, + Font = OsuFont.GetFont(size: 50), Alpha = 0, }; @@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual AddStep("start confirming", () => overlay.Begin()); - AddUntilStep(() => fired, "wait until confirmed"); + AddUntilStep("wait until confirmed", () => fired); } private class TestHoldToConfirmOverlay : ExitConfirmOverlay diff --git a/osu.Game.Tests/Visual/TestCaseIconButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs similarity index 82% rename from osu.Game.Tests/Visual/TestCaseIconButton.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs index d1f0727af5..c80b3e6297 100644 --- a/osu.Game.Tests/Visual/TestCaseIconButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneIconButton.cs @@ -1,22 +1,22 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseIconButton : OsuTestCase + public class TestSceneIconButton : OsuTestScene { - public TestCaseIconButton() + public TestSceneIconButton() { Child = new FillFlowContainer { @@ -26,8 +26,7 @@ namespace osu.Game.Tests.Visual { new NamedIconButton("No change", new IconButton()), new NamedIconButton("Background colours", new ColouredIconButton()), - new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }), - new NamedIconButton("Unchanging size", new IconButton(), false), + new NamedIconButton("Full-width", new IconButton { Size = new Vector2(200, 30) }), new NamedIconButton("Icon colours", new IconButton { IconColour = Color4.Green, @@ -48,7 +47,7 @@ namespace osu.Game.Tests.Visual private class NamedIconButton : Container { - public NamedIconButton(string name, IconButton button, bool allowSizeChange = true) + public NamedIconButton(string name, IconButton button) { AutoSizeAxes = Axes.Y; Width = 200; @@ -101,17 +100,11 @@ namespace osu.Game.Tests.Visual } }; - if (allowSizeChange) - iconContainer.AutoSizeAxes = Axes.Both; - else - { - iconContainer.RelativeSizeAxes = Axes.X; - iconContainer.Height = 30; - } + iconContainer.AutoSizeAxes = Axes.Both; button.Anchor = Anchor.Centre; button.Origin = Anchor.Centre; - button.Icon = FontAwesome.fa_osu_osu_o; + button.Icon = OsuIcon.RulesetOsu; } } } diff --git a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs similarity index 80% rename from osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index e1470a860e..395905a30d 100644 --- a/osu.Game.Tests/Visual/TestCaseLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using System; -using System.Collections.Generic; using osu.Game.Screens.Edit.Setup.Components.LabelledComponents; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseLabelledTextBox : OsuTestCase + public class TestSceneLabelledTextBox : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { diff --git a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingAnimation.cs similarity index 81% rename from osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneLoadingAnimation.cs index e52564c40d..b0233d35f9 100644 --- a/osu.Game.Tests/Visual/TestCaseLoadingAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLoadingAnimation.cs @@ -1,17 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseLoadingAnimation : GridTestCase + public class TestSceneLoadingAnimation : OsuGridTestScene { - public TestCaseLoadingAnimation() + public TestSceneLoadingAnimation() : base(2, 2) { LoadingAnimation loading; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs new file mode 100644 index 0000000000..54876dbbda --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLogoTrackingContainer.cs @@ -0,0 +1,299 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneLogoTrackingContainer : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(PlayerLoader), + typeof(Player), + typeof(LogoTrackingContainer), + typeof(ButtonSystem), + typeof(ButtonSystemState), + typeof(Menu), + typeof(MainMenu) + }; + + private OsuLogo logo; + private TestLogoTrackingContainer trackingContainer; + private Container transferContainer; + private Box visualBox; + private Box transferContainerBox; + private Drawable logoFacade; + private bool randomPositions; + + private const float visual_box_size = 72; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Clear facades", () => + { + Clear(); + Add(logo = new OsuLogo { Scale = new Vector2(0.15f), RelativePositionAxes = Axes.Both }); + trackingContainer = null; + transferContainer = null; + }); + } + + /// + /// Move the facade to 0,0, then move it to a random new location while the logo is still transforming to it. + /// Check if the logo is still tracking the facade. + /// + [Test] + public void TestMoveFacade() + { + AddToggleStep("Toggle move continuously", b => randomPositions = b); + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + waitForMove(); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + /// + /// Check if the facade is removed from the container, the logo stops tracking. + /// + [Test] + public void TestRemoveFacade() + { + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + AddStep("Remove facade from FacadeContainer", removeFacade); + waitForMove(); + AddAssert("Logo is not tracking", () => !trackingContainer.IsLogoTracking); + } + + /// + /// Check if the facade gets added to a new container, tracking starts on the new facade. + /// + [Test] + public void TestTransferFacade() + { + AddStep("Add tracking containers", addFacadeContainers); + AddStep("Move facade to random position", moveLogoFacade); + AddStep("Remove facade from FacadeContainer", removeFacade); + AddStep("Transfer facade to a new container", () => + { + transferContainer.Add(logoFacade); + transferContainerBox.Colour = Color4.Tomato; + moveLogoFacade(); + }); + + waitForMove(); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + /// + /// Add a facade to a flow container, move the logo to the center of the screen, then start tracking on the facade. + /// + [Test] + public void TestFlowContainer() + { + FillFlowContainer flowContainer; + + AddStep("Create new flow container with facade", () => + { + Add(trackingContainer = new TestLogoTrackingContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Child = flowContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + } + }); + flowContainer.Children = new Drawable[] + { + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(visual_box_size) + }, + new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Children = new Drawable[] + { + visualBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + trackingContainer.LogoFacade, + } + }, + new Box + { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = Color4.Azure, + Size = new Vector2(visual_box_size) + }, + }; + }); + + AddStep("Perform logo movements", () => + { + trackingContainer.StopTracking(); + logo.MoveTo(new Vector2(0.5f), 500, Easing.InOutExpo); + + visualBox.Colour = Color4.White; + + Scheduler.AddDelayed(() => + { + trackingContainer.StartTracking(logo, 1000, Easing.InOutExpo); + visualBox.Colour = Color4.Tomato; + }, 700); + }); + + waitForMove(8); + AddAssert("Logo is tracking", () => trackingContainer.IsLogoTracking); + } + + [Test] + public void TestSetFacadeSize() + { + bool failed = false; + + AddStep("Set up scenario", () => + { + failed = false; + addFacadeContainers(); + }); + + AddStep("Try setting facade size", () => + { + try + { + logoFacade.Size = new Vector2(0, 0); + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + + AddAssert("Exception thrown", () => failed); + } + + [Test] + public void TestSetMultipleContainers() + { + bool failed = false; + LogoTrackingContainer newContainer = new LogoTrackingContainer(); + + AddStep("Set up scenario", () => + { + failed = false; + newContainer = new LogoTrackingContainer(); + addFacadeContainers(); + moveLogoFacade(); + }); + + AddStep("Try tracking new container", () => + { + try + { + newContainer.StartTracking(logo); + } + catch (Exception e) + { + if (e is InvalidOperationException) + failed = true; + } + }); + + AddAssert("Exception thrown", () => failed); + } + + private void addFacadeContainers() + { + AddRange(new Drawable[] + { + trackingContainer = new TestLogoTrackingContainer + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Child = visualBox = new Box + { + Colour = Color4.Tomato, + RelativeSizeAxes = Axes.Both, + } + }, + transferContainer = new Container + { + Alpha = 0.35f, + RelativeSizeAxes = Axes.None, + Size = new Vector2(visual_box_size), + Child = transferContainerBox = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + } + }, + }); + + trackingContainer.Add(logoFacade = trackingContainer.LogoFacade); + trackingContainer.StartTracking(logo, 1000); + } + + private void waitForMove(int count = 5) => AddWaitStep("Wait for transforms to finish", count); + + private void removeFacade() + { + trackingContainer.Remove(logoFacade); + visualBox.Colour = Color4.White; + moveLogoFacade(); + } + + private void moveLogoFacade() + { + if (logoFacade?.Transforms.Count == 0 && transferContainer?.Transforms.Count == 0) + { + Random random = new Random(); + trackingContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + transferContainer.Delay(500).MoveTo(new Vector2(random.Next(0, (int)logo.Parent.DrawWidth), random.Next(0, (int)logo.Parent.DrawHeight)), 300); + } + + if (randomPositions) + Schedule(moveLogoFacade); + } + + private class TestLogoTrackingContainer : LogoTrackingContainer + { + /// + /// Check that the logo is tracking the position of the facade, with an acceptable precision lenience. + /// + public bool IsLogoTracking => Precision.AlmostEquals(Logo.Position, ComputeLogoTrackingPosition()); + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseMods.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs similarity index 86% rename from osu.Game.Tests/Visual/TestCaseMods.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 029f7da5a9..80408ab43b 100644 --- a/osu.Game.Tests/Visual/TestCaseMods.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -1,34 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets; -using osu.Game.Screens.Play.HUD; -using osuTK; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using System.Linq; -using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Configuration; -using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Mods.Sections; -using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play.HUD; +using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("mod select and icon display")] - public class TestCaseMods : OsuTestCase + public class TestSceneModSelectOverlay : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { - typeof(ModSelectOverlay), typeof(ModDisplay), typeof(ModSection), typeof(ModIcon), @@ -77,7 +76,7 @@ namespace osu.Game.Tests.Visual public void TestOsuMods() { var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 0); - AddStep("change ruleset", () => { Ruleset.Value = ruleset; }); + changeRuleset(ruleset); var instance = ruleset.CreateInstance(); @@ -109,7 +108,7 @@ namespace osu.Game.Tests.Visual public void TestManiaMods() { var ruleset = rulesets.AvailableRulesets.First(r => r.ID == 3); - AddStep("change ruleset", () => { Ruleset.Value = ruleset; }); + changeRuleset(ruleset); testRankedText(ruleset.CreateInstance().GetModsFor(ModType.Conversion).First(m => m is ManiaModRandom)); } @@ -120,7 +119,7 @@ namespace osu.Game.Tests.Visual var rulesetOsu = rulesets.AvailableRulesets.First(r => r.ID == 0); var rulesetMania = rulesets.AvailableRulesets.First(r => r.ID == 3); - AddStep("change ruleset to null", () => { Ruleset.Value = null; }); + changeRuleset(null); var instance = rulesetOsu.CreateInstance(); var easierMods = instance.GetModsFor(ModType.DifficultyReduction); @@ -128,15 +127,15 @@ namespace osu.Game.Tests.Visual AddStep("set mods externally", () => { modDisplay.Current.Value = new[] { noFailMod }; }); - AddStep("change ruleset to osu", () => { Ruleset.Value = rulesetOsu; }); + changeRuleset(rulesetOsu); AddAssert("ensure mods still selected", () => modDisplay.Current.Value.Single(m => m is OsuModNoFail) != null); - AddStep("change ruleset to mania", () => { Ruleset.Value = rulesetMania; }); + changeRuleset(rulesetMania); AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any(m => m is OsuModNoFail)); - AddStep("change ruleset to osu", () => { Ruleset.Value = rulesetOsu; }); + changeRuleset(rulesetOsu); AddAssert("ensure mods not selected", () => !modDisplay.Current.Value.Any()); } @@ -208,23 +207,20 @@ namespace osu.Game.Tests.Visual { checkLabelColor(Color4.White); selectNext(mod); - AddWaitStep(1, "wait for changing colour"); + AddWaitStep("wait for changing colour", 1); checkLabelColor(colour); selectPrevious(mod); - AddWaitStep(1, "wait for changing colour"); + AddWaitStep("wait for changing colour", 1); checkLabelColor(Color4.White); } private void testRankedText(Mod mod) { - AddWaitStep(1, "wait for fade"); - AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); + AddUntilStep("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); selectNext(mod); - AddWaitStep(1, "wait for fade"); - AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0); + AddUntilStep("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0); selectPrevious(mod); - AddWaitStep(1, "wait for fade"); - AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); + AddUntilStep("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0); } private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); @@ -240,6 +236,15 @@ namespace osu.Game.Tests.Visual }); } + private void changeRuleset(RulesetInfo ruleset) + { + AddStep($"change ruleset to {ruleset}", () => { Ruleset.Value = ruleset; }); + waitForLoad(); + } + + private void waitForLoad() => + AddUntilStep("wait for icons to load", () => modSelect.AllLoaded); + private void checkNotSelected(Mod mod) { AddAssert($"check {mod.Name} is not selected", () => @@ -253,7 +258,9 @@ namespace osu.Game.Tests.Visual private class TestModSelectOverlay : ModSelectOverlay { - public new Bindable> SelectedMods => base.SelectedMods; + public new Bindable> SelectedMods => base.SelectedMods; + + public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded); public ModButton GetModButton(Mod mod) { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs new file mode 100644 index 0000000000..ab2ca47100 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneMusicController.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Timing; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneMusicController : OsuTestScene + { + public TestSceneMusicController() + { + Clock = new FramedClock(); + + var mc = new MusicController + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre + }; + Add(mc); + + AddStep(@"show", () => mc.Show()); + AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state); + AddStep(@"show", () => mc.Hide()); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs new file mode 100644 index 0000000000..d8a4514df1 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNotificationOverlay.cs @@ -0,0 +1,246 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.MathUtils; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneNotificationOverlay : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(NotificationSection), + typeof(SimpleNotification), + typeof(ProgressNotification), + typeof(ProgressCompletionNotification), + typeof(IHasCompletionTarget), + typeof(Notification) + }; + + private NotificationOverlay notificationOverlay; + + private readonly List progressingNotifications = new List(); + + private SpriteText displayedCount; + + [SetUp] + public void SetUp() => Schedule(() => + { + progressingNotifications.Clear(); + + Content.Children = new Drawable[] + { + notificationOverlay = new NotificationOverlay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight + }, + displayedCount = new OsuSpriteText() + }; + + notificationOverlay.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count.NewValue}"; }; + }); + + [Test] + public void TestBasicFlow() + { + setState(Visibility.Visible); + AddStep(@"simple #1", sendHelloNotification); + AddStep(@"simple #2", sendAmazingNotification); + AddStep(@"progress #1", sendUploadProgress); + AddStep(@"progress #2", sendDownloadProgress); + + checkProgressingCount(2); + + setState(Visibility.Hidden); + + AddRepeatStep(@"add many simple", sendManyNotifications, 3); + + AddWaitStep("wait some", 5); + + checkProgressingCount(0); + + AddStep(@"progress #3", sendUploadProgress); + + checkProgressingCount(1); + + checkDisplayedCount(33); + + AddWaitStep("wait some", 10); + + checkProgressingCount(0); + } + + [Test] + public void TestImportantWhileClosed() + { + AddStep(@"simple #1", sendHelloNotification); + + AddAssert("Is visible", () => notificationOverlay.State.Value == Visibility.Visible); + + checkDisplayedCount(1); + + AddStep(@"progress #1", sendUploadProgress); + AddStep(@"progress #2", sendDownloadProgress); + + checkProgressingCount(2); + checkDisplayedCount(3); + } + + [Test] + public void TestUnimportantWhileClosed() + { + AddStep(@"background #1", sendBackgroundNotification); + + AddAssert("Is not visible", () => notificationOverlay.State.Value == Visibility.Hidden); + + checkDisplayedCount(1); + + AddStep(@"background progress #1", sendBackgroundUploadProgress); + + AddWaitStep("wait some", 5); + + checkProgressingCount(0); + + checkDisplayedCount(2); + + AddStep(@"simple #1", sendHelloNotification); + + checkDisplayedCount(3); + } + + [Test] + public void TestSpam() + { + setState(Visibility.Visible); + AddRepeatStep("send barrage", sendBarrage, 10); + } + + protected override void Update() + { + base.Update(); + + progressingNotifications.RemoveAll(n => n.State == ProgressNotificationState.Completed); + + if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) + { + var p = progressingNotifications.Find(n => n.State == ProgressNotificationState.Queued); + + if (p != null) + p.State = ProgressNotificationState.Active; + } + + foreach (var n in progressingNotifications.FindAll(n => n.State == ProgressNotificationState.Active)) + { + if (n.Progress < 1) + n.Progress += (float)(Time.Elapsed / 400) * RNG.NextSingle(); + else + n.State = ProgressNotificationState.Completed; + } + } + + private void checkDisplayedCount(int expected) => + AddAssert($"Displayed count is {expected}", () => notificationOverlay.UnreadCount.Value == expected); + + private void sendDownloadProgress() + { + var n = new ProgressNotification + { + Text = @"Downloading Haitai...", + CompletionText = "Downloaded Haitai!", + }; + notificationOverlay.Post(n); + progressingNotifications.Add(n); + } + + private void sendUploadProgress() + { + var n = new ProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; + notificationOverlay.Post(n); + progressingNotifications.Add(n); + } + + private void sendBackgroundUploadProgress() + { + var n = new BackgroundProgressNotification + { + Text = @"Uploading to BSS...", + CompletionText = "Uploaded to BSS!", + }; + notificationOverlay.Post(n); + progressingNotifications.Add(n); + } + + private void setState(Visibility state) => AddStep(state.ToString(), () => notificationOverlay.State.Value = state); + + private void checkProgressingCount(int expected) => AddAssert($"progressing count is {expected}", () => progressingNotifications.Count == expected); + + private void sendBarrage() + { + switch (RNG.Next(0, 4)) + { + case 0: + sendHelloNotification(); + break; + + case 1: + sendAmazingNotification(); + break; + + case 2: + sendUploadProgress(); + break; + + case 3: + sendDownloadProgress(); + break; + } + } + + private void sendAmazingNotification() + { + notificationOverlay.Post(new SimpleNotification { Text = @"You are amazing" }); + } + + private void sendHelloNotification() + { + notificationOverlay.Post(new SimpleNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); + } + + private void sendBackgroundNotification() + { + notificationOverlay.Post(new BackgroundNotification { Text = @"Welcome to osu!. Enjoy your stay!" }); + } + + private void sendManyNotifications() + { + for (int i = 0; i < 10; i++) + notificationOverlay.Post(new SimpleNotification { Text = @"Spam incoming!!" }); + } + + private class BackgroundNotification : SimpleNotification + { + public override bool IsImportant => false; + } + + private class BackgroundProgressNotification : ProgressNotification + { + public override bool IsImportant => false; + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs new file mode 100644 index 0000000000..f73450db60 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneNumberBox : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(OsuNumberBox), + }; + + private OsuNumberBox numberBox; + + [BackgroundDependencyLoader] + private void load() + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 250 }, + Child = numberBox = new OsuNumberBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + PlaceholderText = "Insert numbers here" + } + }; + + clearInput(); + AddStep("enter numbers", () => numberBox.Text = "987654321"); + expectedValue("987654321"); + clearInput(); + AddStep("enter text + single number", () => numberBox.Text = "1 hello 2 world 3"); + expectedValue("123"); + clearInput(); + } + + private void clearInput() => AddStep("clear input", () => numberBox.Text = null); + + private void expectedValue(string value) => AddAssert("expect number", () => numberBox.Text == value); + } +} diff --git a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs similarity index 93% rename from osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs index bc232d814d..d900526c07 100644 --- a/osu.Game.Tests/Visual/TestCaseOnScreenDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOnScreenDisplay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Allocation; @@ -8,10 +8,10 @@ using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics; using osu.Game.Overlays; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseOnScreenDisplay : OsuTestCase + public class TestSceneOnScreenDisplay : OsuTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs new file mode 100644 index 0000000000..6a41d08f01 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuAnimatedButton.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuAnimatedButton : OsuGridTestScene + { + public TestSceneOsuAnimatedButton() + : base(3, 2) + { + Cell(0).Add(new BaseContainer("relative sized") + { + RelativeSizeAxes = Axes.Both, + }); + + Cell(1).Add(new BaseContainer("auto sized") + { + AutoSizeAxes = Axes.Both + }); + + Cell(2).Add(new BaseContainer("relative Y auto X") + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X + }); + + Cell(3).Add(new BaseContainer("relative X auto Y") + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }); + + Cell(4).Add(new BaseContainer("fixed") + { + Size = new Vector2(100), + }); + + Cell(5).Add(new BaseContainer("fixed") + { + Size = new Vector2(100, 50), + }); + + AddToggleStep("toggle enabled", toggle => + { + for (int i = 0; i < 6; i++) + ((BaseContainer)Cell(i).Child).Action = toggle ? () => { } : (Action)null; + }); + } + + public class BaseContainer : OsuAnimatedButton + { + public BaseContainer(string text) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = text + }); + } + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs new file mode 100644 index 0000000000..dbef7d1686 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuHoverContainer.cs @@ -0,0 +1,194 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneOsuHoverContainer : ManualInputManagerTestScene + { + private OsuHoverTestContainer hoverContainer; + private Box colourContainer; + + [SetUp] + public void SetUp() => Schedule(() => + { + Child = hoverContainer = new OsuHoverTestContainer + { + Enabled = { Value = true }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(100), + Child = colourContainer = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }; + + doMoveOut(); + }); + + [Description("Checks IsHovered property value on a container when it is hovered/unhovered.")] + [TestCase(true, TestName = "Enabled_Check_IsHovered")] + [TestCase(false, TestName = "Disabled_Check_IsHovered")] + public void TestIsHoveredHasProperValue(bool isEnabled) + { + setContainerEnabledTo(isEnabled); + + checkNotHovered(); + + moveToText(); + checkHovered(); + + moveOut(); + checkNotHovered(); + + moveToText(); + checkHovered(); + + moveOut(); + checkNotHovered(); + } + + [Test] + [Description("Checks colour fading on an enabled container when it is hovered/unhovered.")] + public void TestTransitionWhileEnabled() + { + enableContainer(); + + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveToText(); + waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR); + + moveOut(); + waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR); + + moveToText(); + waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR); + + moveOut(); + waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR); + } + + [Test] + [Description("Checks colour fading on a disabled container when it is hovered/unhovered.")] + public void TestNoTransitionWhileDisabled() + { + disableContainer(); + + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveToText(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveOut(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveToText(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveOut(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + } + + [Test] + [Description("Checks that when a disabled & hovered container gets enabled, colour fading happens")] + public void TestBecomesEnabledTransition() + { + disableContainer(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveToText(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + enableContainer(); + waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR); + } + + [Test] + [Description("Checks that when an enabled & hovered container gets disabled, colour fading happens")] + public void TestBecomesDisabledTransition() + { + enableContainer(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveToText(); + waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR); + + disableContainer(); + waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR); + } + + [Test] + [Description("Checks that when a hovered container gets enabled and disabled multiple times, colour fading happens")] + public void TestDisabledChangesMultipleTimes() + { + enableContainer(); + checkColour(OsuHoverTestContainer.IDLE_COLOUR); + + moveToText(); + waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR); + + disableContainer(); + waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR); + + enableContainer(); + waitUntilColourIs(OsuHoverTestContainer.HOVER_COLOUR); + + disableContainer(); + waitUntilColourIs(OsuHoverTestContainer.IDLE_COLOUR); + } + + private void enableContainer() => setContainerEnabledTo(true); + + private void disableContainer() => setContainerEnabledTo(false); + + private void setContainerEnabledTo(bool newValue) + { + string word = newValue ? "Enable" : "Disable"; + AddStep($"{word} container", () => hoverContainer.Enabled.Value = newValue); + } + + private void moveToText() => AddStep("Move mouse to text", () => InputManager.MoveMouseTo(hoverContainer)); + + private void moveOut() => AddStep("Move out", doMoveOut); + + private void checkHovered() => AddAssert("Check hovered", () => hoverContainer.IsHovered); + + private void checkNotHovered() => AddAssert("Check not hovered", () => !hoverContainer.IsHovered); + + private void checkColour(ColourInfo expectedColour) + => AddAssert($"Check colour to be '{expectedColour}'", () => currentColour.Equals(expectedColour)); + + private void waitUntilColourIs(ColourInfo expectedColour) + => AddUntilStep($"Wait until hover colour is {expectedColour}", () => currentColour.Equals(expectedColour)); + + private ColourInfo currentColour => colourContainer.DrawColourInfo.Colour; + + /// + /// Moves the cursor to top left corner of the screen + /// + private void doMoveOut() + => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.TopLeft.X, InputManager.ScreenSpaceDrawQuad.TopLeft.Y)); + + private sealed class OsuHoverTestContainer : OsuHoverContainer + { + public static readonly Color4 HOVER_COLOUR = Color4.Red; + public static readonly Color4 IDLE_COLOUR = Color4.Green; + + public OsuHoverTestContainer() + { + HoverColour = HOVER_COLOUR; + IdleColour = IDLE_COLOUR; + } + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs new file mode 100644 index 0000000000..061039b297 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneOsuIcon : OsuTestScene + { + public TestSceneOsuIcon() + { + FillFlowContainer flow; + + AddRange(new Drawable[] + { + new Box + { + Colour = Color4.Teal, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = flow = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + }, + } + }); + + foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static)) + flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)p.GetValue(null))); + + AddStep("toggle shadows", () => flow.Children.ForEach(i => i.SpriteIcon.Shadow = !i.SpriteIcon.Shadow)); + AddStep("change icons", () => flow.Children.ForEach(i => i.SpriteIcon.Icon = new IconUsage((char)(i.SpriteIcon.Icon.Icon + 1)))); + } + + private class Icon : Container, IHasTooltip + { + public string TooltipText { get; } + + public SpriteIcon SpriteIcon { get; } + + public Icon(string name, IconUsage icon) + { + TooltipText = name; + + AutoSizeAxes = Axes.Both; + Child = SpriteIcon = new SpriteIcon + { + Icon = icon, + Size = new Vector2(60), + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneParallaxContainer.cs similarity index 55% rename from osu.Game.Tests/Visual/TestCaseParallaxContainer.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneParallaxContainer.cs index 8c12589c6f..588b25c02d 100644 --- a/osu.Game.Tests/Visual/TestCaseParallaxContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneParallaxContainer.cs @@ -1,20 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Screens.Backgrounds; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseParallaxContainer : OsuTestCase + public class TestSceneParallaxContainer : OsuTestScene { - public TestCaseParallaxContainer() + public TestSceneParallaxContainer() { ParallaxContainer parallax; Add(parallax = new ParallaxContainer { - Child = new BackgroundScreenDefault { Alpha = 0.8f } + Child = new ScreenStack(new BackgroundScreenDefault { Alpha = 0.8f }) + { + RelativeSizeAxes = Axes.Both, + } }); AddStep("default parallax", () => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT); diff --git a/osu.Game.Tests/Visual/TestCasePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs similarity index 62% rename from osu.Game.Tests/Visual/TestCasePopupDialog.cs rename to osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index d88be1e7c7..9ddd8f4038 100644 --- a/osu.Game.Tests/Visual/TestCasePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCasePopupDialog : OsuTestCase + public class TestScenePopupDialog : OsuTestScene { - public TestCasePopupDialog() + public TestScenePopupDialog() { var popup = new PopupDialog { RelativeSizeAxes = Axes.Both, - State = Framework.Graphics.Containers.Visibility.Visible, - Icon = FontAwesome.fa_assistive_listening_systems, + State = { Value = Framework.Graphics.Containers.Visibility.Visible }, + Icon = FontAwesome.Solid.AssistiveListeningSystems, HeaderText = @"This is a test popup", BodyText = "I can say lots of stuff and even wrap my words!", Buttons = new PopupDialogButton[] diff --git a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs similarity index 76% rename from osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs index 82da609e49..9c83fdf96c 100644 --- a/osu.Game.Tests/Visual/TestCaseScreenBreadcrumbControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneScreenBreadcrumbControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using NUnit.Framework; @@ -13,22 +13,24 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestCaseScreenBreadcrumbControl : OsuTestCase + public class TestSceneScreenBreadcrumbControl : OsuTestScene { private readonly ScreenBreadcrumbControl breadcrumbs; - private Screen currentScreen, changedScreen; + private readonly OsuScreenStack screenStack; - public TestCaseScreenBreadcrumbControl() + public TestSceneScreenBreadcrumbControl() { - TestScreen startScreen; OsuSpriteText titleText; + IScreen startScreen = new TestScreenOne(); + screenStack = new OsuScreenStack(startScreen) { RelativeSizeAxes = Axes.Both }; + Children = new Drawable[] { - currentScreen = startScreen = new TestScreenOne(), + screenStack, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -37,7 +39,7 @@ namespace osu.Game.Tests.Visual Spacing = new Vector2(10), Children = new Drawable[] { - breadcrumbs = new ScreenBreadcrumbControl(startScreen) + breadcrumbs = new ScreenBreadcrumbControl(screenStack) { RelativeSizeAxes = Axes.X, }, @@ -46,12 +48,7 @@ namespace osu.Game.Tests.Visual }, }; - breadcrumbs.Current.ValueChanged += s => - { - titleText.Text = $"Changed to {s.ToString()}"; - changedScreen = s; - }; - + breadcrumbs.Current.ValueChanged += screen => titleText.Text = $"Changed to {screen.NewValue.ToString()}"; breadcrumbs.Current.TriggerChange(); waitForCurrent(); @@ -60,18 +57,14 @@ namespace osu.Game.Tests.Visual pushNext(); waitForCurrent(); - AddStep(@"make start current", () => - { - startScreen.MakeCurrent(); - currentScreen = startScreen; - }); + AddStep(@"make start current", () => startScreen.MakeCurrent()); waitForCurrent(); pushNext(); waitForCurrent(); AddAssert(@"only 2 items", () => breadcrumbs.Items.Count() == 2); - AddStep(@"exit current", () => changedScreen.Exit()); - AddAssert(@"current screen is first", () => startScreen == changedScreen); + AddStep(@"exit current", () => screenStack.CurrentScreen.Exit()); + AddAssert(@"current screen is first", () => startScreen == screenStack.CurrentScreen); } [BackgroundDependencyLoader] @@ -80,8 +73,8 @@ namespace osu.Game.Tests.Visual breadcrumbs.StripColour = colours.Blue; } - private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext()); - private void waitForCurrent() => AddUntilStep(() => currentScreen.IsCurrentScreen, "current screen"); + private void pushNext() => AddStep(@"push next screen", () => ((TestScreen)screenStack.CurrentScreen).PushNext()); + private void waitForCurrent() => AddUntilStep("current screen", () => screenStack.CurrentScreen.IsCurrentScreen()); private abstract class TestScreen : OsuScreen { @@ -91,14 +84,14 @@ namespace osu.Game.Tests.Visual public TestScreen PushNext() { TestScreen screen = CreateNextScreen(); - Push(screen); + this.Push(screen); return screen; } protected TestScreen() { - Child = new FillFlowContainer + InternalChild = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/TestCaseTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs similarity index 67% rename from osu.Game.Tests/Visual/TestCaseTabControl.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs index 00b69fd415..a884741ff8 100644 --- a/osu.Game.Tests/Visual/TestCaseTabControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneTabControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; using osu.Framework.Graphics; @@ -8,12 +8,12 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Select.Filter; using osuTK; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { [Description("SongSelect filter control")] - public class TestCaseTabControl : OsuTestCase + public class TestSceneTabControl : OsuTestScene { - public TestCaseTabControl() + public TestSceneTabControl() { OsuSpriteText text; OsuTabControl filter; @@ -33,9 +33,9 @@ namespace osu.Game.Tests.Visual filter.PinItem(GroupMode.All); filter.PinItem(GroupMode.RecentlyPlayed); - filter.Current.ValueChanged += newFilter => + filter.Current.ValueChanged += grouping => { - text.Text = "Currently Selected: " + newFilter.ToString(); + text.Text = "Currently Selected: " + grouping.NewValue.ToString(); }; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs new file mode 100644 index 0000000000..0da256855a --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneToolbarRulesetSelector.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays.Toolbar; +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.MathUtils; +using osu.Game.Rulesets; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneToolbarRulesetSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ToolbarRulesetSelector), + typeof(ToolbarRulesetTabButton), + }; + + [Resolved] + private RulesetStore rulesets { get; set; } + + [Test] + public void TestDisplay() + { + ToolbarRulesetSelector selector = null; + + AddStep("create selector", () => + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + Height = Toolbar.HEIGHT, + Child = selector = new ToolbarRulesetSelector() + }; + }); + + AddStep("Select random", () => + { + selector.Current.Value = selector.Items.ElementAt(RNG.Next(selector.Items.Count())); + }); + AddStep("Toggle disabled state", () => selector.Current.Disabled = !selector.Current.Disabled); + } + + [Test] + public void TestNonFirstRulesetInitialState() + { + TestSelector selector = null; + + AddStep("create selector", () => + { + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.X, + Height = Toolbar.HEIGHT, + Child = selector = new TestSelector() + }; + + selector.Current.Value = rulesets.GetRuleset(2); + }); + + AddAssert("mode line has moved", () => selector.ModeButtonLine.DrawPosition.X > 0); + } + + private class TestSelector : ToolbarRulesetSelector + { + public new Drawable ModeButtonLine => base.ModeButtonLine; + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs new file mode 100644 index 0000000000..849577186d --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneTwoLayerButton.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneTwoLayerButton : OsuTestScene + { + public TestSceneTwoLayerButton() + { + Add(new TwoLayerButton + { + Position = new Vector2(100), + Text = "button", + Icon = FontAwesome.Solid.Check, + BackgroundColour = Color4.SlateGray, + HoverColour = Color4.SlateGray.Darken(0.2f) + }); + } + } +} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs new file mode 100644 index 0000000000..9cdfcb6cc4 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -0,0 +1,148 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Tests.Beatmaps.IO; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneUpdateableBeatmapBackgroundSprite : OsuTestScene + { + private BeatmapSetInfo testBeatmap; + private IAPIProvider api; + private RulesetStore rulesets; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) + { + this.api = api; + this.rulesets = rulesets; + + testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; + } + + [Test] + public void TestNullBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load null beatmap", () => Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }); + AddUntilStep("content loaded", () => background.ContentLoaded); + } + + [Test] + public void TestLocalBeatmap() + { + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load local beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = testBeatmap.Beatmaps.First() } + }; + }); + + AddUntilStep("wait for load", () => background.ContentLoaded); + } + + [Test] + public void TestOnlineBeatmap() + { + if (api.IsLoggedIn) + { + var req = new GetBeatmapSetRequest(1); + api.Queue(req); + + AddUntilStep("wait for api response", () => req.Result != null); + + TestUpdateableBeatmapBackgroundSprite background = null; + + AddStep("load online beatmap", () => + { + Child = background = new TestUpdateableBeatmapBackgroundSprite + { + RelativeSizeAxes = Axes.Both, + Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } } + }; + }); + + AddUntilStep("wait for load", () => background.ContentLoaded); + } + else + AddStep("online (login first)", () => { }); + } + + [Test] + public void TestUnloadAndReload() + { + var backgrounds = new List(); + OsuScrollContainer scrollContainer = null; + + AddStep("create backgrounds hierarchy", () => + { + FillFlowContainer backgroundFlow; + + Child = scrollContainer = new OsuScrollContainer + { + Size = new Vector2(500), + Child = backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Padding = new MarginPadding { Bottom = 550 } + } + }; + + for (int i = 0; i < 25; i++) + { + var background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }; + + if (i % 2 == 0) + background.Beatmap.Value = testBeatmap.Beatmaps.First(); + + backgroundFlow.Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 100, + Masking = true, + Child = background + }); + + backgrounds.Add(background); + } + }); + + var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded); + + AddUntilStep("some loaded", () => loadedBackgrounds.Any()); + AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd()); + AddUntilStep("all unloaded", () => !loadedBackgrounds.Any()); + } + + private class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite + { + protected override double UnloadDelay => 2000; + + public bool ContentLoaded => ((DelayedLoadUnloadWrapper)InternalChildren.LastOrDefault())?.Content?.IsLoaded ?? false; + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs similarity index 79% rename from osu.Game.Tests/Visual/TestCaseVolumePieces.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs index 5d7b7088e7..2fe6240b22 100644 --- a/osu.Game.Tests/Visual/TestCaseVolumePieces.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumePieces.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,9 +8,9 @@ using osu.Game.Overlays.Volume; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual +namespace osu.Game.Tests.Visual.UserInterface { - public class TestCaseVolumePieces : OsuTestCase + public class TestSceneVolumePieces : OsuTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(VolumeMeter), typeof(MuteButton) }; diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index 17aa7db14d..fdb91b7c5b 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -1,30 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.IO; using System.Linq; +using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO.Archives; -using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Resources; namespace osu.Game.Tests { /// - /// A that is used for testcases that include waveforms. + /// A that is used for test scenes that include waveforms. /// public class WaveformTestBeatmap : WorkingBeatmap { private readonly ZipArchiveReader reader; - private readonly FileStream stream; + private readonly Stream stream; + private readonly ITrackStore trackStore; - public WaveformTestBeatmap() - : base(new BeatmapInfo()) + public WaveformTestBeatmap(AudioManager audioManager) + : base(new BeatmapInfo(), audioManager) { - stream = File.OpenRead(ImportBeatmapTest.TEST_OSZ_PATH); + stream = TestResources.GetTestBeatmapStream(); reader = new ZipArchiveReader(stream); + trackStore = audioManager.GetTrackStore(reader); } public override void Dispose() @@ -32,17 +35,19 @@ namespace osu.Game.Tests base.Dispose(); stream?.Dispose(); reader?.Dispose(); + trackStore?.Dispose(); } protected override IBeatmap GetBeatmap() => createTestBeatmap(); protected override Texture GetBackground() => null; - protected override Waveform GetWaveform() => new Waveform(getAudioStream()); + protected override Waveform GetWaveform() => new Waveform(trackStore.GetStream(firstAudioFile)); - protected override Track GetTrack() => new TrackBass(getAudioStream()); + protected override Track GetTrack() => trackStore.Get(firstAudioFile); + + private string firstAudioFile => reader.Filenames.First(f => f.EndsWith(".mp3")); - private Stream getAudioStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".mp3"))); private Stream getBeatmapStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu"))); private Beatmap createTestBeatmap() diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index c0f0695ff8..11d70ee7be 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -2,15 +2,15 @@ - - - - + + + + WinExe - netcoreapp2.1 + netcoreapp2.2 diff --git a/osu.Game.Tournament.Tests/.vscode/launch.json b/osu.Game.Tournament.Tests/.vscode/launch.json new file mode 100644 index 0000000000..0204158347 --- /dev/null +++ b/osu.Game.Tournament.Tests/.vscode/launch.json @@ -0,0 +1,31 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "VisualTests (Debug)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Tournament.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Debug)", + "env": {}, + "console": "internalConsole" + }, + { + "name": "VisualTests (Release)", + "type": "coreclr", + "request": "launch", + "program": "dotnet", + "args": [ + "${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Tournament.Tests.dll" + ], + "cwd": "${workspaceRoot}", + "preLaunchTask": "Build (Release)", + "env": {}, + "console": "internalConsole" + } + ] +} \ No newline at end of file diff --git a/osu.Game.Tournament.Tests/.vscode/tasks.json b/osu.Game.Tournament.Tests/.vscode/tasks.json new file mode 100644 index 0000000000..37f2f32874 --- /dev/null +++ b/osu.Game.Tournament.Tests/.vscode/tasks.json @@ -0,0 +1,47 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build (Debug)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Tournament.Tests.csproj", + "/p:GenerateFullPaths=true", + "/m", + "/verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Build (Release)", + "type": "shell", + "command": "dotnet", + "args": [ + "build", + "--no-restore", + "osu.Game.Tournament.Tests.csproj", + "/p:Configuration=Release", + "/p:GenerateFullPaths=true", + "/m", + "/verbosity:m" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Restore", + "type": "shell", + "command": "dotnet", + "args": [ + "restore" + ], + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs new file mode 100644 index 0000000000..f329623703 --- /dev/null +++ b/osu.Game.Tournament.Tests/Components/TestSceneDrawableTournamentMatch.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Ladder.Components; + +namespace osu.Game.Tournament.Tests.Components +{ + public class TestSceneDrawableTournamentMatch : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TournamentMatch), + typeof(DrawableTournamentTeam), + }; + + public TestSceneDrawableTournamentMatch() + { + Container level1; + Container level2; + + var match1 = new TournamentMatch( + new TournamentTeam { FlagName = { Value = "AU" }, FullName = { Value = "Australia" }, }, + new TournamentTeam { FlagName = { Value = "JP" }, FullName = { Value = "Japan" }, Acronym = { Value = "JPN" } }) + { + Team1Score = { Value = 4 }, + Team2Score = { Value = 1 }, + }; + + var match2 = new TournamentMatch( + new TournamentTeam + { + FlagName = { Value = "RO" }, + FullName = { Value = "Romania" }, + } + ); + + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + level1 = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Children = new[] + { + new DrawableTournamentMatch(match1), + new DrawableTournamentMatch(match2), + new DrawableTournamentMatch(new TournamentMatch()), + } + }, + level2 = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Margin = new MarginPadding(20), + Children = new[] + { + new DrawableTournamentMatch(new TournamentMatch()), + new DrawableTournamentMatch(new TournamentMatch()) + } + } + } + }; + + level1.Children[0].Match.Progression.Value = level2.Children[0].Match; + level1.Children[1].Match.Progression.Value = level2.Children[0].Match; + + AddRepeatStep("change scores", () => match1.Team2Score.Value++, 4); + AddStep("add new team", () => match2.Team2.Value = new TournamentTeam { FlagName = { Value = "PT" }, FullName = { Value = "Portugal" } }); + AddStep("Add progression", () => level1.Children[2].Match.Progression.Value = level2.Children[1].Match); + + AddStep("start match", () => match2.StartMatch()); + + AddRepeatStep("change scores", () => match2.Team1Score.Value++, 10); + + AddStep("start submatch", () => level2.Children[0].Match.StartMatch()); + + AddRepeatStep("change scores", () => level2.Children[0].Match.Team1Score.Value++, 5); + + AddRepeatStep("change scores", () => level2.Children[0].Match.Team2Score.Value++, 4); + } + } +} diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs new file mode 100644 index 0000000000..72d9eb0e07 --- /dev/null +++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Screens.Gameplay.Components; + +namespace osu.Game.Tournament.Tests.Components +{ + public class TestSceneMatchScoreDisplay : LadderTestScene + { + [Cached(Type = typeof(MatchIPCInfo))] + private MatchIPCInfo matchInfo = new MatchIPCInfo(); + + public TestSceneMatchScoreDisplay() + { + Add(new MatchScoreDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Scheduler.AddDelayed(() => + { + int amount = (int)((RNG.NextDouble() - 0.5) * 10000); + if (amount < 0) + matchInfo.Score1.Value -= amount; + else + matchInfo.Score2.Value += amount; + }, 100, true); + } + } +} diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs new file mode 100644 index 0000000000..77fa411058 --- /dev/null +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Components; + +namespace osu.Game.Tournament.Tests.Components +{ + public class TestSceneTournamentBeatmapPanel : OsuTestScene + { + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = 1091460 }); + req.Success += success; + api.Queue(req); + } + + private void success(APIBeatmap apiBeatmap) + { + var beatmap = apiBeatmap.ToBeatmap(rulesets); + Add(new TournamentBeatmapPanel(beatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs new file mode 100644 index 0000000000..41d32d9448 --- /dev/null +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs @@ -0,0 +1,130 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Online.Chat; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osu.Game.Users; + +namespace osu.Game.Tournament.Tests.Components +{ + public class TestSceneTournamentMatchChatDisplay : OsuTestScene + { + private readonly Channel testChannel = new Channel(); + private readonly Channel testChannel2 = new Channel(); + + private readonly User admin = new User + { + Username = "HappyStick", + Id = 2, + Colour = "f2ca34" + }; + + private readonly User redUser = new User + { + Username = "BanchoBot", + Id = 3, + }; + + private readonly User blueUser = new User + { + Username = "Zallius", + Id = 4, + }; + + [Cached] + private LadderInfo ladderInfo = new LadderInfo(); + + [Cached] + private MatchIPCInfo matchInfo = new MatchIPCInfo(); // hide parent + + private readonly TournamentMatchChatDisplay chatDisplay; + + public TestSceneTournamentMatchChatDisplay() + { + Add(chatDisplay = new TournamentMatchChatDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + + ladderInfo.CurrentMatch.Value = new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam { Players = new BindableList { redUser } } + }, + Team2 = + { + Value = new TournamentTeam { Players = new BindableList { blueUser } } + } + }; + + chatDisplay.Channel.Value = testChannel; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(nextMessageId()) + { + Sender = admin, + Content = "I am a wang!" + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(nextMessageId()) + { + Sender = redUser, + Content = "I am team red." + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(nextMessageId()) + { + Sender = redUser, + Content = "I plan to win!" + })); + + AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(nextMessageId()) + { + Sender = blueUser, + Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." + })); + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(nextMessageId()) + { + Sender = admin, + Content = "Okay okay, calm down guys. Let's do this!" + })); + + AddStep("multiple messages", () => testChannel.AddNewMessages(new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!" + }, + new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!!!1" + }, + new Message(nextMessageId()) + { + Sender = admin, + Content = "I spam you!1!1" + })); + + AddStep("change channel to 2", () => chatDisplay.Channel.Value = testChannel2); + + AddStep("change channel to 1", () => chatDisplay.Channel.Value = testChannel); + } + + private int messageId; + + private long? nextMessageId() => messageId++; + } +} diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs new file mode 100644 index 0000000000..b49341d0d1 --- /dev/null +++ b/osu.Game.Tournament.Tests/LadderTestScene.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Tests +{ + public abstract class LadderTestScene : OsuTestScene + { + [Resolved] + protected LadderInfo Ladder { get; private set; } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs new file mode 100644 index 0000000000..201736f38a --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneGameplayScreen.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Screens.Gameplay; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneGameplayScreen : OsuTestScene + { + [Cached] + private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); + + [BackgroundDependencyLoader] + private void load() + { + Add(new GameplayScreen()); + Add(chat); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs new file mode 100644 index 0000000000..a45c5de2bd --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Cursor; +using osu.Game.Tournament.Screens.Editors; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneLadderEditorScreen : LadderTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new LadderEditorScreen() + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs new file mode 100644 index 0000000000..2be0564c82 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics.Cursor; +using osu.Game.Tournament.Screens.Ladder; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneLadderScreen : LadderTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new LadderScreen() + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs new file mode 100644 index 0000000000..a7011c6d3c --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Game.Tournament.Screens.MapPool; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneMapPoolScreen : LadderTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(MapPoolScreen) + }; + + [BackgroundDependencyLoader] + private void load() + { + Add(new MapPoolScreen { Width = 0.7f }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs new file mode 100644 index 0000000000..e15ac416b0 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Tournament.Screens.Editors; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneRoundEditorScreen : LadderTestScene + { + public TestSceneRoundEditorScreen() + { + Add(new RoundEditorScreen + { + Width = 0.85f // create room for control panel + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs new file mode 100644 index 0000000000..f3e65919eb --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneScheduleScreen.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Screens.Schedule; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneScheduleScreen : OsuTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new ScheduleScreen()); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs new file mode 100644 index 0000000000..edf1477b06 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneShowcaseScreen.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Tests.Visual; +using osu.Game.Tournament.Screens.Showcase; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneShowcaseScreen : OsuTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Add(new ShowcaseScreen()); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs new file mode 100644 index 0000000000..097bad4a02 --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Tournament.Screens.Editors; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneTeamEditorScreen : LadderTestScene + { + public TestSceneTeamEditorScreen() + { + Add(new TeamEditorScreen + { + Width = 0.85f // create room for control panel + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs new file mode 100644 index 0000000000..3d340e393c --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.TeamIntro; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneTeamIntroScreen : LadderTestScene + { + [Cached] + private readonly Bindable currentMatch = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { + var match = new TournamentMatch(); + match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); + match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); + match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); + currentMatch.Value = match; + + Add(new TeamIntroScreen + { + FillMode = FillMode.Fit, + FillAspectRatio = 16 / 9f + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs new file mode 100644 index 0000000000..6f5e17a36e --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.TeamWin; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneTeamWinScreen : LadderTestScene + { + [Cached] + private readonly Bindable currentMatch = new Bindable(); + + [BackgroundDependencyLoader] + private void load() + { + var match = new TournamentMatch(); + match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); + match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); + match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); + currentMatch.Value = match; + + Add(new TeamWinScreen + { + FillMode = FillMode.Fit, + FillAspectRatio = 16 / 9f + }); + } + } +} diff --git a/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs new file mode 100644 index 0000000000..378614343a --- /dev/null +++ b/osu.Game.Tournament.Tests/TestSceneTournamentSceneManager.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Platform; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tournament.Tests +{ + public class TestSceneTournamentSceneManager : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(Storage storage) + { + Add(new TournamentSceneManager()); + } + } +} diff --git a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs new file mode 100644 index 0000000000..f7ad757926 --- /dev/null +++ b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; + +namespace osu.Game.Tournament.Tests +{ + public class TournamentTestBrowser : TournamentGameBase + { + protected override void LoadComplete() + { + base.LoadComplete(); + + LoadComponentAsync(new Background("Menu/menu-background-0") + { + Colour = OsuColour.Gray(0.5f), + Depth = 10 + }, AddInternal); + + // Have to construct this here, rather than in the constructor, because + // we depend on some dependencies to be loaded within OsuGameBase.load(). + Add(new TestBrowser()); + } + } +} diff --git a/osu.Game.Tournament.Tests/TournamentTestRunner.cs b/osu.Game.Tournament.Tests/TournamentTestRunner.cs new file mode 100644 index 0000000000..1f63f7c545 --- /dev/null +++ b/osu.Game.Tournament.Tests/TournamentTestRunner.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework; +using osu.Framework.Platform; + +namespace osu.Game.Tournament.Tests +{ + public static class TournamentTestRunner + { + [STAThread] + public static int Main(string[] args) + { + using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) + { + host.Run(new TournamentTestBrowser()); + return 0; + } + } + } +} diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj new file mode 100644 index 0000000000..1c169184fb --- /dev/null +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -0,0 +1,23 @@ + + + + osu.Game.Tournament.Tests.TournamentTestRunner + + + + + + + + + WinExe + netcoreapp2.2 + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Tournament/Components/ControlPanel.cs b/osu.Game.Tournament/Components/ControlPanel.cs new file mode 100644 index 0000000000..a9bb1bf42f --- /dev/null +++ b/osu.Game.Tournament/Components/ControlPanel.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Components +{ + /// + /// An element anchored to the right-hand area of a screen that provides streamer level controls. + /// Should be off-screen. + /// + public class ControlPanel : Container + { + private readonly FillFlowContainer buttons; + + protected override Container Content => buttons; + + public ControlPanel() + { + RelativeSizeAxes = Axes.Both; + AlwaysPresent = true; + Width = 0.15f; + Anchor = Anchor.TopRight; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(54, 54, 54, 255) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Control Panel", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22) + }, + buttons = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.75f, + Position = new Vector2(0, 35f), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5f), + }, + }; + } + + public class Spacer : CompositeDrawable + { + public Spacer(float height = 20) + { + RelativeSizeAxes = Axes.X; + Height = height; + AlwaysPresent = true; + } + } + } +} diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs new file mode 100644 index 0000000000..ee7e350970 --- /dev/null +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Tournament.Components +{ + public class DateTextBox : SettingsTextBox + { + public new Bindable Bindable + { + get => bindable; + set + { + bindable = value.GetBoundCopy(); + bindable.BindValueChanged(dto => + base.Bindable.Value = dto.NewValue.ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ"), true); + } + } + + // hold a reference to the provided bindable so we don't have to in every settings section. + private Bindable bindable; + + public DateTextBox() + { + base.Bindable = new Bindable(); + ((OsuTextBox)Control).OnCommit = (sender, newText) => + { + try + { + bindable.Value = DateTimeOffset.Parse(sender.Text); + } + catch + { + // reset textbox content to its last valid state on a parse failure. + bindable.TriggerChange(); + } + }; + } + } +} diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs new file mode 100644 index 0000000000..361bd92770 --- /dev/null +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Components +{ + public abstract class DrawableTournamentTeam : CompositeDrawable + { + public readonly TournamentTeam Team; + + protected readonly Sprite Flag; + protected readonly OsuSpriteText AcronymText; + + [UsedImplicitly] + private Bindable acronym; + + [UsedImplicitly] + private Bindable flag; + + protected DrawableTournamentTeam(TournamentTeam team) + { + Team = team; + + Flag = new Sprite + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit + }; + + AcronymText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Regular), + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + if (Team == null) return; + + (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(acronym => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true); + (flag = Team.FlagName.GetBoundCopy()).BindValueChanged(acronym => Flag.Texture = textures.Get($@"Flags/{Team.FlagName}"), true); + } + } +} diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs new file mode 100644 index 0000000000..c07882ddd0 --- /dev/null +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -0,0 +1,251 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Screens.Menu; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Components +{ + public class SongBar : CompositeDrawable + { + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + get => beatmap; + set + { + if (beatmap == value) + return; + + beatmap = value; + update(); + } + } + + private LegacyMods mods; + + public LegacyMods Mods + { + get => mods; + set + { + mods = value; + update(); + } + } + + private Container panelContents; + private Container innerPanel; + private Container outerPanel; + private TournamentBeatmapPanel panel; + + private float panelWidth => expanded ? 0.6f : 1; + + private const float main_width = 0.97f; + private const float inner_panel_width = 0.7f; + + private bool expanded; + + public bool Expanded + { + get => expanded; + set + { + expanded = value; + panel?.ResizeWidthTo(panelWidth, 800, Easing.OutQuint); + + if (expanded) + { + innerPanel.ResizeWidthTo(inner_panel_width, 800, Easing.OutQuint); + outerPanel.ResizeWidthTo(main_width, 800, Easing.OutQuint); + } + else + { + innerPanel.ResizeWidthTo(1, 800, Easing.OutQuint); + outerPanel.ResizeWidthTo(0.25f, 800, Easing.OutQuint); + } + } + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + outerPanel = new Container + { + Masking = true, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.2f), + Type = EdgeEffectType.Shadow, + Radius = 5, + }, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + RelativePositionAxes = Axes.X, + X = -(1 - main_width) / 2, + Y = -10, + Width = main_width, + Height = TournamentBeatmapPanel.HEIGHT, + CornerRadius = TournamentBeatmapPanel.HEIGHT / 2, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.93f), + }, + new OsuLogo + { + Triangles = false, + Colour = OsuColour.Gray(0.33f), + Scale = new Vector2(0.08f), + Margin = new MarginPadding(50), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + }, + innerPanel = new Container + { + Masking = true, + CornerRadius = TournamentBeatmapPanel.HEIGHT / 2, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = inner_panel_width, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.86f), + }, + panelContents = new Container + { + RelativeSizeAxes = Axes.Both, + } + } + } + } + } + }; + + Expanded = true; + } + + private void update() + { + if (beatmap == null) + { + panelContents.Clear(); + return; + } + + var bpm = beatmap.BeatmapSet.OnlineInfo.BPM; + var length = beatmap.OnlineInfo.Length; + string hardRockExtra = ""; + string srExtra = ""; + + //var ar = beatmap.BaseDifficulty.ApproachRate; + if ((mods & LegacyMods.HardRock) > 0) + { + hardRockExtra = "*"; + srExtra = "*"; + } + + if ((mods & LegacyMods.DoubleTime) > 0) + { + //ar *= 1.5f; + bpm *= 1.5f; + length /= 1.5f; + srExtra = "*"; + } + + panelContents.Children = new Drawable[] + { + new DiffPiece(("Length", TimeSpan.FromSeconds(length).ToString(@"mm\:ss"))) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomLeft, + }, + new DiffPiece(("BPM", $"{bpm:0.#}")) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopLeft + }, + new DiffPiece( + //("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), + //("AR", $"{ar:0.#}{srExtra}"), + ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), + ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}") + ) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.BottomRight + }, + new DiffPiece(("Star Rating", $"{beatmap.StarDifficulty:0.#}{srExtra}")) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.TopRight + }, + panel = new TournamentBeatmapPanel(beatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(panelWidth, 1) + } + }; + } + + public class DiffPiece : TextFlowContainer + { + public DiffPiece(params (string heading, string content)[] tuples) + { + Margin = new MarginPadding { Horizontal = 15, Vertical = 1 }; + AutoSizeAxes = Axes.Both; + + void cp(SpriteText s, Color4 colour) + { + s.Colour = colour; + s.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15); + } + + for (var i = 0; i < tuples.Length; i++) + { + var tuple = tuples[i]; + + if (i > 0) + { + AddText(" / ", s => + { + cp(s, OsuColour.Gray(0.33f)); + s.Spacing = new Vector2(-2, 0); + }); + } + + AddText(new OsuSpriteText { Text = tuple.heading }, s => cp(s, OsuColour.Gray(0.33f))); + AddText(" ", s => cp(s, OsuColour.Gray(0.33f))); + AddText(new OsuSpriteText { Text = tuple.content }, s => cp(s, OsuColour.Gray(0.5f))); + } + } + } + } +} diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs new file mode 100644 index 0000000000..d5e28c1e3e --- /dev/null +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -0,0 +1,203 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Specialized; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Models; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Components +{ + public class TournamentBeatmapPanel : CompositeDrawable + { + public readonly BeatmapInfo Beatmap; + private readonly string mods; + + private const float horizontal_padding = 10; + private const float vertical_padding = 5; + + public const float HEIGHT = 50; + + private readonly Bindable currentMatch = new Bindable(); + private Box flash; + + public TournamentBeatmapPanel(BeatmapInfo beatmap, string mods = null) + { + if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); + + Beatmap = beatmap; + this.mods = mods; + Width = 400; + Height = HEIGHT; + } + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder, TextureStore textures) + { + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(ladder.CurrentMatch); + + CornerRadius = HEIGHT / 2; + Masking = true; + + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + new UpdateableBeatmapSetCover + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.5f), + BeatmapSet = Beatmap.BeatmapSet, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(vertical_padding), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = new LocalisedString(( + $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}", + $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}")), + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(vertical_padding), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "mapper", + Padding = new MarginPadding { Right = 5 }, + Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14) + }, + new OsuSpriteText + { + Text = Beatmap.Metadata.AuthorString, + Padding = new MarginPadding { Right = 20 }, + Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14) + }, + new OsuSpriteText + { + Text = "difficulty", + Padding = new MarginPadding { Right = 5 }, + Font = OsuFont.GetFont(italics: true, weight: FontWeight.Regular, size: 14) + }, + new OsuSpriteText + { + Text = Beatmap.Version, + Font = OsuFont.GetFont(italics: true, weight: FontWeight.Bold, size: 14) + }, + } + } + }, + }, + flash = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Gray, + Blending = BlendingMode.Additive, + Alpha = 0, + }, + }); + + if (!string.IsNullOrEmpty(mods)) + AddInternal(new Sprite + { + Texture = textures.Get($"mods/{mods}"), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding(20), + Scale = new Vector2(0.5f) + }); + } + + private void matchChanged(ValueChangedEvent match) + { + if (match.OldValue != null) + match.OldValue.PicksBans.CollectionChanged -= picksBansOnCollectionChanged; + match.NewValue.PicksBans.CollectionChanged += picksBansOnCollectionChanged; + updateState(); + } + + private void picksBansOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + => updateState(); + + private BeatmapChoice choice; + + private void updateState() + { + var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap.OnlineBeatmapID); + + bool doFlash = found != choice; + choice = found; + + if (found != null) + { + if (doFlash) + flash?.FadeOutFromOne(500).Loop(0, 10); + + BorderThickness = 6; + + switch (found.Team) + { + case TeamColour.Red: + BorderColour = Color4.Red; + break; + + case TeamColour.Blue: + BorderColour = Color4.Blue; + break; + } + + switch (found.Type) + { + case ChoiceType.Pick: + Colour = Color4.White; + Alpha = 1; + break; + + case ChoiceType.Ban: + Colour = Color4.Gray; + Alpha = 0.5f; + break; + } + } + else + { + Colour = Color4.White; + BorderThickness = 0; + Alpha = 1; + } + } + } +} diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs new file mode 100644 index 0000000000..48c5b9bd35 --- /dev/null +++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Online.Chat; +using osu.Game.Overlays.Chat; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Components +{ + public class TournamentMatchChatDisplay : StandAloneChatDisplay + { + private readonly Bindable chatChannel = new Bindable(); + + private ChannelManager manager; + + public TournamentMatchChatDisplay() + { + RelativeSizeAxes = Axes.X; + Y = 100; + Size = new Vector2(0.45f, 112); + Margin = new MarginPadding(10); + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + } + + [BackgroundDependencyLoader(true)] + private void load(MatchIPCInfo ipc) + { + if (ipc != null) + { + chatChannel.BindTo(ipc.ChatChannel); + chatChannel.BindValueChanged(c => + { + if (string.IsNullOrWhiteSpace(c.NewValue)) + return; + + int id = int.Parse(c.NewValue); + + if (id <= 0) return; + + if (manager == null) + { + AddInternal(manager = new ChannelManager()); + Channel.BindTo(manager.CurrentChannel); + } + + foreach (var ch in manager.JoinedChannels.ToList()) + manager.LeaveChannel(ch); + + var channel = new Channel + { + Id = id, + Type = ChannelType.Public + }; + + manager.JoinChannel(channel); + manager.CurrentChannel.Value = channel; + }, true); + } + } + + protected override ChatLine CreateMessage(Message message) => new MatchMessage(message); + + protected class MatchMessage : StandAloneMessage + { + public MatchMessage(Message message) + : base(message) + { + } + + [BackgroundDependencyLoader] + private void load(LadderInfo info) + { + //if (info.CurrentMatch.Value.Team1.Value.Players.Any(u => u.Id == Message.Sender.Id)) + // ColourBox.Colour = red; + //else if (info.CurrentMatch.Value.Team2.Value.Players.Any(u => u.Id == Message.Sender.Id)) + // ColourBox.Colour = blue; + //else if (Message.Sender.Colour != null) + // SenderText.Colour = ColourBox.Colour = OsuColour.FromHex(Message.Sender.Colour); + } + + private readonly Color4 red = new Color4(186, 0, 18, 255); + private readonly Color4 blue = new Color4(17, 136, 170, 255); + } + } +} diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs new file mode 100644 index 0000000000..4f4660f645 --- /dev/null +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.IO; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Video; +using osu.Game.Graphics; + +namespace osu.Game.Tournament.Components +{ + public class TourneyVideo : CompositeDrawable + { + private readonly VideoSprite video; + + public TourneyVideo(Stream stream) + { + if (stream == null) + { + InternalChild = new Box + { + Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.3f), OsuColour.Gray(0.6f)), + RelativeSizeAxes = Axes.Both, + }; + } + else + InternalChild = video = new VideoSprite(stream) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + }; + } + + public bool Loop + { + set + { + if (video != null) + video.Loop = value; + } + } + } +} diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs new file mode 100644 index 0000000000..4fd858bd12 --- /dev/null +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -0,0 +1,194 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Linq; +using Microsoft.Win32; +using osu.Framework.Allocation; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Platform.Windows; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.IPC +{ + public class FileBasedIPC : MatchIPCInfo + { + [Resolved] + protected IAPIProvider API { get; private set; } + + [Resolved] + protected RulesetStore Rulesets { get; private set; } + + private int lastBeatmapId; + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder, GameHost host) + { + StableStorage stable; + + try + { + stable = new StableStorage(host as DesktopGameHost); + } + catch (Exception e) + { + Logger.Error(e, "Stable installation could not be found; disabling file based IPC"); + return; + } + + const string file_ipc_filename = "ipc.txt"; + const string file_ipc_state_filename = "ipc-state.txt"; + const string file_ipc_scores_filename = "ipc-scores.txt"; + const string file_ipc_channel_filename = "ipc-channel.txt"; + + if (stable.Exists(file_ipc_filename)) + Scheduler.AddDelayed(delegate + { + try + { + using (var stream = stable.GetStream(file_ipc_filename)) + using (var sr = new StreamReader(stream)) + { + var beatmapId = int.Parse(sr.ReadLine()); + var mods = int.Parse(sr.ReadLine()); + + if (lastBeatmapId != beatmapId) + { + lastBeatmapId = beatmapId; + + var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null); + + if (existing != null) + Beatmap.Value = existing.BeatmapInfo; + else + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); + req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); + API.Queue(req); + } + } + + Mods.Value = (LegacyMods)mods; + } + } + catch + { + // file might be in use. + } + + try + { + using (var stream = stable.GetStream(file_ipc_channel_filename)) + using (var sr = new StreamReader(stream)) + { + ChatChannel.Value = sr.ReadLine(); + } + } + catch (Exception) + { + // file might be in use. + } + + try + { + using (var stream = stable.GetStream(file_ipc_state_filename)) + using (var sr = new StreamReader(stream)) + { + State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine()); + } + } + catch (Exception) + { + // file might be in use. + } + + try + { + using (var stream = stable.GetStream(file_ipc_scores_filename)) + using (var sr = new StreamReader(stream)) + { + Score1.Value = int.Parse(sr.ReadLine()); + Score2.Value = int.Parse(sr.ReadLine()); + } + } + catch (Exception) + { + // file might be in use. + } + }, 250, true); + } + + /// + /// A method of accessing an osu-stable install in a controlled fashion. + /// + private class StableStorage : WindowsStorage + { + protected override string LocateBasePath() + { + bool checkExists(string p) + { + return File.Exists(Path.Combine(p, "ipc.txt")); + } + + string stableInstallPath = string.Empty; + + try + { + try + { + stableInstallPath = "G:\\My Drive\\Main\\osu!tourney"; + + if (checkExists(stableInstallPath)) + return stableInstallPath; + + stableInstallPath = "G:\\My Drive\\Main\\osu!mappool"; + + if (checkExists(stableInstallPath)) + return stableInstallPath; + } + catch + { + } + + try + { + using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + + if (checkExists(stableInstallPath)) + return stableInstallPath; + } + catch + { + } + + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); + if (checkExists(stableInstallPath)) + return stableInstallPath; + + return null; + } + finally + { + Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); + } + } + + public StableStorage(DesktopGameHost host) + : base(string.Empty, host) + { + } + } + } +} diff --git a/osu.Game.Tournament/IPC/MatchIPCInfo.cs b/osu.Game.Tournament/IPC/MatchIPCInfo.cs new file mode 100644 index 0000000000..701258c6c7 --- /dev/null +++ b/osu.Game.Tournament/IPC/MatchIPCInfo.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; + +namespace osu.Game.Tournament.IPC +{ + public class MatchIPCInfo : Component + { + public Bindable Beatmap { get; } = new Bindable(); + public Bindable Mods { get; } = new Bindable(); + public Bindable State { get; } = new Bindable(); + public Bindable ChatChannel { get; } = new Bindable(); + public BindableInt Score1 { get; } = new BindableInt(); + public BindableInt Score2 { get; } = new BindableInt(); + } +} diff --git a/osu.Game.Tournament/IPC/TourneyState.cs b/osu.Game.Tournament/IPC/TourneyState.cs new file mode 100644 index 0000000000..ef1c612a53 --- /dev/null +++ b/osu.Game.Tournament/IPC/TourneyState.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tournament.IPC +{ + public enum TourneyState + { + Initialising, + Idle, + WaitingForClients, + Playing, + Ranking + } +} diff --git a/osu.Game.Tournament/Models/BeatmapChoice.cs b/osu.Game.Tournament/Models/BeatmapChoice.cs new file mode 100644 index 0000000000..384b349b24 --- /dev/null +++ b/osu.Game.Tournament/Models/BeatmapChoice.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace osu.Game.Tournament.Models +{ + /// + /// A beatmap choice by a team from a tournament's map pool. + /// + [Serializable] + public class BeatmapChoice + { + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public TeamColour Team; + + [JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)] + public ChoiceType Type; + + public int BeatmapID; + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum TeamColour + { + Red, + Blue + } + + [JsonConverter(typeof(StringEnumConverter))] + public enum ChoiceType + { + Pick, + Ban, + } +} diff --git a/osu.Game.Tournament/Models/LadderEditorInfo.cs b/osu.Game.Tournament/Models/LadderEditorInfo.cs new file mode 100644 index 0000000000..70fd115e25 --- /dev/null +++ b/osu.Game.Tournament/Models/LadderEditorInfo.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; + +namespace osu.Game.Tournament.Models +{ + public class LadderEditorInfo + { + public readonly Bindable Selected = new Bindable(); + } +} diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs new file mode 100644 index 0000000000..547c4eab08 --- /dev/null +++ b/osu.Game.Tournament/Models/LadderInfo.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Framework.Bindables; + +namespace osu.Game.Tournament.Models +{ + /// + /// Holds the complete data required to operate the tournament system. + /// + [Serializable] + public class LadderInfo + { + public BindableList Matches = new BindableList(); + public BindableList Rounds = new BindableList(); + public BindableList Teams = new BindableList(); + + // only used for serialisation + public List Progressions = new List(); + + [JsonIgnore] + public Bindable CurrentMatch = new Bindable(); + } +} diff --git a/osu.Game.Tournament/Models/RoundBeatmap.cs b/osu.Game.Tournament/Models/RoundBeatmap.cs new file mode 100644 index 0000000000..5d43d0ca66 --- /dev/null +++ b/osu.Game.Tournament/Models/RoundBeatmap.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; + +namespace osu.Game.Tournament.Models +{ + public class RoundBeatmap + { + public int ID; + public string Mods; + + public BeatmapInfo BeatmapInfo; + } +} diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs new file mode 100644 index 0000000000..06cce3d59e --- /dev/null +++ b/osu.Game.Tournament/Models/TournamentMatch.cs @@ -0,0 +1,125 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Game.Tournament.Screens.Ladder.Components; +using SixLabors.Primitives; + +namespace osu.Game.Tournament.Models +{ + /// + /// A collection of two teams competing in a head-to-head match. + /// + [Serializable] + public class TournamentMatch + { + public int ID; + + public List Acronyms + { + get + { + List acronyms = new List(); + if (Team1Acronym != null) acronyms.Add(Team1Acronym); + if (Team2Acronym != null) acronyms.Add(Team2Acronym); + return acronyms; + } + } + + [JsonIgnore] + public readonly Bindable Team1 = new Bindable(); + + public string Team1Acronym; + + public readonly Bindable Team1Score = new Bindable(); + + [JsonIgnore] + public readonly Bindable Team2 = new Bindable(); + + public string Team2Acronym; + + public readonly Bindable Team2Score = new Bindable(); + + public readonly Bindable Completed = new Bindable(); + + public readonly Bindable Losers = new Bindable(); + + public readonly ObservableCollection PicksBans = new ObservableCollection(); + + [JsonIgnore] + public readonly Bindable Round = new Bindable(); + + [JsonIgnore] + public readonly Bindable Progression = new Bindable(); + + [JsonIgnore] + public readonly Bindable LosersProgression = new Bindable(); + + /// + /// Should not be set directly. Use LadderInfo.CurrentMatch.Value = this instead. + /// + public readonly Bindable Current = new Bindable(); + + public readonly Bindable Date = new Bindable(DateTimeOffset.Now); + + [JsonProperty] + public readonly BindableList ConditionalMatches = new BindableList(); + + public readonly Bindable Position = new Bindable(); + + public TournamentMatch() + { + Team1.BindValueChanged(t => Team1Acronym = t.NewValue?.Acronym.Value, true); + Team2.BindValueChanged(t => Team2Acronym = t.NewValue?.Acronym.Value, true); + } + + public TournamentMatch(TournamentTeam team1 = null, TournamentTeam team2 = null) + : this() + { + Team1.Value = team1; + Team2.Value = team2; + } + + [JsonIgnore] + public TournamentTeam Winner => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team1.Value : Team2.Value; + + [JsonIgnore] + public TournamentTeam Loser => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team2.Value : Team1.Value; + + public int PointsToWin => Round.Value?.BestOf.Value / 2 + 1 ?? 0; + + /// + /// Remove scores from the match, in case of a false click or false start. + /// + public void CancelMatchStart() + { + Team1Score.Value = null; + Team2Score.Value = null; + } + + /// + /// Initialise this match with zeroed scores. Will be a noop if either team is not present. + /// + public void StartMatch() + { + if (Team1.Value == null || Team2.Value == null) + return; + + Team1Score.Value = 0; + Team2Score.Value = 0; + } + + public void Reset() + { + CancelMatchStart(); + Team1.Value = null; + Team2.Value = null; + Completed.Value = false; + PicksBans.Clear(); + } + } +} diff --git a/osu.Game.Tournament/Models/TournamentProgression.cs b/osu.Game.Tournament/Models/TournamentProgression.cs new file mode 100644 index 0000000000..5575b4c29e --- /dev/null +++ b/osu.Game.Tournament/Models/TournamentProgression.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Tournament.Models +{ + /// + /// A mapping between two es. + /// Used for serialisation exclusively. + /// + [Serializable] + public class TournamentProgression + { + public int SourceID; + public int TargetID; + + public bool Losers; + + public TournamentProgression(int sourceID, int targetID, bool losers = false) + { + SourceID = sourceID; + TargetID = targetID; + Losers = losers; + } + } +} diff --git a/osu.Game.Tournament/Models/TournamentRound.cs b/osu.Game.Tournament/Models/TournamentRound.cs new file mode 100644 index 0000000000..08b3143be1 --- /dev/null +++ b/osu.Game.Tournament/Models/TournamentRound.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Framework.Bindables; + +namespace osu.Game.Tournament.Models +{ + /// + /// A tournament round, containing many matches, generally executed in a short time period. + /// + [Serializable] + public class TournamentRound + { + public readonly Bindable Name = new Bindable(); + public readonly Bindable Description = new Bindable(); + + public readonly BindableInt BestOf = new BindableInt(9) { Default = 9, MinValue = 3, MaxValue = 23 }; + + [JsonProperty] + public readonly BindableList Beatmaps = new BindableList(); + + public readonly Bindable StartDate = new Bindable { Value = DateTimeOffset.UtcNow }; + + // only used for serialisation + public List Matches = new List(); + + public override string ToString() => Name.Value ?? "None"; + } +} diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs new file mode 100644 index 0000000000..54b8a35180 --- /dev/null +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -0,0 +1,54 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; +using osu.Framework.Bindables; +using osu.Game.Users; + +namespace osu.Game.Tournament.Models +{ + /// + /// A team representation. For official tournaments this is generally a country. + /// + [Serializable] + public class TournamentTeam + { + /// + /// The name of this team. + /// + public Bindable FullName = new Bindable(string.Empty); + + /// + /// Name of the file containing the flag. + /// + public Bindable FlagName = new Bindable(string.Empty); + + /// + /// Short acronym which appears in the group boxes post-selection. + /// + public Bindable Acronym = new Bindable(string.Empty); + + [JsonProperty] + public BindableList Players { get; set; } = new BindableList(); + + public TournamentTeam() + { + Acronym.ValueChanged += val => + { + // use a sane default flag name based on acronym. + if (val.OldValue.StartsWith(FlagName.Value, StringComparison.InvariantCultureIgnoreCase)) + FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpper() : string.Empty; + }; + + FullName.ValueChanged += val => + { + // use a sane acronym based on full name. + if (val.OldValue.StartsWith(Acronym.Value, StringComparison.InvariantCultureIgnoreCase)) + Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpper() : string.Empty; + }; + } + + public override string ToString() => FullName.Value ?? Acronym.Value; + } +} diff --git a/osu.Game.Tournament/Properties/AssemblyInfo.cs b/osu.Game.Tournament/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..70e42bcafb --- /dev/null +++ b/osu.Game.Tournament/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Runtime.CompilerServices; + +// We publish our internal attributes to other sub-projects of the framework. +// Note, that we omit visual tests as they are meant to test the framework +// behavior "in the wild". + +[assembly: InternalsVisibleTo("osu.Game.Tournament.Tests")] +[assembly: InternalsVisibleTo("osu.Game.Tournament.Tests.Dynamic")] diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin new file mode 100644 index 0000000000..42cfdf08de Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light.bin differ diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png new file mode 100644 index 0000000000..332d9ca056 Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Light_0.png differ diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin new file mode 100644 index 0000000000..3047c2eb3e Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular.bin differ diff --git a/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png new file mode 100644 index 0000000000..1252d233d3 Binary files /dev/null and b/osu.Game.Tournament/Resources/Fonts/Aquatico-Regular_0.png differ diff --git a/osu.Game.Tournament/Resources/countries.json b/osu.Game.Tournament/Resources/countries.json new file mode 100644 index 0000000000..ec2ca2bf37 --- /dev/null +++ b/osu.Game.Tournament/Resources/countries.json @@ -0,0 +1,1252 @@ +[ + { + "FlagName": "BD", + "FullName": "Bangladesh", + "Acronym": "BGD" + }, + { + "FlagName": "BE", + "FullName": "Belgium", + "Acronym": "BEL" + }, + { + "FlagName": "BF", + "FullName": "Burkina Faso", + "Acronym": "BFA" + }, + { + "FlagName": "BG", + "FullName": "Bulgaria", + "Acronym": "BGR" + }, + { + "FlagName": "BA", + "FullName": "Bosnia and Herzegovina", + "Acronym": "BIH" + }, + { + "FlagName": "BB", + "FullName": "Barbados", + "Acronym": "BRB" + }, + { + "FlagName": "WF", + "FullName": "Wallis and Futuna", + "Acronym": "WLF" + }, + { + "FlagName": "BL", + "FullName": "Saint Barthelemy", + "Acronym": "BLM" + }, + { + "FlagName": "BM", + "FullName": "Bermuda", + "Acronym": "BMU" + }, + { + "FlagName": "BN", + "FullName": "Brunei", + "Acronym": "BRN" + }, + { + "FlagName": "BO", + "FullName": "Bolivia", + "Acronym": "BOL" + }, + { + "FlagName": "BH", + "FullName": "Bahrain", + "Acronym": "BHR" + }, + { + "FlagName": "BI", + "FullName": "Burundi", + "Acronym": "BDI" + }, + { + "FlagName": "BJ", + "FullName": "Benin", + "Acronym": "BEN" + }, + { + "FlagName": "BT", + "FullName": "Bhutan", + "Acronym": "BTN" + }, + { + "FlagName": "JM", + "FullName": "Jamaica", + "Acronym": "JAM" + }, + { + "FlagName": "BV", + "FullName": "Bouvet Island", + "Acronym": "BVT" + }, + { + "FlagName": "BW", + "FullName": "Botswana", + "Acronym": "BWA" + }, + { + "FlagName": "WS", + "FullName": "Samoa", + "Acronym": "WSM" + }, + { + "FlagName": "BQ", + "FullName": "Bonaire, Saint Eustatius and Saba", + "Acronym": "BES" + }, + { + "FlagName": "BR", + "FullName": "Brazil", + "Acronym": "BRA" + }, + { + "FlagName": "BS", + "FullName": "Bahamas", + "Acronym": "BHS" + }, + { + "FlagName": "JE", + "FullName": "Jersey", + "Acronym": "JEY" + }, + { + "FlagName": "BY", + "FullName": "Belarus", + "Acronym": "BLR" + }, + { + "FlagName": "BZ", + "FullName": "Belize", + "Acronym": "BLZ" + }, + { + "FlagName": "RU", + "FullName": "Russia", + "Acronym": "RUS" + }, + { + "FlagName": "RW", + "FullName": "Rwanda", + "Acronym": "RWA" + }, + { + "FlagName": "RS", + "FullName": "Serbia", + "Acronym": "SRB" + }, + { + "FlagName": "TL", + "FullName": "East Timor", + "Acronym": "TLS" + }, + { + "FlagName": "RE", + "FullName": "Reunion", + "Acronym": "REU" + }, + { + "FlagName": "TM", + "FullName": "Turkmenistan", + "Acronym": "TKM" + }, + { + "FlagName": "TJ", + "FullName": "Tajikistan", + "Acronym": "TJK" + }, + { + "FlagName": "RO", + "FullName": "Romania", + "Acronym": "ROU" + }, + { + "FlagName": "TK", + "FullName": "Tokelau", + "Acronym": "TKL" + }, + { + "FlagName": "GW", + "FullName": "Guinea-Bissau", + "Acronym": "GNB" + }, + { + "FlagName": "GU", + "FullName": "Guam", + "Acronym": "GUM" + }, + { + "FlagName": "GT", + "FullName": "Guatemala", + "Acronym": "GTM" + }, + { + "FlagName": "GS", + "FullName": "South Georgia and the South Sandwich Islands", + "Acronym": "SGS" + }, + { + "FlagName": "GR", + "FullName": "Greece", + "Acronym": "GRC" + }, + { + "FlagName": "GQ", + "FullName": "Equatorial Guinea", + "Acronym": "GNQ" + }, + { + "FlagName": "GP", + "FullName": "Guadeloupe", + "Acronym": "GLP" + }, + { + "FlagName": "JP", + "FullName": "Japan", + "Acronym": "JPN" + }, + { + "FlagName": "GY", + "FullName": "Guyana", + "Acronym": "GUY" + }, + { + "FlagName": "GG", + "FullName": "Guernsey", + "Acronym": "GGY" + }, + { + "FlagName": "GF", + "FullName": "French Guiana", + "Acronym": "GUF" + }, + { + "FlagName": "GE", + "FullName": "Georgia", + "Acronym": "GEO" + }, + { + "FlagName": "GD", + "FullName": "Grenada", + "Acronym": "GRD" + }, + { + "FlagName": "GB", + "FullName": "United Kingdom", + "Acronym": "GBR" + }, + { + "FlagName": "GA", + "FullName": "Gabon", + "Acronym": "GAB" + }, + { + "FlagName": "SV", + "FullName": "El Salvador", + "Acronym": "SLV" + }, + { + "FlagName": "GN", + "FullName": "Guinea", + "Acronym": "GIN" + }, + { + "FlagName": "GM", + "FullName": "Gambia", + "Acronym": "GMB" + }, + { + "FlagName": "GL", + "FullName": "Greenland", + "Acronym": "GRL" + }, + { + "FlagName": "GI", + "FullName": "Gibraltar", + "Acronym": "GIB" + }, + { + "FlagName": "GH", + "FullName": "Ghana", + "Acronym": "GHA" + }, + { + "FlagName": "OM", + "FullName": "Oman", + "Acronym": "OMN" + }, + { + "FlagName": "TN", + "FullName": "Tunisia", + "Acronym": "TUN" + }, + { + "FlagName": "JO", + "FullName": "Jordan", + "Acronym": "JOR" + }, + { + "FlagName": "HR", + "FullName": "Croatia", + "Acronym": "HRV" + }, + { + "FlagName": "HT", + "FullName": "Haiti", + "Acronym": "HTI" + }, + { + "FlagName": "HU", + "FullName": "Hungary", + "Acronym": "HUN" + }, + { + "FlagName": "HK", + "FullName": "Hong Kong", + "Acronym": "HKG" + }, + { + "FlagName": "HN", + "FullName": "Honduras", + "Acronym": "HND" + }, + { + "FlagName": "HM", + "FullName": "Heard Island and McDonald Islands", + "Acronym": "HMD" + }, + { + "FlagName": "VE", + "FullName": "Venezuela", + "Acronym": "VEN" + }, + { + "FlagName": "PR", + "FullName": "Puerto Rico", + "Acronym": "PRI" + }, + { + "FlagName": "PS", + "FullName": "Palestinian Territory", + "Acronym": "PSE" + }, + { + "FlagName": "PW", + "FullName": "Palau", + "Acronym": "PLW" + }, + { + "FlagName": "PT", + "FullName": "Portugal", + "Acronym": "PRT" + }, + { + "FlagName": "SJ", + "FullName": "Svalbard and Jan Mayen", + "Acronym": "SJM" + }, + { + "FlagName": "PY", + "FullName": "Paraguay", + "Acronym": "PRY" + }, + { + "FlagName": "IQ", + "FullName": "Iraq", + "Acronym": "IRQ" + }, + { + "FlagName": "PA", + "FullName": "Panama", + "Acronym": "PAN" + }, + { + "FlagName": "PF", + "FullName": "French Polynesia", + "Acronym": "PYF" + }, + { + "FlagName": "PG", + "FullName": "Papua New Guinea", + "Acronym": "PNG" + }, + { + "FlagName": "PE", + "FullName": "Peru", + "Acronym": "PER" + }, + { + "FlagName": "PK", + "FullName": "Pakistan", + "Acronym": "PAK" + }, + { + "FlagName": "PH", + "FullName": "Philippines", + "Acronym": "PHL" + }, + { + "FlagName": "PN", + "FullName": "Pitcairn", + "Acronym": "PCN" + }, + { + "FlagName": "PL", + "FullName": "Poland", + "Acronym": "POL" + }, + { + "FlagName": "PM", + "FullName": "Saint Pierre and Miquelon", + "Acronym": "SPM" + }, + { + "FlagName": "ZM", + "FullName": "Zambia", + "Acronym": "ZMB" + }, + { + "FlagName": "EH", + "FullName": "Western Sahara", + "Acronym": "ESH" + }, + { + "FlagName": "EE", + "FullName": "Estonia", + "Acronym": "EST" + }, + { + "FlagName": "EG", + "FullName": "Egypt", + "Acronym": "EGY" + }, + { + "FlagName": "ZA", + "FullName": "South Africa", + "Acronym": "ZAF" + }, + { + "FlagName": "EC", + "FullName": "Ecuador", + "Acronym": "ECU" + }, + { + "FlagName": "IT", + "FullName": "Italy", + "Acronym": "ITA" + }, + { + "FlagName": "VN", + "FullName": "Vietnam", + "Acronym": "VNM" + }, + { + "FlagName": "SB", + "FullName": "Solomon Islands", + "Acronym": "SLB" + }, + { + "FlagName": "ET", + "FullName": "Ethiopia", + "Acronym": "ETH" + }, + { + "FlagName": "SO", + "FullName": "Somalia", + "Acronym": "SOM" + }, + { + "FlagName": "ZW", + "FullName": "Zimbabwe", + "Acronym": "ZWE" + }, + { + "FlagName": "SA", + "FullName": "Saudi Arabia", + "Acronym": "SAU" + }, + { + "FlagName": "ES", + "FullName": "Spain", + "Acronym": "ESP" + }, + { + "FlagName": "ER", + "FullName": "Eritrea", + "Acronym": "ERI" + }, + { + "FlagName": "ME", + "FullName": "Montenegro", + "Acronym": "MNE" + }, + { + "FlagName": "MD", + "FullName": "Moldova", + "Acronym": "MDA" + }, + { + "FlagName": "MG", + "FullName": "Madagascar", + "Acronym": "MDG" + }, + { + "FlagName": "MF", + "FullName": "Saint Martin", + "Acronym": "MAF" + }, + { + "FlagName": "MA", + "FullName": "Morocco", + "Acronym": "MAR" + }, + { + "FlagName": "MC", + "FullName": "Monaco", + "Acronym": "MCO" + }, + { + "FlagName": "UZ", + "FullName": "Uzbekistan", + "Acronym": "UZB" + }, + { + "FlagName": "MM", + "FullName": "Myanmar", + "Acronym": "MMR" + }, + { + "FlagName": "ML", + "FullName": "Mali", + "Acronym": "MLI" + }, + { + "FlagName": "MO", + "FullName": "Macao", + "Acronym": "MAC" + }, + { + "FlagName": "MN", + "FullName": "Mongolia", + "Acronym": "MNG" + }, + { + "FlagName": "MH", + "FullName": "Marshall Islands", + "Acronym": "MHL" + }, + { + "FlagName": "MK", + "FullName": "Macedonia", + "Acronym": "MKD" + }, + { + "FlagName": "MU", + "FullName": "Mauritius", + "Acronym": "MUS" + }, + { + "FlagName": "MT", + "FullName": "Malta", + "Acronym": "MLT" + }, + { + "FlagName": "MW", + "FullName": "Malawi", + "Acronym": "MWI" + }, + { + "FlagName": "MV", + "FullName": "Maldives", + "Acronym": "MDV" + }, + { + "FlagName": "MQ", + "FullName": "Martinique", + "Acronym": "MTQ" + }, + { + "FlagName": "MP", + "FullName": "Northern Mariana Islands", + "Acronym": "MNP" + }, + { + "FlagName": "MS", + "FullName": "Montserrat", + "Acronym": "MSR" + }, + { + "FlagName": "MR", + "FullName": "Mauritania", + "Acronym": "MRT" + }, + { + "FlagName": "IM", + "FullName": "Isle of Man", + "Acronym": "IMN" + }, + { + "FlagName": "UG", + "FullName": "Uganda", + "Acronym": "UGA" + }, + { + "FlagName": "TZ", + "FullName": "Tanzania", + "Acronym": "TZA" + }, + { + "FlagName": "MY", + "FullName": "Malaysia", + "Acronym": "MYS" + }, + { + "FlagName": "MX", + "FullName": "Mexico", + "Acronym": "MEX" + }, + { + "FlagName": "IL", + "FullName": "Israel", + "Acronym": "ISR" + }, + { + "FlagName": "FR", + "FullName": "France", + "Acronym": "FRA" + }, + { + "FlagName": "IO", + "FullName": "British Indian Ocean Territory", + "Acronym": "IOT" + }, + { + "FlagName": "SH", + "FullName": "Saint Helena", + "Acronym": "SHN" + }, + { + "FlagName": "FI", + "FullName": "Finland", + "Acronym": "FIN" + }, + { + "FlagName": "FJ", + "FullName": "Fiji", + "Acronym": "FJI" + }, + { + "FlagName": "FK", + "FullName": "Falkland Islands", + "Acronym": "FLK" + }, + { + "FlagName": "FM", + "FullName": "Micronesia", + "Acronym": "FSM" + }, + { + "FlagName": "FO", + "FullName": "Faroe Islands", + "Acronym": "FRO" + }, + { + "FlagName": "NI", + "FullName": "Nicaragua", + "Acronym": "NIC" + }, + { + "FlagName": "NL", + "FullName": "Netherlands", + "Acronym": "NLD" + }, + { + "FlagName": "NO", + "FullName": "Norway", + "Acronym": "NOR" + }, + { + "FlagName": "NA", + "FullName": "Namibia", + "Acronym": "NAM" + }, + { + "FlagName": "VU", + "FullName": "Vanuatu", + "Acronym": "VUT" + }, + { + "FlagName": "NC", + "FullName": "New Caledonia", + "Acronym": "NCL" + }, + { + "FlagName": "NE", + "FullName": "Niger", + "Acronym": "NER" + }, + { + "FlagName": "NF", + "FullName": "Norfolk Island", + "Acronym": "NFK" + }, + { + "FlagName": "NG", + "FullName": "Nigeria", + "Acronym": "NGA" + }, + { + "FlagName": "NZ", + "FullName": "New Zealand", + "Acronym": "NZL" + }, + { + "FlagName": "NP", + "FullName": "Nepal", + "Acronym": "NPL" + }, + { + "FlagName": "NR", + "FullName": "Nauru", + "Acronym": "NRU" + }, + { + "FlagName": "NU", + "FullName": "Niue", + "Acronym": "NIU" + }, + { + "FlagName": "CK", + "FullName": "Cook Islands", + "Acronym": "COK" + }, + { + "FlagName": "XK", + "FullName": "Kosovo", + "Acronym": "XKX" + }, + { + "FlagName": "CI", + "FullName": "Ivory Coast", + "Acronym": "CIV" + }, + { + "FlagName": "CH", + "FullName": "Switzerland", + "Acronym": "CHE" + }, + { + "FlagName": "CO", + "FullName": "Colombia", + "Acronym": "COL" + }, + { + "FlagName": "CN", + "FullName": "China", + "Acronym": "CHN" + }, + { + "FlagName": "CM", + "FullName": "Cameroon", + "Acronym": "CMR" + }, + { + "FlagName": "CL", + "FullName": "Chile", + "Acronym": "CHL" + }, + { + "FlagName": "CC", + "FullName": "Cocos Islands", + "Acronym": "CCK" + }, + { + "FlagName": "CA", + "FullName": "Canada", + "Acronym": "CAN" + }, + { + "FlagName": "CG", + "FullName": "Republic of the Congo", + "Acronym": "COG" + }, + { + "FlagName": "CF", + "FullName": "Central African Republic", + "Acronym": "CAF" + }, + { + "FlagName": "CD", + "FullName": "Democratic Republic of the Congo", + "Acronym": "COD" + }, + { + "FlagName": "CZ", + "FullName": "Czech Republic", + "Acronym": "CZE" + }, + { + "FlagName": "CY", + "FullName": "Cyprus", + "Acronym": "CYP" + }, + { + "FlagName": "CX", + "FullName": "Christmas Island", + "Acronym": "CXR" + }, + { + "FlagName": "CR", + "FullName": "Costa Rica", + "Acronym": "CRI" + }, + { + "FlagName": "CW", + "FullName": "Curacao", + "Acronym": "CUW" + }, + { + "FlagName": "CV", + "FullName": "Cape Verde", + "Acronym": "CPV" + }, + { + "FlagName": "CU", + "FullName": "Cuba", + "Acronym": "CUB" + }, + { + "FlagName": "SZ", + "FullName": "Swaziland", + "Acronym": "SWZ" + }, + { + "FlagName": "SY", + "FullName": "Syria", + "Acronym": "SYR" + }, + { + "FlagName": "SX", + "FullName": "Sint Maarten", + "Acronym": "SXM" + }, + { + "FlagName": "KG", + "FullName": "Kyrgyzstan", + "Acronym": "KGZ" + }, + { + "FlagName": "KE", + "FullName": "Kenya", + "Acronym": "KEN" + }, + { + "FlagName": "SS", + "FullName": "South Sudan", + "Acronym": "SSD" + }, + { + "FlagName": "SR", + "FullName": "Suriname", + "Acronym": "SUR" + }, + { + "FlagName": "KI", + "FullName": "Kiribati", + "Acronym": "KIR" + }, + { + "FlagName": "KH", + "FullName": "Cambodia", + "Acronym": "KHM" + }, + { + "FlagName": "KN", + "FullName": "Saint Kitts and Nevis", + "Acronym": "KNA" + }, + { + "FlagName": "KM", + "FullName": "Comoros", + "Acronym": "COM" + }, + { + "FlagName": "ST", + "FullName": "Sao Tome and Principe", + "Acronym": "STP" + }, + { + "FlagName": "SK", + "FullName": "Slovakia", + "Acronym": "SVK" + }, + { + "FlagName": "KR", + "FullName": "South Korea", + "Acronym": "KOR" + }, + { + "FlagName": "SI", + "FullName": "Slovenia", + "Acronym": "SVN" + }, + { + "FlagName": "KP", + "FullName": "North Korea", + "Acronym": "PRK" + }, + { + "FlagName": "KW", + "FullName": "Kuwait", + "Acronym": "KWT" + }, + { + "FlagName": "SN", + "FullName": "Senegal", + "Acronym": "SEN" + }, + { + "FlagName": "SM", + "FullName": "San Marino", + "Acronym": "SMR" + }, + { + "FlagName": "SL", + "FullName": "Sierra Leone", + "Acronym": "SLE" + }, + { + "FlagName": "SC", + "FullName": "Seychelles", + "Acronym": "SYC" + }, + { + "FlagName": "KZ", + "FullName": "Kazakhstan", + "Acronym": "KAZ" + }, + { + "FlagName": "KY", + "FullName": "Cayman Islands", + "Acronym": "CYM" + }, + { + "FlagName": "SG", + "FullName": "Singapore", + "Acronym": "SGP" + }, + { + "FlagName": "SE", + "FullName": "Sweden", + "Acronym": "SWE" + }, + { + "FlagName": "SD", + "FullName": "Sudan", + "Acronym": "SDN" + }, + { + "FlagName": "DO", + "FullName": "Dominican Republic", + "Acronym": "DOM" + }, + { + "FlagName": "DM", + "FullName": "Dominica", + "Acronym": "DMA" + }, + { + "FlagName": "DJ", + "FullName": "Djibouti", + "Acronym": "DJI" + }, + { + "FlagName": "DK", + "FullName": "Denmark", + "Acronym": "DNK" + }, + { + "FlagName": "VG", + "FullName": "British Virgin Islands", + "Acronym": "VGB" + }, + { + "FlagName": "DE", + "FullName": "Germany", + "Acronym": "DEU" + }, + { + "FlagName": "YE", + "FullName": "Yemen", + "Acronym": "YEM" + }, + { + "FlagName": "DZ", + "FullName": "Algeria", + "Acronym": "DZA" + }, + { + "FlagName": "US", + "FullName": "United States", + "Acronym": "USA" + }, + { + "FlagName": "UY", + "FullName": "Uruguay", + "Acronym": "URY" + }, + { + "FlagName": "YT", + "FullName": "Mayotte", + "Acronym": "MYT" + }, + { + "FlagName": "UM", + "FullName": "United States Minor Outlying Islands", + "Acronym": "UMI" + }, + { + "FlagName": "LB", + "FullName": "Lebanon", + "Acronym": "LBN" + }, + { + "FlagName": "LC", + "FullName": "Saint Lucia", + "Acronym": "LCA" + }, + { + "FlagName": "LA", + "FullName": "Laos", + "Acronym": "LAO" + }, + { + "FlagName": "TV", + "FullName": "Tuvalu", + "Acronym": "TUV" + }, + { + "FlagName": "TW", + "FullName": "Taiwan", + "Acronym": "TWN" + }, + { + "FlagName": "TT", + "FullName": "Trinidad and Tobago", + "Acronym": "TTO" + }, + { + "FlagName": "TR", + "FullName": "Turkey", + "Acronym": "TUR" + }, + { + "FlagName": "LK", + "FullName": "Sri Lanka", + "Acronym": "LKA" + }, + { + "FlagName": "LI", + "FullName": "Liechtenstein", + "Acronym": "LIE" + }, + { + "FlagName": "LV", + "FullName": "Latvia", + "Acronym": "LVA" + }, + { + "FlagName": "TO", + "FullName": "Tonga", + "Acronym": "TON" + }, + { + "FlagName": "LT", + "FullName": "Lithuania", + "Acronym": "LTU" + }, + { + "FlagName": "LU", + "FullName": "Luxembourg", + "Acronym": "LUX" + }, + { + "FlagName": "LR", + "FullName": "Liberia", + "Acronym": "LBR" + }, + { + "FlagName": "LS", + "FullName": "Lesotho", + "Acronym": "LSO" + }, + { + "FlagName": "TH", + "FullName": "Thailand", + "Acronym": "THA" + }, + { + "FlagName": "TF", + "FullName": "French Southern Territories", + "Acronym": "ATF" + }, + { + "FlagName": "TG", + "FullName": "Togo", + "Acronym": "TGO" + }, + { + "FlagName": "TD", + "FullName": "Chad", + "Acronym": "TCD" + }, + { + "FlagName": "TC", + "FullName": "Turks and Caicos Islands", + "Acronym": "TCA" + }, + { + "FlagName": "LY", + "FullName": "Libya", + "Acronym": "LBY" + }, + { + "FlagName": "VA", + "FullName": "Vatican", + "Acronym": "VAT" + }, + { + "FlagName": "VC", + "FullName": "Saint Vincent and the Grenadines", + "Acronym": "VCT" + }, + { + "FlagName": "AE", + "FullName": "United Arab Emirates", + "Acronym": "ARE" + }, + { + "FlagName": "AD", + "FullName": "Andorra", + "Acronym": "AND" + }, + { + "FlagName": "AG", + "FullName": "Antigua and Barbuda", + "Acronym": "ATG" + }, + { + "FlagName": "AF", + "FullName": "Afghanistan", + "Acronym": "AFG" + }, + { + "FlagName": "AI", + "FullName": "Anguilla", + "Acronym": "AIA" + }, + { + "FlagName": "VI", + "FullName": "U.S. Virgin Islands", + "Acronym": "VIR" + }, + { + "FlagName": "IS", + "FullName": "Iceland", + "Acronym": "ISL" + }, + { + "FlagName": "IR", + "FullName": "Iran", + "Acronym": "IRN" + }, + { + "FlagName": "AM", + "FullName": "Armenia", + "Acronym": "ARM" + }, + { + "FlagName": "AL", + "FullName": "Albania", + "Acronym": "ALB" + }, + { + "FlagName": "AO", + "FullName": "Angola", + "Acronym": "AGO" + }, + { + "FlagName": "AQ", + "FullName": "Antarctica", + "Acronym": "ATA" + }, + { + "FlagName": "AS", + "FullName": "American Samoa", + "Acronym": "ASM" + }, + { + "FlagName": "AR", + "FullName": "Argentina", + "Acronym": "ARG" + }, + { + "FlagName": "AU", + "FullName": "Australia", + "Acronym": "AUS" + }, + { + "FlagName": "AT", + "FullName": "Austria", + "Acronym": "AUT" + }, + { + "FlagName": "AW", + "FullName": "Aruba", + "Acronym": "ABW" + }, + { + "FlagName": "IN", + "FullName": "India", + "Acronym": "IND" + }, + { + "FlagName": "AX", + "FullName": "Aland Islands", + "Acronym": "ALA" + }, + { + "FlagName": "AZ", + "FullName": "Azerbaijan", + "Acronym": "AZE" + }, + { + "FlagName": "IE", + "FullName": "Ireland", + "Acronym": "IRL" + }, + { + "FlagName": "ID", + "FullName": "Indonesia", + "Acronym": "IDN" + }, + { + "FlagName": "UA", + "FullName": "Ukraine", + "Acronym": "UKR" + }, + { + "FlagName": "QA", + "FullName": "Qatar", + "Acronym": "QAT" + }, + { + "FlagName": "MZ", + "FullName": "Mozambique", + "Acronym": "MOZ" + } +] \ No newline at end of file diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs new file mode 100644 index 0000000000..fccd35ca9e --- /dev/null +++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.IPC; + +namespace osu.Game.Tournament.Screens +{ + public abstract class BeatmapInfoScreen : TournamentScreen + { + protected readonly SongBar SongBar; + + protected BeatmapInfoScreen() + { + AddInternal(SongBar = new SongBar + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }); + } + + [BackgroundDependencyLoader] + private void load(MatchIPCInfo ipc) + { + ipc.Beatmap.BindValueChanged(beatmapChanged, true); + ipc.Mods.BindValueChanged(modsChanged, true); + } + + private void modsChanged(ValueChangedEvent mods) + { + SongBar.Mods = mods.NewValue; + } + + private void beatmapChanged(ValueChangedEvent beatmap) + { + SongBar.FadeInFromZero(300, Easing.OutQuint); + SongBar.Beatmap = beatmap.NewValue; + } + } +} diff --git a/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs similarity index 73% rename from osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs rename to osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs index 49fb39c6da..d197c0f5d9 100644 --- a/osu.Game/Screens/Tournament/Components/DrawingsConfigManager.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/DrawingsConfigManager.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Configuration; using osu.Framework.Platform; -namespace osu.Game.Screens.Tournament.Components +namespace osu.Game.Tournament.Screens.Drawings.Components { public class DrawingsConfigManager : IniConfigManager { diff --git a/osu.Game/Screens/Tournament/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs similarity index 66% rename from osu.Game/Screens/Tournament/Group.cs rename to osu.Game.Tournament/Screens/Drawings/Components/Group.cs index e9825b8d31..549ff26018 100644 --- a/osu.Game/Screens/Tournament/Group.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs @@ -1,21 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; using System.Text; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; -using osu.Game.Screens.Tournament.Teams; -using osu.Framework.Graphics.Shapes; -namespace osu.Game.Screens.Tournament +namespace osu.Game.Tournament.Screens.Drawings.Components { public class Group : Container { @@ -52,8 +51,7 @@ namespace osu.Game.Screens.Tournament Position = new Vector2(0, 7f), Text = $"GROUP {name.ToUpperInvariant()}", - TextSize = 8f, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 8), Colour = new Color4(255, 204, 34, 255), }, teams = new FillFlowContainer @@ -73,7 +71,7 @@ namespace osu.Game.Screens.Tournament }; } - public void AddTeam(DrawingsTeam team) + public void AddTeam(TournamentTeam team) { GroupTeam gt = new GroupTeam(team); @@ -88,10 +86,10 @@ namespace osu.Game.Screens.Tournament public bool ContainsTeam(string fullName) { - return allTeams.Any(t => t.Team.FullName == fullName); + return allTeams.Any(t => t.Team.FullName.Value == fullName); } - public bool RemoveTeam(DrawingsTeam team) + public bool RemoveTeam(TournamentTeam team) { allTeams.RemoveAll(gt => gt.Team == team); @@ -116,25 +114,29 @@ namespace osu.Game.Screens.Tournament { StringBuilder sb = new StringBuilder(); foreach (GroupTeam gt in allTeams) - sb.AppendLine(gt.Team.FullName); + sb.AppendLine(gt.Team.FullName.Value); return sb.ToString(); } - private class GroupTeam : Container + private class GroupTeam : DrawableTournamentTeam { - public readonly DrawingsTeam Team; - private readonly FillFlowContainer innerContainer; - private readonly Sprite flagSprite; - public GroupTeam(DrawingsTeam team) + public GroupTeam(TournamentTeam team) + : base(team) { - Team = team; - Width = 36; AutoSizeAxes = Axes.Y; - Children = new Drawable[] + Flag.Anchor = Anchor.TopCentre; + Flag.Origin = Anchor.TopCentre; + + AcronymText.Anchor = Anchor.TopCentre; + AcronymText.Origin = Anchor.TopCentre; + AcronymText.Text = team.Acronym.Value.ToUpperInvariant(); + AcronymText.Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 10); + + InternalChildren = new Drawable[] { innerContainer = new FillFlowContainer { @@ -149,22 +151,8 @@ namespace osu.Game.Screens.Tournament Children = new Drawable[] { - flagSprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - FillMode = FillMode.Fit - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - - Text = team.Acronym.ToUpperInvariant(), - TextSize = 10f, - Font = @"Exo2.0-Bold" - } + Flag, + AcronymText } } }; @@ -176,12 +164,6 @@ namespace osu.Game.Screens.Tournament innerContainer.ScaleTo(1.5f); innerContainer.ScaleTo(1f, 200); } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - flagSprite.Texture = textures.Get($@"Flags/{Team.FlagName}"); - } } } } diff --git a/osu.Game/Screens/Tournament/GroupContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs similarity index 89% rename from osu.Game/Screens/Tournament/GroupContainer.cs rename to osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs index 73d4f13132..8a66ca7bf6 100644 --- a/osu.Game/Screens/Tournament/GroupContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/GroupContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,10 +7,10 @@ using System.Linq; using System.Text; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Tournament.Models; using osuTK; -using osu.Game.Screens.Tournament.Teams; -namespace osu.Game.Screens.Tournament +namespace osu.Game.Tournament.Screens.Drawings.Components { public class GroupContainer : Container { @@ -64,7 +64,7 @@ namespace osu.Game.Screens.Tournament } } - public void AddTeam(DrawingsTeam team) + public void AddTeam(TournamentTeam team) { if (groups[currentGroup].TeamsCount == maxTeams) return; diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ITeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/ITeamList.cs new file mode 100644 index 0000000000..09208818a9 --- /dev/null +++ b/osu.Game.Tournament/Screens/Drawings/Components/ITeamList.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens.Drawings.Components +{ + public interface ITeamList + { + IEnumerable Teams { get; } + } +} diff --git a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs similarity index 86% rename from osu.Game/Screens/Tournament/ScrollingTeamContainer.cs rename to osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index d401332e06..b147d680f0 100644 --- a/osu.Game/Screens/Tournament/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -1,30 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Threading; -using osu.Game.Screens.Tournament.Teams; +using osu.Game.Graphics; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Tournament +namespace osu.Game.Tournament.Screens.Drawings.Components { public class ScrollingTeamContainer : Container { public event Action OnScrollStarted; - public event Action OnSelected; + public event Action OnSelected; - private readonly List availableTeams = new List(); + private readonly List availableTeams = new List(); private readonly Container tracker; @@ -55,6 +54,7 @@ namespace osu.Game.Screens.Tournament Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.33f), Masking = true, CornerRadius = 10f, @@ -84,9 +84,10 @@ namespace osu.Game.Screens.Tournament } private ScrollState _scrollState; + private ScrollState scrollState { - get { return _scrollState; } + get => _scrollState; set { @@ -107,12 +108,14 @@ namespace osu.Game.Screens.Tournament speedTo(1000f, 200); tracker.FadeOut(100); break; + case ScrollState.Stopping: speedTo(0f, 2000); tracker.FadeIn(200); delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); break; + case ScrollState.Stopped: // Find closest to center if (!Children.Any()) @@ -154,6 +157,7 @@ namespace osu.Game.Screens.Tournament delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); break; + case ScrollState.Idle: resetSelected(); @@ -166,7 +170,7 @@ namespace osu.Game.Screens.Tournament } } - public void AddTeam(DrawingsTeam team) + public void AddTeam(TournamentTeam team) { if (availableTeams.Contains(team)) return; @@ -177,12 +181,12 @@ namespace osu.Game.Screens.Tournament scrollState = ScrollState.Idle; } - public void AddTeams(IEnumerable teams) + public void AddTeams(IEnumerable teams) { if (teams == null) return; - foreach (DrawingsTeam t in teams) + foreach (TournamentTeam t in teams) AddTeam(t); } @@ -193,7 +197,7 @@ namespace osu.Game.Screens.Tournament scrollState = ScrollState.Idle; } - public void RemoveTeam(DrawingsTeam team) + public void RemoveTeam(TournamentTeam team) { availableTeams.Remove(team); @@ -278,7 +282,7 @@ namespace osu.Game.Screens.Tournament private void addFlags() { - foreach (DrawingsTeam t in availableTeams) + foreach (TournamentTeam t in availableTeams) { Add(new ScrollingTeam(t) { @@ -315,20 +319,18 @@ namespace osu.Game.Screens.Tournament Scrolling } - public class ScrollingTeam : Container + public class ScrollingTeam : DrawableTournamentTeam { public const float WIDTH = 58; public const float HEIGHT = 41; - public DrawingsTeam Team; - - private readonly Sprite flagSprite; private readonly Box outline; private bool selected; + public bool Selected { - get { return selected; } + get => selected; set { @@ -341,10 +343,9 @@ namespace osu.Game.Screens.Tournament } } - public ScrollingTeam(DrawingsTeam team) + public ScrollingTeam(TournamentTeam team) + : base(team) { - Team = team; - Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; @@ -354,28 +355,21 @@ namespace osu.Game.Screens.Tournament Alpha = 0; - Children = new Drawable[] + Flag.Anchor = Anchor.Centre; + Flag.Origin = Anchor.Centre; + Flag.Scale = new Vector2(0.9f); + + InternalChildren = new Drawable[] { outline = new Box { RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.33f), Alpha = 0 }, - flagSprite = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - - Size = new Vector2(WIDTH, HEIGHT) - new Vector2(8) - } + Flag }; } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - flagSprite.Texture = textures.Get($@"Flags/{Team.FlagName}"); - } } } } diff --git a/osu.Game/Screens/Tournament/Teams/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs similarity index 67% rename from osu.Game/Screens/Tournament/Teams/StorageBackedTeamList.cs rename to osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index bb112c7e2d..f96ec01cbb 100644 --- a/osu.Game/Screens/Tournament/Teams/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -1,13 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.IO; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Tournament.Models; -namespace osu.Game.Screens.Tournament.Teams +namespace osu.Game.Tournament.Screens.Drawings.Components { public class StorageBackedTeamList : ITeamList { @@ -20,11 +21,11 @@ namespace osu.Game.Screens.Tournament.Teams this.storage = storage; } - public IEnumerable Teams + public IEnumerable Teams { get { - var teams = new List(); + var teams = new List(); try { @@ -47,17 +48,11 @@ namespace osu.Game.Screens.Tournament.Teams continue; } - string flagName = split[0].Trim(); - string teamName = split[1].Trim(); - - string acronym = split.Length >= 3 ? split[2].Trim() : teamName; - acronym = acronym.Substring(0, Math.Min(3, acronym.Length)); - - teams.Add(new DrawingsTeam + teams.Add(new TournamentTeam { - FlagName = flagName, - FullName = teamName, - Acronym = acronym + FullName = { Value = split[1].Trim(), }, + Acronym = { Value = split.Length >= 3 ? split[2].Trim() : null, }, + FlagName = { Value = split[0].Trim() } }); } } diff --git a/osu.Game/Screens/Tournament/Components/VisualiserContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs similarity index 92% rename from osu.Game/Screens/Tournament/Components/VisualiserContainer.cs rename to osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs index 1453d4e78f..1cd942b987 100644 --- a/osu.Game/Screens/Tournament/Components/VisualiserContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.MathUtils; -using System.Collections.Generic; -using System.Linq; -namespace osu.Game.Screens.Tournament.Components +namespace osu.Game.Tournament.Screens.Drawings.Components { public class VisualiserContainer : Container { @@ -19,7 +19,7 @@ namespace osu.Game.Screens.Tournament.Components /// public int Lines { - get { return allLines.Count; } + get => allLines.Count; set { while (value > allLines.Count) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs new file mode 100644 index 0000000000..3a14b6d9c2 --- /dev/null +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -0,0 +1,265 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Drawings.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.Drawings +{ + public class DrawingsScreen : CompositeDrawable + { + private const string results_filename = "drawings_results.txt"; + + private ScrollingTeamContainer teamsContainer; + private GroupContainer groupsContainer; + private OsuSpriteText fullTeamNameText; + + private readonly List allTeams = new List(); + + private DrawingsConfigManager drawingsConfig; + + private Task writeOp; + + private Storage storage; + + public ITeamList TeamList; + + [BackgroundDependencyLoader] + private void load(TextureStore textures, Storage storage) + { + RelativeSizeAxes = Axes.Both; + + this.storage = storage; + + if (TeamList == null) + TeamList = new StorageBackedTeamList(storage); + + if (!TeamList.Teams.Any()) + { + return; + } + + drawingsConfig = new DrawingsConfigManager(storage); + + InternalChildren = new Drawable[] + { + // Main container + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Texture = textures.Get(@"Backgrounds/Drawings/background.png") + }, + // Visualiser + new VisualiserContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + + RelativeSizeAxes = Axes.X, + Size = new Vector2(1, 10), + + Colour = new Color4(255, 204, 34, 255), + + Lines = 6 + }, + // Groups + groupsContainer = new GroupContainer(drawingsConfig.Get(DrawingsConfig.Groups), drawingsConfig.Get(DrawingsConfig.TeamsPerGroup)) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + + Padding = new MarginPadding + { + Top = 35f, + Bottom = 35f + } + }, + // Scrolling teams + teamsContainer = new ScrollingTeamContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + + RelativeSizeAxes = Axes.X, + }, + // Scrolling team name + fullTeamNameText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + + Position = new Vector2(0, 45f), + + Colour = OsuColour.Gray(0.33f), + + Alpha = 0, + + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), + } + } + }, + // Control panel container + new ControlPanel + { + new OsuButton + { + RelativeSizeAxes = Axes.X, + + Text = "Begin random", + Action = teamsContainer.StartScrolling, + }, + new OsuButton + { + RelativeSizeAxes = Axes.X, + + Text = "Stop random", + Action = teamsContainer.StopScrolling, + }, + new OsuButton + { + RelativeSizeAxes = Axes.X, + + Text = "Reload", + Action = reloadTeams + }, + new ControlPanel.Spacer(), + new OsuButton + { + RelativeSizeAxes = Axes.X, + + Text = "Reset", + Action = () => reset() + } + } + }; + + teamsContainer.OnSelected += onTeamSelected; + teamsContainer.OnScrollStarted += () => fullTeamNameText.FadeOut(200); + + reset(true); + } + + private void onTeamSelected(TournamentTeam team) + { + groupsContainer.AddTeam(team); + + fullTeamNameText.Text = team.FullName.Value; + fullTeamNameText.FadeIn(200); + + writeResults(groupsContainer.GetStringRepresentation()); + } + + private void writeResults(string text) + { + void writeAction() + { + try + { + // Write to drawings_results + using (Stream stream = storage.GetStream(results_filename, FileAccess.Write, FileMode.Create)) + using (StreamWriter sw = new StreamWriter(stream)) + { + sw.Write(text); + } + } + catch (Exception ex) + { + Logger.Error(ex, "Failed to write results."); + } + } + + writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run((Action)writeAction); + } + + private void reloadTeams() + { + teamsContainer.ClearTeams(); + allTeams.Clear(); + + foreach (TournamentTeam t in TeamList.Teams) + { + if (groupsContainer.ContainsTeam(t.FullName.Value)) + continue; + + allTeams.Add(t); + teamsContainer.AddTeam(t); + } + } + + private void reset(bool loadLastResults = false) + { + groupsContainer.ClearTeams(); + + reloadTeams(); + + if (!storage.Exists(results_filename)) + return; + + if (loadLastResults) + { + try + { + // Read from drawings_results + using (Stream stream = storage.GetStream(results_filename, FileAccess.Read, FileMode.Open)) + using (StreamReader sr = new StreamReader(stream)) + { + string line; + + while ((line = sr.ReadLine()?.Trim()) != null) + { + if (string.IsNullOrEmpty(line)) + continue; + + if (line.ToUpperInvariant().StartsWith("GROUP")) + continue; + + // ReSharper disable once AccessToModifiedClosure + TournamentTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName.Value == line); + + if (teamToAdd == null) + continue; + + groupsContainer.AddTeam(teamToAdd); + teamsContainer.RemoveTeam(teamToAdd); + } + } + } + catch (Exception ex) + { + Logger.Error(ex, "Failed to read last drawings results."); + } + } + else + { + writeResults(string.Empty); + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Editors/IModelBacked.cs b/osu.Game.Tournament/Screens/Editors/IModelBacked.cs new file mode 100644 index 0000000000..ed94bd7f45 --- /dev/null +++ b/osu.Game.Tournament/Screens/Editors/IModelBacked.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tournament.Screens.Editors +{ + /// + /// Provides a mechanism to access a related model from a representing class. + /// + /// The type of model. + public interface IModelBacked + { + /// + /// The model. + /// + TModel Model { get; } + } +} diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs new file mode 100644 index 0000000000..ba63013886 --- /dev/null +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -0,0 +1,152 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Framework.Input.States; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Ladder; +using osu.Game.Tournament.Screens.Ladder.Components; +using osuTK; +using osuTK.Graphics; +using SixLabors.Primitives; + +namespace osu.Game.Tournament.Screens.Editors +{ + [Cached] + public class LadderEditorScreen : LadderScreen, IHasContextMenu + { + [Cached] + private LadderEditorInfo editorInfo = new LadderEditorInfo(); + + protected override bool DrawLoserPaths => true; + + [BackgroundDependencyLoader] + private void load() + { + Content.Add(new LadderEditorSettings + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(5) + }); + } + + public void BeginJoin(TournamentMatch match, bool losers) + { + ScrollContent.Add(new JoinVisualiser(MatchesContainer, match, losers, UpdateLayout)); + } + + public MenuItem[] ContextMenuItems + { + get + { + if (editorInfo == null) + return new MenuItem[0]; + + return new MenuItem[] + { + new OsuMenuItem("Create new match", MenuItemType.Highlighted, () => + { + var pos = MatchesContainer.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position); + LadderInfo.Matches.Add(new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } }); + }), + new OsuMenuItem("Reset teams", MenuItemType.Destructive, () => + { + foreach (var p in MatchesContainer) + p.Match.Reset(); + }) + }; + } + } + + public void Remove(TournamentMatch match) + { + MatchesContainer.FirstOrDefault(p => p.Match == match)?.Remove(); + } + + private class JoinVisualiser : CompositeDrawable + { + private readonly Container matchesContainer; + public readonly TournamentMatch Source; + private readonly bool losers; + private readonly Action complete; + + private ProgressionPath path; + + public JoinVisualiser(Container matchesContainer, TournamentMatch source, bool losers, Action complete) + { + this.matchesContainer = matchesContainer; + RelativeSizeAxes = Axes.Both; + + Source = source; + this.losers = losers; + this.complete = complete; + if (losers) + Source.LosersProgression.Value = null; + else + Source.Progression.Value = null; + } + + private DrawableTournamentMatch findTarget(InputState state) + { + return matchesContainer.FirstOrDefault(d => d.ReceivePositionalInputAt(state.Mouse.Position)); + } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + { + return true; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + var found = findTarget(e.CurrentState); + + if (found == path?.Destination) + return false; + + path?.Expire(); + path = null; + + if (found == null) + return false; + + AddInternal(path = new ProgressionPath(matchesContainer.First(c => c.Match == Source), found) + { + Colour = Color4.Yellow, + }); + + return base.OnMouseMove(e); + } + + protected override bool OnClick(ClickEvent e) + { + var found = findTarget(e.CurrentState); + + if (found != null) + { + if (found.Match != Source) + { + if (losers) + Source.LosersProgression.Value = found.Match; + else + Source.Progression.Value = found.Match; + } + + complete?.Invoke(); + Expire(); + return true; + } + + return false; + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs new file mode 100644 index 0000000000..b036350879 --- /dev/null +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -0,0 +1,282 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Settings; +using osu.Game.Rulesets; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osuTK; + +namespace osu.Game.Tournament.Screens.Editors +{ + public class RoundEditorScreen : TournamentEditorScreen + { + protected override BindableList Storage => LadderInfo.Rounds; + + public class RoundRow : CompositeDrawable, IModelBacked + { + public TournamentRound Model { get; } + + [Resolved] + private LadderInfo ladderInfo { get; set; } + + public RoundRow(TournamentRound round) + { + Model = round; + + Masking = true; + CornerRadius = 10; + + RoundBeatmapEditor beatmapEditor = new RoundBeatmapEditor(round) + { + Width = 0.95f + }; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.1f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Padding = new MarginPadding { Right = 160 }, + Spacing = new Vector2(5), + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SettingsTextBox + { + LabelText = "Name", + Width = 0.33f, + Bindable = Model.Name + }, + new SettingsTextBox + { + LabelText = "Description", + Width = 0.33f, + Bindable = Model.Description + }, + new DateTextBox + { + LabelText = "Start Time", + Width = 0.33f, + Bindable = Model.StartDate + }, + new SettingsSlider + { + LabelText = "Best of", + Width = 0.33f, + Bindable = Model.BestOf + }, + new SettingsButton + { + Width = 0.2f, + Margin = new MarginPadding(10), + Text = "Add beatmap", + Action = () => beatmapEditor.CreateNew() + }, + beatmapEditor + } + }, + new DangerousSettingsButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.None, + Width = 150, + Text = "Delete Round", + Action = () => + { + Expire(); + ladderInfo.Rounds.Remove(Model); + }, + } + }; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + public class RoundBeatmapEditor : CompositeDrawable + { + private readonly TournamentRound round; + private readonly FillFlowContainer flow; + + public RoundBeatmapEditor(TournamentRound round) + { + this.round = round; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + ChildrenEnumerable = round.Beatmaps.Select(p => new RoundBeatmapRow(round, p)) + }; + } + + public void CreateNew() + { + var user = new RoundBeatmap(); + round.Beatmaps.Add(user); + flow.Add(new RoundBeatmapRow(round, user)); + } + + public class RoundBeatmapRow : CompositeDrawable + { + public RoundBeatmap Model { get; } + + [Resolved] + protected IAPIProvider API { get; private set; } + + private readonly Bindable beatmapId = new Bindable(); + + private readonly Bindable mods = new Bindable(); + + private readonly Container drawableContainer; + + public RoundBeatmapRow(TournamentRound team, RoundBeatmap beatmap) + { + Model = beatmap; + + Margin = new MarginPadding(10); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Padding = new MarginPadding { Right = 160 }, + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SettingsNumberBox + { + LabelText = "Beatmap ID", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = beatmapId, + }, + new SettingsTextBox + { + LabelText = "Mods", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = mods, + }, + drawableContainer = new Container + { + Size = new Vector2(100, 70), + }, + } + }, + new DangerousSettingsButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.None, + Width = 150, + Text = "Delete Beatmap", + Action = () => + { + Expire(); + team.Beatmaps.Remove(beatmap); + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + beatmapId.Value = Model.ID.ToString(); + beatmapId.BindValueChanged(idString => + { + int parsed; + + int.TryParse(idString.NewValue, out parsed); + + Model.ID = parsed; + + if (idString.NewValue != idString.OldValue) + Model.BeatmapInfo = null; + + if (Model.BeatmapInfo != null) + { + updatePanel(); + return; + } + + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = Model.ID }); + + req.Success += res => + { + Model.BeatmapInfo = res.ToBeatmap(rulesets); + updatePanel(); + }; + + req.Failure += _ => + { + Model.BeatmapInfo = null; + updatePanel(); + }; + + API.Queue(req); + }, true); + + mods.Value = Model.Mods; + mods.BindValueChanged(modString => Model.Mods = modString.NewValue); + } + + private void updatePanel() + { + drawableContainer.Clear(); + + if (Model.BeatmapInfo != null) + drawableContainer.Child = new TournamentBeatmapPanel(Model.BeatmapInfo, Model.Mods) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 300 + }; + } + } + } + } + + protected override RoundRow CreateDrawable(TournamentRound model) => new RoundRow(model); + } +} diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs new file mode 100644 index 0000000000..a4479f3cfd --- /dev/null +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -0,0 +1,314 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Settings; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tournament.Screens.Editors +{ + public class TeamEditorScreen : TournamentEditorScreen + { + [Resolved] + private Framework.Game game { get; set; } + + protected override BindableList Storage => LadderInfo.Teams; + + [BackgroundDependencyLoader] + private void load() + { + ControlPanel.Add(new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Add all countries", + Action = addAllCountries + }); + } + + protected override TeamRow CreateDrawable(TournamentTeam model) => new TeamRow(model); + + private void addAllCountries() + { + List countries; + using (Stream stream = game.Resources.GetStream("Resources/countries.json")) + using (var sr = new StreamReader(stream)) + countries = JsonConvert.DeserializeObject>(sr.ReadToEnd()); + + foreach (var c in countries) + Storage.Add(c); + } + + public class TeamRow : CompositeDrawable, IModelBacked + { + public TournamentTeam Model { get; } + + private readonly Container drawableContainer; + + [Resolved] + private LadderInfo ladderInfo { get; set; } + + public TeamRow(TournamentTeam team) + { + Model = team; + + Masking = true; + CornerRadius = 10; + + PlayerEditor playerEditor = new PlayerEditor(Model) + { + Width = 0.95f + }; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.1f), + RelativeSizeAxes = Axes.Both, + }, + drawableContainer = new Container + { + Size = new Vector2(100, 50), + Margin = new MarginPadding(10), + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Spacing = new Vector2(5), + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SettingsTextBox + { + LabelText = "Name", + Width = 0.2f, + Bindable = Model.FullName + }, + new SettingsTextBox + { + LabelText = "Acronym", + Width = 0.2f, + Bindable = Model.Acronym + }, + new SettingsTextBox + { + LabelText = "Flag", + Width = 0.2f, + Bindable = Model.FlagName + }, + new SettingsButton + { + Width = 0.11f, + Margin = new MarginPadding(10), + Text = "Add player", + Action = () => playerEditor.CreateNew() + }, + new DangerousSettingsButton + { + Width = 0.11f, + Text = "Delete Team", + Margin = new MarginPadding(10), + Action = () => + { + Expire(); + ladderInfo.Teams.Remove(Model); + }, + }, + playerEditor + } + }, + }; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Model.FlagName.BindValueChanged(updateDrawable, true); + } + + private void updateDrawable(ValueChangedEvent flag) + { + drawableContainer.Child = new DrawableTeamFlag(Model); + } + + private class DrawableTeamFlag : DrawableTournamentTeam + { + public DrawableTeamFlag(TournamentTeam team) + : base(team) + { + InternalChild = Flag; + RelativeSizeAxes = Axes.Both; + + Flag.Anchor = Anchor.Centre; + Flag.Origin = Anchor.Centre; + } + } + + public class PlayerEditor : CompositeDrawable + { + private readonly TournamentTeam team; + private readonly FillFlowContainer flow; + + public PlayerEditor(TournamentTeam team) + { + this.team = team; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = flow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + ChildrenEnumerable = team.Players.Select(p => new PlayerRow(team, p)) + }; + } + + public void CreateNew() + { + var user = new User(); + team.Players.Add(user); + flow.Add(new PlayerRow(team, user)); + } + + public class PlayerRow : CompositeDrawable + { + private readonly User user; + + [Resolved] + protected IAPIProvider API { get; private set; } + + private readonly Bindable userId = new Bindable(); + + private readonly Container drawableContainer; + + public PlayerRow(TournamentTeam team, User user) + { + this.user = user; + + Margin = new MarginPadding(10); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(5), + Padding = new MarginPadding { Right = 160 }, + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new SettingsNumberBox + { + LabelText = "User ID", + RelativeSizeAxes = Axes.None, + Width = 200, + Bindable = userId, + }, + drawableContainer = new Container + { + Size = new Vector2(100, 70), + }, + } + }, + new DangerousSettingsButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.None, + Width = 150, + Text = "Delete Player", + Action = () => + { + Expire(); + team.Players.Remove(user); + }, + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + userId.Value = user.Id.ToString(); + userId.BindValueChanged(idString => + { + long parsed; + + long.TryParse(idString.NewValue, out parsed); + + user.Id = parsed; + + if (idString.NewValue != idString.OldValue) + user.Username = string.Empty; + + if (!string.IsNullOrEmpty(user.Username)) + { + updatePanel(); + return; + } + + var req = new GetUserRequest(user.Id); + + req.Success += res => + { + // TODO: this should be done in a better way. + user.Username = res.Username; + user.Country = res.Country; + user.Cover = res.Cover; + + updatePanel(); + }; + + req.Failure += _ => + { + user.Id = 1; + updatePanel(); + }; + + API.Queue(req); + }, true); + } + + private void updatePanel() + { + drawableContainer.Child = new UserPanel(user) { Width = 300 }; + } + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs new file mode 100644 index 0000000000..50d3207345 --- /dev/null +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -0,0 +1,84 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osu.Game.Tournament.Components; +using osuTK; + +namespace osu.Game.Tournament.Screens.Editors +{ + public abstract class TournamentEditorScreen : TournamentScreen, IProvideVideo + where TDrawable : Drawable, IModelBacked + where TModel : class, new() + { + protected abstract BindableList Storage { get; } + + private FillFlowContainer flow; + + protected ControlPanel ControlPanel; + + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f), + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Width = 0.9f, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Child = flow = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + Spacing = new Vector2(20) + }, + }, + ControlPanel = new ControlPanel + { + Children = new Drawable[] + { + new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Add new", + Action = () => Storage.Add(new TModel()) + }, + new DangerousSettingsButton + { + RelativeSizeAxes = Axes.X, + Text = "Clear all", + Action = Storage.Clear + }, + } + } + }); + + Storage.ItemsAdded += items => items.ForEach(i => flow.Add(CreateDrawable(i))); + Storage.ItemsRemoved += items => items.ForEach(i => flow.RemoveAll(d => d.Model == i)); + + foreach (var model in Storage) + flow.Add(CreateDrawable(model)); + } + + protected abstract TDrawable CreateDrawable(TModel model); + } +} diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs new file mode 100644 index 0000000000..9e1888b44b --- /dev/null +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs @@ -0,0 +1,220 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Showcase; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Tournament.Screens.Gameplay.Components +{ + public class MatchHeader : Container + { + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + Height = 95; + Children = new Drawable[] + { + new TournamentLogo(), + new RoundDisplay + { + Y = 10, + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + }, + new TeamScoreDisplay(TeamColour.Red) + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + new TeamScoreDisplay(TeamColour.Blue) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + }; + } + + private class TeamScoreDisplay : CompositeDrawable + { + private readonly TeamColour teamColour; + + private readonly Color4 red = new Color4(129, 68, 65, 255); + private readonly Color4 blue = new Color4(41, 91, 97, 255); + + private readonly Bindable currentMatch = new Bindable(); + private readonly Bindable currentTeam = new Bindable(); + private readonly Bindable currentTeamScore = new Bindable(); + + public TeamScoreDisplay(TeamColour teamColour) + { + this.teamColour = teamColour; + + RelativeSizeAxes = Axes.Y; + Width = 300; + } + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder) + { + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(ladder.CurrentMatch); + } + + private void matchChanged(ValueChangedEvent match) + { + currentTeamScore.UnbindBindings(); + currentTeamScore.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1Score : match.NewValue.Team2Score); + + currentTeam.UnbindBindings(); + currentTeam.BindTo(teamColour == TeamColour.Red ? match.NewValue.Team1 : match.NewValue.Team2); + + // team may change to same team, which means score is not in a good state. + // thus we handle this manually. + teamChanged(currentTeam.Value); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + switch (e.Button) + { + case MouseButton.Left: + if (currentTeamScore.Value < currentMatch.Value.PointsToWin) + currentTeamScore.Value++; + return true; + + case MouseButton.Right: + if (currentTeamScore.Value > 0) + currentTeamScore.Value--; + return true; + } + + return base.OnMouseDown(e); + } + + private void teamChanged(TournamentTeam team) + { + var colour = teamColour == TeamColour.Red ? red : blue; + var flip = teamColour != TeamColour.Red; + + InternalChildren = new Drawable[] + { + new TeamDisplay(team, colour, flip), + new TeamScore(currentTeamScore, flip, currentMatch.Value.PointsToWin) + { + Colour = colour + } + }; + } + } + + private class TeamScore : CompositeDrawable + { + private readonly Bindable currentTeamScore = new Bindable(); + private readonly StarCounter counter; + + public TeamScore(Bindable score, bool flip, int count) + { + var anchor = flip ? Anchor.CentreRight : Anchor.CentreLeft; + + Anchor = anchor; + Origin = anchor; + + InternalChild = counter = new StarCounter(count) + { + Anchor = anchor, + X = (flip ? -1 : 1) * 90, + Y = 5, + Scale = flip ? new Vector2(-1, 1) : Vector2.One, + }; + + currentTeamScore.BindValueChanged(scoreChanged); + currentTeamScore.BindTo(score); + } + + private void scoreChanged(ValueChangedEvent score) => counter.CountStars = score.NewValue ?? 0; + } + + private class TeamDisplay : DrawableTournamentTeam + { + public TeamDisplay(TournamentTeam team, Color4 colour, bool flip) + : base(team) + { + RelativeSizeAxes = Axes.Both; + + var anchor = flip ? Anchor.CentreRight : Anchor.CentreLeft; + + Anchor = Origin = anchor; + + Flag.Anchor = Flag.Origin = anchor; + Flag.RelativeSizeAxes = Axes.None; + Flag.Size = new Vector2(60, 40); + Flag.Margin = new MarginPadding(20); + + InternalChild = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Flag, + new OsuSpriteText + { + Text = team?.FullName.Value.ToUpper() ?? "???", + X = (flip ? -1 : 1) * 90, + Y = -10, + Colour = colour, + Font = TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 20), + Origin = anchor, + Anchor = anchor, + }, + } + }; + } + } + + private class RoundDisplay : CompositeDrawable + { + private readonly Bindable currentMatch = new Bindable(); + + public RoundDisplay() + { + Width = 200; + Height = 20; + } + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder) + { + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(ladder.CurrentMatch); + } + + private void matchChanged(ValueChangedEvent match) + { + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.White, + Text = match.NewValue.Round.Value?.Name.Value ?? "Unknown Round", + Font = TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 18), + }, + }; + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs new file mode 100644 index 0000000000..78455c8bb7 --- /dev/null +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchScoreDisplay.cs @@ -0,0 +1,131 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.Gameplay.Components +{ + public class MatchScoreDisplay : CompositeDrawable + { + private readonly Color4 red = new Color4(186, 0, 18, 255); + private readonly Color4 blue = new Color4(17, 136, 170, 255); + + private const float bar_height = 20; + + private readonly BindableInt score1 = new BindableInt(); + private readonly BindableInt score2 = new BindableInt(); + + private readonly MatchScoreCounter score1Text; + private readonly MatchScoreCounter score2Text; + + private readonly Circle score1Bar; + private readonly Circle score2Bar; + + public MatchScoreDisplay() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + score1Bar = new Circle + { + Name = "top bar red", + RelativeSizeAxes = Axes.X, + Height = bar_height, + Width = 0, + Colour = red, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopRight + }, + score1Text = new MatchScoreCounter + { + Colour = red, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + score2Bar = new Circle + { + Name = "top bar blue", + RelativeSizeAxes = Axes.X, + Height = bar_height, + Width = 0, + Colour = blue, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopLeft + }, + score2Text = new MatchScoreCounter + { + Colour = blue, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + }; + } + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder, MatchIPCInfo ipc) + { + score1.BindValueChanged(_ => updateScores()); + score1.BindTo(ipc.Score1); + + score2.BindValueChanged(_ => updateScores()); + score2.BindTo(ipc.Score2); + } + + private void updateScores() + { + score1Text.Current.Value = score1.Value; + score2Text.Current.Value = score2.Value; + + var winningText = score1.Value > score2.Value ? score1Text : score2Text; + var losingText = score1.Value <= score2.Value ? score1Text : score2Text; + + winningText.Winning = true; + losingText.Winning = false; + + var winningBar = score1.Value > score2.Value ? score1Bar : score2Bar; + var losingBar = score1.Value <= score2.Value ? score1Bar : score2Bar; + + var diff = Math.Max(score1.Value, score2.Value) - Math.Min(score1.Value, score2.Value); + + losingBar.ResizeWidthTo(0, 400, Easing.OutQuint); + winningBar.ResizeWidthTo(Math.Min(0.4f, (float)Math.Pow(diff / 1500000f, 0.5) / 2), 400, Easing.OutQuint); + } + + protected override void Update() + { + base.Update(); + + score1Text.X = -Math.Max(5 + score1Text.DrawWidth / 2, score1Bar.DrawWidth); + score2Text.X = Math.Max(5 + score2Text.DrawWidth / 2, score2Bar.DrawWidth); + } + + private class MatchScoreCounter : ScoreCounter + { + public MatchScoreCounter() + { + Margin = new MarginPadding { Top = bar_height + 5, Horizontal = 10 }; + + Winning = false; + } + + public bool Winning + { + set => DisplayedCountSpriteText.Font = value + ? TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Regular, size: 60) + : TournamentFont.GetFont(typeface: TournamentTypeface.Aquatico, weight: FontWeight.Light, size: 40); + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs new file mode 100644 index 0000000000..b9a74bfe16 --- /dev/null +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -0,0 +1,211 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Gameplay.Components; +using osu.Game.Tournament.Screens.MapPool; +using osu.Game.Tournament.Screens.TeamWin; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.Gameplay +{ + public class GameplayScreen : BeatmapInfoScreen + { + private readonly BindableBool warmup = new BindableBool(); + + private readonly Bindable currentMatch = new Bindable(); + + public readonly Bindable State = new Bindable(); + private OsuButton warmupButton; + private MatchIPCInfo ipc; + + private readonly Color4 red = new Color4(186, 0, 18, 255); + private readonly Color4 blue = new Color4(17, 136, 170, 255); + + [Resolved(canBeNull: true)] + private TournamentSceneManager sceneManager { get; set; } + + [Resolved] + private TournamentMatchChatDisplay chat { get; set; } + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder, MatchIPCInfo ipc) + { + this.ipc = ipc; + + AddRangeInternal(new Drawable[] + { + new MatchHeader(), + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Y = 5, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Box + { + // chroma key area for stable gameplay + Name = "chroma", + RelativeSizeAxes = Axes.X, + Height = 512, + Colour = new Color4(0, 255, 0, 255), + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Y = -4, + Children = new Drawable[] + { + new Circle + { + Name = "top bar red", + RelativeSizeAxes = Axes.X, + Height = 8, + Width = 0.5f, + Colour = red, + }, + new Circle + { + Name = "top bar blue", + RelativeSizeAxes = Axes.X, + Height = 8, + Width = 0.5f, + Colour = blue, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + } + }, + } + }, + scoreDisplay = new MatchScoreDisplay + { + Y = -60, + Scale = new Vector2(0.8f), + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, + new ControlPanel + { + Children = new Drawable[] + { + warmupButton = new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Toggle warmup", + Action = () => warmup.Toggle() + }, + new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Toggle chat", + Action = () => { State.Value = State.Value == TourneyState.Idle ? TourneyState.Playing : TourneyState.Idle; } + } + } + } + }); + + State.BindTo(ipc.State); + State.BindValueChanged(stateChanged, true); + + currentMatch.BindValueChanged(m => + { + warmup.Value = m.NewValue.Team1Score.Value + m.NewValue.Team2Score.Value == 0; + scheduledOperation?.Cancel(); + }); + + currentMatch.BindTo(ladder.CurrentMatch); + + warmup.BindValueChanged(w => warmupButton.Alpha = !w.NewValue ? 0.5f : 1, true); + } + + private ScheduledDelegate scheduledOperation; + private MatchScoreDisplay scoreDisplay; + + private TourneyState lastState; + + private void stateChanged(ValueChangedEvent state) + { + try + { + if (state.NewValue == TourneyState.Ranking) + { + if (warmup.Value) return; + + if (ipc.Score1.Value > ipc.Score2.Value) + currentMatch.Value.Team1Score.Value++; + else + currentMatch.Value.Team2Score.Value++; + } + + scheduledOperation?.Cancel(); + + void expand() + { + chat?.Expand(); + + using (BeginDelayedSequence(300, true)) + { + scoreDisplay.FadeIn(100); + SongBar.Expanded = true; + } + } + + void contract() + { + SongBar.Expanded = false; + scoreDisplay.FadeOut(100); + using (chat?.BeginDelayedSequence(500)) + chat?.Contract(); + } + + switch (state.NewValue) + { + case TourneyState.Idle: + contract(); + + const float delay_before_progression = 4000; + + // if we've returned to idle and the last screen was ranking + // we should automatically proceed after a short delay + if (lastState == TourneyState.Ranking && !warmup.Value) + { + if (currentMatch.Value?.Completed.Value == true) + scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(TeamWinScreen)); }, delay_before_progression); + else if (currentMatch.Value?.Completed.Value == false) + scheduledOperation = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(MapPoolScreen)); }, delay_before_progression); + } + + break; + + case TourneyState.Ranking: + scheduledOperation = Scheduler.AddDelayed(contract, 10000); + break; + + default: + chat.Expand(); + expand(); + break; + } + } + finally + { + lastState = state.NewValue; + } + } + } +} diff --git a/osu.Game.Tournament/Screens/IProvideVideo.cs b/osu.Game.Tournament/Screens/IProvideVideo.cs new file mode 100644 index 0000000000..61989d8448 --- /dev/null +++ b/osu.Game.Tournament/Screens/IProvideVideo.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tournament.Screens +{ + /// + /// Marker interface for a screen which provides its own local video background. + /// + public interface IProvideVideo + { + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/ConditionalTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/ConditionalTournamentMatch.cs new file mode 100644 index 0000000000..16224a7fb4 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/ConditionalTournamentMatch.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + /// + /// A match that may not necessarily occur. + /// + public class ConditionalTournamentMatch : TournamentMatch + { + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs new file mode 100644 index 0000000000..ded21730f3 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -0,0 +1,207 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Editors; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class DrawableMatchTeam : DrawableTournamentTeam, IHasContextMenu + { + private readonly TournamentMatch match; + private readonly bool losers; + private OsuSpriteText scoreText; + private Box background; + + private readonly Bindable score = new Bindable(); + private readonly BindableBool completed = new BindableBool(); + + private Color4 colourWinner; + private Color4 colourNormal; + + private readonly Func isWinner; + private LadderEditorScreen ladderEditor; + + [Resolved] + private LadderInfo ladderInfo { get; set; } + + private void setCurrent() + { + //todo: tournamentgamebase? + if (ladderInfo.CurrentMatch.Value != null) + ladderInfo.CurrentMatch.Value.Current.Value = false; + + ladderInfo.CurrentMatch.Value = match; + ladderInfo.CurrentMatch.Value.Current.Value = true; + } + + [Resolved(CanBeNull = true)] + private LadderEditorInfo editorInfo { get; set; } + + public DrawableMatchTeam(TournamentTeam team, TournamentMatch match, bool losers) + : base(team) + { + this.match = match; + this.losers = losers; + Size = new Vector2(150, 40); + + Masking = true; + CornerRadius = 5; + + Flag.Scale = new Vector2(0.9f); + Flag.Anchor = Flag.Origin = Anchor.CentreLeft; + + AcronymText.Anchor = AcronymText.Origin = Anchor.CentreLeft; + AcronymText.Padding = new MarginPadding { Left = 50 }; + AcronymText.Font = OsuFont.GetFont(size: 24); + + if (match != null) + { + isWinner = () => match.Winner == Team; + + completed.BindTo(match.Completed); + if (team != null) + score.BindTo(team == match.Team1.Value ? match.Team1Score : match.Team2Score); + } + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, LadderEditorScreen ladderEditor) + { + this.ladderEditor = ladderEditor; + + colourWinner = losers ? colours.YellowDarker : colours.BlueDarker; + colourNormal = OsuColour.Gray(0.2f); + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Padding = new MarginPadding(5), + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + AcronymText, + Flag, + new Container + { + Masking = true, + CornerRadius = 5, + Width = 0.3f, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.1f), + Alpha = 0.8f, + RelativeSizeAxes = Axes.Both, + }, + scoreText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20), + } + } + } + } + } + }; + + completed.BindValueChanged(_ => updateWinStyle()); + + score.BindValueChanged(val => + { + scoreText.Text = val.NewValue?.ToString() ?? string.Empty; + updateWinStyle(); + }, true); + } + + protected override bool OnClick(ClickEvent e) + { + if (Team == null || editorInfo != null) return false; + + if (!match.Current.Value) + { + setCurrent(); + return true; + } + + if (e.Button == MouseButton.Left) + { + if (score.Value == null) + { + match.StartMatch(); + } + else if (!match.Completed.Value) + score.Value++; + } + else + { + if (match.Progression.Value?.Completed.Value == true) + // don't allow changing scores if the match has a progression. can cause large data loss + return false; + + if (match.Completed.Value && match.Winner != Team) + // don't allow changing scores from the non-winner + return false; + + if (score.Value > 0) + score.Value--; + else + match.CancelMatchStart(); + } + + return false; + } + + private void updateWinStyle() + { + bool winner = completed.Value && isWinner?.Invoke() == true; + + background.FadeColour(winner ? colourWinner : colourNormal, winner ? 500 : 0, Easing.OutQuint); + + scoreText.Font = AcronymText.Font = OsuFont.GetFont(weight: winner ? FontWeight.Bold : FontWeight.Regular); + } + + public MenuItem[] ContextMenuItems + { + get + { + if (editorInfo == null) + return new MenuItem[0]; + + return new MenuItem[] + { + new OsuMenuItem("Set as current", MenuItemType.Standard, setCurrent), + new OsuMenuItem("Join with", MenuItemType.Standard, () => ladderEditor.BeginJoin(match, false)), + new OsuMenuItem("Join with (loser)", MenuItemType.Standard, () => ladderEditor.BeginJoin(match, true)), + new OsuMenuItem("Remove", MenuItemType.Destructive, () => ladderEditor.Remove(match)), + }; + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs new file mode 100644 index 0000000000..dde280ccd8 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -0,0 +1,313 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Tournament.Models; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; +using SixLabors.Primitives; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class DrawableTournamentMatch : CompositeDrawable + { + public readonly TournamentMatch Match; + private readonly bool editor; + protected readonly FillFlowContainer Flow; + private readonly Drawable selectionBox; + private readonly Drawable currentMatchSelectionBox; + private Bindable globalSelection; + + [Resolved(CanBeNull = true)] + private LadderEditorInfo editorInfo { get; set; } + + [Resolved(CanBeNull = true)] + private LadderInfo ladderInfo { get; set; } + + public DrawableTournamentMatch(TournamentMatch match, bool editor = false) + { + Match = match; + this.editor = editor; + + AutoSizeAxes = Axes.Both; + + Margin = new MarginPadding(5); + + InternalChildren = new[] + { + selectionBox = new Container + { + CornerRadius = 5, + Masking = true, + Scale = new Vector2(1.05f), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Colour = Color4.YellowGreen, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + currentMatchSelectionBox = new Container + { + CornerRadius = 5, + Masking = true, + Scale = new Vector2(1.05f), + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0, + Colour = Color4.OrangeRed, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + Flow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2) + } + }; + + boundReference(match.Team1).BindValueChanged(_ => updateTeams()); + boundReference(match.Team2).BindValueChanged(_ => updateTeams()); + boundReference(match.Team1Score).BindValueChanged(_ => updateWinConditions()); + boundReference(match.Team2Score).BindValueChanged(_ => updateWinConditions()); + boundReference(match.Round).BindValueChanged(_ => + { + updateWinConditions(); + Changed?.Invoke(); + }); + boundReference(match.Completed).BindValueChanged(_ => updateProgression()); + boundReference(match.Progression).BindValueChanged(_ => updateProgression()); + boundReference(match.LosersProgression).BindValueChanged(_ => updateProgression()); + boundReference(match.Losers).BindValueChanged(_ => + { + updateTeams(); + Changed?.Invoke(); + }); + boundReference(match.Current).BindValueChanged(_ => updateCurrentMatch(), true); + boundReference(match.Position).BindValueChanged(pos => + { + if (!IsDragged) + Position = new Vector2(pos.NewValue.X, pos.NewValue.Y); + Changed?.Invoke(); + }, true); + + updateTeams(); + } + + /// + /// Fired when somethign changed that requires a ladder redraw. + /// + public Action Changed; + + private readonly List refBindables = new List(); + + private T boundReference(T obj) + where T : IBindable + { + obj = (T)obj.GetBoundCopy(); + refBindables.Add(obj); + return obj; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + foreach (var b in refBindables) + b.UnbindAll(); + } + + private void updateCurrentMatch() + { + if (Match.Current.Value) + currentMatchSelectionBox.Show(); + else + currentMatchSelectionBox.Hide(); + } + + private bool selected; + + public bool Selected + { + get => selected; + + set + { + if (value == selected) return; + + selected = value; + + if (selected) + { + selectionBox.Show(); + if (editor) + editorInfo.Selected.Value = Match; + else + ladderInfo.CurrentMatch.Value = Match; + } + else + selectionBox.Hide(); + } + } + + private void updateProgression() + { + if (!Match.Completed.Value) + { + // ensure we clear any of our teams from our progression. + // this is not pretty logic but should suffice for now. + if (Match.Progression.Value != null && Match.Progression.Value.Team1.Value == Match.Team1.Value) + Match.Progression.Value.Team1.Value = null; + + if (Match.Progression.Value != null && Match.Progression.Value.Team2.Value == Match.Team2.Value) + Match.Progression.Value.Team2.Value = null; + + if (Match.LosersProgression.Value != null && Match.LosersProgression.Value.Team1.Value == Match.Team1.Value) + Match.LosersProgression.Value.Team1.Value = null; + + if (Match.LosersProgression.Value != null && Match.LosersProgression.Value.Team2.Value == Match.Team2.Value) + Match.LosersProgression.Value.Team2.Value = null; + } + else + { + transferProgression(Match.Progression?.Value, Match.Winner); + transferProgression(Match.LosersProgression?.Value, Match.Loser); + } + + Changed?.Invoke(); + } + + private void transferProgression(TournamentMatch destination, TournamentTeam team) + { + if (destination == null) return; + + bool progressionAbove = destination.ID < Match.ID; + + Bindable destinationTeam; + + // check for the case where we have already transferred out value + if (destination.Team1.Value == team) + destinationTeam = destination.Team1; + else if (destination.Team2.Value == team) + destinationTeam = destination.Team2; + else + { + destinationTeam = progressionAbove ? destination.Team2 : destination.Team1; + if (destinationTeam.Value != null) + destinationTeam = progressionAbove ? destination.Team1 : destination.Team2; + } + + destinationTeam.Value = team; + } + + private void updateWinConditions() + { + if (Match.Round.Value == null) return; + + var instaWinAmount = Match.Round.Value.BestOf.Value / 2; + + Match.Completed.Value = Match.Round.Value.BestOf.Value > 0 + && (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instaWinAmount || Match.Team2Score.Value > instaWinAmount); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateTeams(); + + if (editorInfo != null) + { + globalSelection = editorInfo.Selected.GetBoundCopy(); + globalSelection.BindValueChanged(s => + { + if (s.NewValue != Match) Selected = false; + }); + } + } + + private void updateTeams() + { + if (LoadState != LoadState.Loaded) + return; + + // todo: teams may need to be bindable for transitions at a later point. + + if (Match.Team1.Value == null || Match.Team2.Value == null) + Match.CancelMatchStart(); + + if (Match.ConditionalMatches.Count > 0) + { + foreach (var conditional in Match.ConditionalMatches) + { + var team1Match = conditional.Acronyms.Contains(Match.Team1Acronym); + var team2Match = conditional.Acronyms.Contains(Match.Team2Acronym); + + if (team1Match && team2Match) + Match.Date.Value = conditional.Date.Value; + } + } + + Flow.Children = new[] + { + new DrawableMatchTeam(Match.Team1.Value, Match, Match.Losers.Value), + new DrawableMatchTeam(Match.Team2.Value, Match, Match.Losers.Value) + }; + + SchedulerAfterChildren.Add(() => Scheduler.Add(updateProgression)); + updateWinConditions(); + } + + protected override bool OnMouseDown(MouseDownEvent e) => e.Button == MouseButton.Left && editorInfo != null; + + protected override bool OnDragStart(DragStartEvent e) => editorInfo != null; + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (Selected && editorInfo != null && e.Key == Key.Delete) + { + Remove(); + return true; + } + + return base.OnKeyDown(e); + } + + protected override bool OnClick(ClickEvent e) + { + if (editorInfo == null || Match is ConditionalTournamentMatch) + return false; + + Selected = true; + return true; + } + + protected override bool OnDrag(DragEvent e) + { + if (base.OnDrag(e)) return true; + + Selected = true; + this.MoveToOffset(e.Delta); + + var pos = Position; + Match.Position.Value = new Point((int)pos.X, (int)pos.Y); + return true; + } + + public void Remove() + { + Selected = false; + Match.Progression.Value = null; + Match.LosersProgression.Value = null; + + ladderInfo.Matches.Remove(Match); + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs new file mode 100644 index 0000000000..dacd98d3b8 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Models; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class DrawableTournamentRound : CompositeDrawable + { + [UsedImplicitly] + private readonly Bindable name; + + [UsedImplicitly] + private readonly Bindable description; + + public DrawableTournamentRound(TournamentRound round, bool losers = false) + { + OsuSpriteText textName; + OsuSpriteText textDescription; + + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + textDescription = new OsuSpriteText + { + Colour = Color4.Black, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre + }, + textName = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Colour = Color4.Black, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre + }, + } + }; + + name = round.Name.GetBoundCopy(); + name.BindValueChanged(n => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpper(), true); + + description = round.Description.GetBoundCopy(); + description.BindValueChanged(n => textDescription.Text = round.Description.Value?.ToUpper(), true); + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs new file mode 100644 index 0000000000..0864d25a2f --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -0,0 +1,168 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; +using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class LadderEditorSettings : PlayerSettingsGroup + { + private const int padding = 10; + + protected override string Title => @"ladder"; + + private SettingsDropdown roundDropdown; + private PlayerCheckbox losersCheckbox; + private DateTextBox dateTimeBox; + private SettingsTeamDropdown team1Dropdown; + private SettingsTeamDropdown team2Dropdown; + + [Resolved] + private LadderEditorInfo editorInfo { get; set; } + + [Resolved] + private LadderInfo ladderInfo { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + team1Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 1" }, + team2Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 2" }, + roundDropdown = new SettingsRoundDropdown(ladderInfo.Rounds) { LabelText = "Round" }, + losersCheckbox = new PlayerCheckbox { LabelText = "Losers Bracket" }, + dateTimeBox = new DateTextBox { LabelText = "Match Time" }, + }; + + editorInfo.Selected.ValueChanged += selection => + { + roundDropdown.Bindable = selection.NewValue?.Round; + losersCheckbox.Current = selection.NewValue?.Losers; + dateTimeBox.Bindable = selection.NewValue?.Date; + + team1Dropdown.Bindable = selection.NewValue?.Team1; + team2Dropdown.Bindable = selection.NewValue?.Team2; + }; + + roundDropdown.Bindable.ValueChanged += round => + { + if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value) + { + editorInfo.Selected.Value.Date.Value = round.NewValue.StartDate.Value; + editorInfo.Selected.TriggerChange(); + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeIn(); + } + + protected override bool OnHover(HoverEvent e) + { + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + } + + private class SettingsRoundDropdown : LadderSettingsDropdown + { + public SettingsRoundDropdown(BindableList rounds) + { + Bindable = new Bindable(); + + foreach (var r in rounds.Prepend(new TournamentRound())) + add(r); + + rounds.ItemsRemoved += items => items.ForEach(i => Control.RemoveDropdownItem(i)); + rounds.ItemsAdded += items => items.ForEach(add); + } + + private readonly List refBindables = new List(); + + private T boundReference(T obj) + where T : IBindable + { + obj = (T)obj.GetBoundCopy(); + refBindables.Add(obj); + return obj; + } + + private void add(TournamentRound round) + { + Control.AddDropdownItem(round); + boundReference(round.Name).BindValueChanged(_ => + { + Control.RemoveDropdownItem(round); + Control.AddDropdownItem(round); + }); + } + } + + private class SettingsTeamDropdown : LadderSettingsDropdown + { + public SettingsTeamDropdown(BindableList teams) + { + foreach (var t in teams.Prepend(new TournamentTeam())) + add(t); + + teams.ItemsRemoved += items => items.ForEach(i => Control.RemoveDropdownItem(i)); + teams.ItemsAdded += items => items.ForEach(add); + } + + private readonly List refBindables = new List(); + + private T boundReference(T obj) + where T : IBindable + { + obj = (T)obj.GetBoundCopy(); + refBindables.Add(obj); + return obj; + } + + private void add(TournamentTeam team) + { + Control.AddDropdownItem(team); + boundReference(team.FullName).BindValueChanged(_ => + { + Control.RemoveDropdownItem(team); + Control.AddDropdownItem(team); + }); + } + } + + private class LadderSettingsDropdown : SettingsDropdown + { + protected override OsuDropdown CreateDropdown() => new DropdownControl(); + + private new class DropdownControl : SettingsDropdown.DropdownControl + { + protected override DropdownMenu CreateMenu() => new Menu(); + + private new class Menu : OsuDropdownMenu + { + public Menu() + { + MaxHeight = 200; + } + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs new file mode 100644 index 0000000000..34e0dc770f --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/Components/ProgressionPath.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Lines; +using osuTK; + +namespace osu.Game.Tournament.Screens.Ladder.Components +{ + public class ProgressionPath : Path + { + public DrawableTournamentMatch Source { get; private set; } + public DrawableTournamentMatch Destination { get; private set; } + + public ProgressionPath(DrawableTournamentMatch source, DrawableTournamentMatch destination) + { + Source = source; + Destination = destination; + + PathRadius = 3; + BypassAutoSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Vector2 getCenteredVector(Vector2 top, Vector2 bottom) => new Vector2(top.X, top.Y + (bottom.Y - top.Y) / 2); + + var q1 = Source.ScreenSpaceDrawQuad; + var q2 = Destination.ScreenSpaceDrawQuad; + + float padding = q1.Width / 20; + + bool progressionToRight = q2.TopLeft.X > q1.TopLeft.X; + + if (!progressionToRight) + { + var temp = q2; + q2 = q1; + q1 = temp; + } + + var c1 = getCenteredVector(q1.TopRight, q1.BottomRight) + new Vector2(padding, 0); + var c2 = getCenteredVector(q2.TopLeft, q2.BottomLeft) - new Vector2(padding, 0); + + var p1 = c1; + var p2 = p1 + new Vector2(padding, 0); + + if (p2.X > c2.X) + { + c2 = getCenteredVector(q2.TopRight, q2.BottomRight) + new Vector2(padding, 0); + p2.X = c2.X + padding; + } + + var p3 = new Vector2(p2.X, c2.Y); + var p4 = new Vector2(c2.X, p3.Y); + + var points = new[] { p1, p2, p3, p4 }; + + float minX = points.Min(p => p.X); + float minY = points.Min(p => p.Y); + + var topLeft = new Vector2(minX, minY); + + Position = Parent.ToLocalSpace(topLeft); + Vertices = points.Select(p => Parent.ToLocalSpace(p) - Parent.ToLocalSpace(topLeft)).ToList(); + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs new file mode 100644 index 0000000000..f613ce5f46 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/LadderDragContainer.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Input.Events; +using osuTK; + +namespace osu.Game.Tournament.Screens.Ladder +{ + public class LadderDragContainer : Container + { + protected override bool OnDragStart(DragStartEvent e) => true; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + private Vector2 target; + + private float scale = 1; + + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + + protected override bool OnDrag(DragEvent e) + { + this.MoveTo(target += e.Delta, 1000, Easing.OutQuint); + return true; + } + + private const float min_scale = 0.6f; + private const float max_scale = 1.4f; + + protected override bool OnScroll(ScrollEvent e) + { + var newScale = MathHelper.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); + + this.MoveTo(target = target - e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); + this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); + + return true; + } + } +} diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs new file mode 100644 index 0000000000..67531ce5d3 --- /dev/null +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -0,0 +1,174 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Caching; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Lines; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Editors; +using osu.Game.Tournament.Screens.Ladder.Components; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.Ladder +{ + public class LadderScreen : TournamentScreen, IProvideVideo + { + protected Container MatchesContainer; + private Container paths; + private Container headings; + + protected LadderDragContainer ScrollContent; + + protected Container Content; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, Storage storage) + { + normalPathColour = colours.BlueDarker.Darken(2); + losersPathColour = colours.YellowDarker.Darken(2); + + RelativeSizeAxes = Axes.Both; + + InternalChild = Content = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new TourneyVideo(storage.GetStream(@"BG Side Logo - OWC.m4v")) + { + RelativeSizeAxes = Axes.Both, + Loop = true, + }, + ScrollContent = new LadderDragContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + paths = new Container { RelativeSizeAxes = Axes.Both }, + headings = new Container { RelativeSizeAxes = Axes.Both }, + MatchesContainer = new Container { RelativeSizeAxes = Axes.Both }, + } + }, + } + }; + + void addMatch(TournamentMatch match) => + MatchesContainer.Add(new DrawableTournamentMatch(match, this is LadderEditorScreen) + { + Changed = () => layout.Invalidate() + }); + + foreach (var match in LadderInfo.Matches) + addMatch(match); + + LadderInfo.Rounds.ItemsAdded += _ => layout.Invalidate(); + LadderInfo.Rounds.ItemsRemoved += _ => layout.Invalidate(); + + LadderInfo.Matches.ItemsAdded += matches => + { + foreach (var p in matches) + addMatch(p); + layout.Invalidate(); + }; + + LadderInfo.Matches.ItemsRemoved += matches => + { + foreach (var p in matches) + foreach (var d in MatchesContainer.Where(d => d.Match == p)) + d.Expire(); + + layout.Invalidate(); + }; + } + + private Cached layout = new Cached(); + + protected override void Update() + { + base.Update(); + + if (!layout.IsValid) + UpdateLayout(); + } + + private Color4 normalPathColour; + private Color4 losersPathColour; + + protected virtual bool DrawLoserPaths => false; + + protected virtual void UpdateLayout() + { + paths.Clear(); + headings.Clear(); + + int id = 1; + + foreach (var match in MatchesContainer.OrderBy(d => d.Y).ThenBy(d => d.X)) + { + match.Match.ID = id++; + + if (match.Match.Progression.Value != null) + { + var dest = MatchesContainer.FirstOrDefault(p => p.Match == match.Match.Progression.Value); + + if (dest == null) + // clean up outdated progressions. + match.Match.Progression.Value = null; + else + paths.Add(new ProgressionPath(match, dest) { Colour = match.Match.Losers.Value ? losersPathColour : normalPathColour }); + } + + if (DrawLoserPaths) + { + if (match.Match.LosersProgression.Value != null) + { + var dest = MatchesContainer.FirstOrDefault(p => p.Match == match.Match.LosersProgression.Value); + + if (dest == null) + // clean up outdated progressions. + match.Match.LosersProgression.Value = null; + else + paths.Add(new ProgressionPath(match, dest) { Colour = losersPathColour.Opacity(0.1f) }); + } + } + } + + foreach (var round in LadderInfo.Rounds) + { + var topMatch = MatchesContainer.Where(p => !p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault(); + + if (topMatch == null) continue; + + headings.Add(new DrawableTournamentRound(round) + { + Position = headings.ToLocalSpace((topMatch.ScreenSpaceDrawQuad.TopLeft + topMatch.ScreenSpaceDrawQuad.TopRight) / 2), + Margin = new MarginPadding { Bottom = 10 }, + Origin = Anchor.BottomCentre, + }); + } + + foreach (var round in LadderInfo.Rounds) + { + var topMatch = MatchesContainer.Where(p => p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault(); + + if (topMatch == null) continue; + + headings.Add(new DrawableTournamentRound(round, true) + { + Position = headings.ToLocalSpace((topMatch.ScreenSpaceDrawQuad.TopLeft + topMatch.ScreenSpaceDrawQuad.TopRight) / 2), + Margin = new MarginPadding { Bottom = 10 }, + Origin = Anchor.BottomCentre, + }); + } + + layout.Validate(); + } + } +} diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs new file mode 100644 index 0000000000..46d4cfa98c --- /dev/null +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -0,0 +1,239 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Threading; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Gameplay; +using osu.Game.Tournament.Screens.Gameplay.Components; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Tournament.Screens.MapPool +{ + public class MapPoolScreen : TournamentScreen + { + private readonly FillFlowContainer> mapFlows; + + private readonly Bindable currentMatch = new Bindable(); + + [Resolved(canBeNull: true)] + private TournamentSceneManager sceneManager { get; set; } + + private TeamColour pickColour; + private ChoiceType pickType; + + private readonly OsuButton buttonRedBan; + private readonly OsuButton buttonBlueBan; + private readonly OsuButton buttonRedPick; + private readonly OsuButton buttonBluePick; + + public MapPoolScreen() + { + InternalChildren = new Drawable[] + { + new MatchHeader(), + mapFlows = new FillFlowContainer> + { + Y = 100, + Spacing = new Vector2(10, 20), + Padding = new MarginPadding(50), + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.Both, + }, + new ControlPanel + { + Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Current Mode" + }, + buttonRedBan = new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Red Ban", + Action = () => setMode(TeamColour.Red, ChoiceType.Ban) + }, + buttonBlueBan = new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Blue Ban", + Action = () => setMode(TeamColour.Blue, ChoiceType.Ban) + }, + buttonRedPick = new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Red Pick", + Action = () => setMode(TeamColour.Red, ChoiceType.Pick) + }, + buttonBluePick = new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Blue Pick", + Action = () => setMode(TeamColour.Blue, ChoiceType.Pick) + }, + new ControlPanel.Spacer(), + new OsuButton + { + RelativeSizeAxes = Axes.X, + Text = "Reset", + Action = reset + }, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(MatchIPCInfo ipc) + { + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(LadderInfo.CurrentMatch); + + ipc.Beatmap.BindValueChanged(beatmapChanged); + } + + private void beatmapChanged(ValueChangedEvent beatmap) + { + if (currentMatch.Value == null || currentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) < 2) + return; + + // if bans have already been placed, beatmap changes result in a selection being made autoamtically + if (beatmap.NewValue.OnlineBeatmapID != null) + addForBeatmap(beatmap.NewValue.OnlineBeatmapID.Value); + } + + private void setMode(TeamColour colour, ChoiceType choiceType) + { + pickColour = colour; + pickType = choiceType; + + Color4 setColour(bool active) => active ? Color4.White : Color4.Gray; + + buttonRedBan.Colour = setColour(pickColour == TeamColour.Red && pickType == ChoiceType.Ban); + buttonBlueBan.Colour = setColour(pickColour == TeamColour.Blue && pickType == ChoiceType.Ban); + buttonRedPick.Colour = setColour(pickColour == TeamColour.Red && pickType == ChoiceType.Pick); + buttonBluePick.Colour = setColour(pickColour == TeamColour.Blue && pickType == ChoiceType.Pick); + } + + private void setNextMode() + { + const TeamColour roll_winner = TeamColour.Red; //todo: draw from match + + var nextColour = (currentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; + + if (pickType == ChoiceType.Ban && currentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2) + setMode(pickColour, ChoiceType.Pick); + else + setMode(nextColour, currentMatch.Value.PicksBans.Count(p => p.Type == ChoiceType.Ban) >= 2 ? ChoiceType.Pick : ChoiceType.Ban); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + var maps = mapFlows.Select(f => f.FirstOrDefault(m => m.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))); + var map = maps.FirstOrDefault(m => m != null); + + if (map != null) + { + if (e.Button == MouseButton.Left && map.Beatmap.OnlineBeatmapID != null) + addForBeatmap(map.Beatmap.OnlineBeatmapID.Value); + else + { + var existing = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap.OnlineBeatmapID); + + if (existing != null) + { + currentMatch.Value.PicksBans.Remove(existing); + setNextMode(); + } + } + + return true; + } + + return base.OnMouseDown(e); + } + + private void reset() + { + currentMatch.Value.PicksBans.Clear(); + setNextMode(); + } + + private ScheduledDelegate scheduledChange; + + private void addForBeatmap(int beatmapId) + { + if (currentMatch.Value == null) + return; + + if (currentMatch.Value.Round.Value.Beatmaps.All(b => b.BeatmapInfo.OnlineBeatmapID != beatmapId)) + // don't attempt to add if the beatmap isn't in our pool + return; + + if (currentMatch.Value.PicksBans.Any(p => p.BeatmapID == beatmapId)) + // don't attempt to add if already exists. + return; + + currentMatch.Value.PicksBans.Add(new BeatmapChoice + { + Team = pickColour, + Type = pickType, + BeatmapID = beatmapId + }); + + setNextMode(); + + if (pickType == ChoiceType.Pick) + { + scheduledChange?.Cancel(); + scheduledChange = Scheduler.AddDelayed(() => { sceneManager?.SetScreen(typeof(GameplayScreen)); }, 10000); + } + } + + private void matchChanged(ValueChangedEvent match) + { + mapFlows.Clear(); + + if (match.NewValue.Round.Value != null) + { + FillFlowContainer currentFlow = null; + string currentMod = null; + + foreach (var b in match.NewValue.Round.Value.Beatmaps) + { + if (currentFlow == null || currentMod != b.Mods) + { + mapFlows.Add(currentFlow = new FillFlowContainer + { + Spacing = new Vector2(10, 20), + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }); + + currentMod = b.Mods; + } + + currentFlow.Add(new TournamentBeatmapPanel(b.BeatmapInfo, b.Mods) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }); + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs new file mode 100644 index 0000000000..4b46264055 --- /dev/null +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -0,0 +1,201 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Ladder.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.Schedule +{ + public class ScheduleScreen : TournamentScreen, IProvideVideo + { + private readonly Bindable currentMatch = new Bindable(); + private Container mainContainer; + private LadderInfo ladder; + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder, Storage storage) + { + this.ladder = ladder; + + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new TourneyVideo(storage.GetStream(@"BG Side Logo - OWC.m4v")) + { + RelativeSizeAxes = Axes.Both, + Loop = true, + }, + mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + } + }; + + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(ladder.CurrentMatch); + } + + private void matchChanged(ValueChangedEvent match) + { + if (match.NewValue == null) + { + mainContainer.Clear(); + return; + } + + var upcoming = ladder.Matches.Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); + var conditionals = ladder + .Matches.Where(p => !p.Completed.Value && (p.Team1.Value == null || p.Team2.Value == null) && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .SelectMany(m => m.ConditionalMatches.Where(cp => m.Acronyms.TrueForAll(a => cp.Acronyms.Contains(a)))); + + upcoming = upcoming.Concat(conditionals); + upcoming = upcoming.OrderBy(p => p.Date.Value).Take(12); + + mainContainer.Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Height = 0.65f, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ScheduleContainer("recent matches") + { + RelativeSizeAxes = Axes.Both, + Width = 0.4f, + ChildrenEnumerable = ladder.Matches + .Where(p => p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null + && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4) + .OrderByDescending(p => p.Date.Value) + .Take(8) + .Select(p => new ScheduleMatch(p)) + }, + new ScheduleContainer("match overview") + { + RelativeSizeAxes = Axes.Both, + Width = 0.6f, + ChildrenEnumerable = upcoming.Select(p => new ScheduleMatch(p)) + }, + } + } + }, + new ScheduleContainer("current match") + { + RelativeSizeAxes = Axes.Both, + Height = 0.25f, + Children = new Drawable[] + { + new OsuSpriteText + { + Margin = new MarginPadding { Left = -10, Bottom = 10, Top = -5 }, + Spacing = new Vector2(10, 0), + Text = match.NewValue.Round.Value?.Name.Value, + Colour = Color4.Black, + Font = OsuFont.GetFont(size: 20) + }, + new ScheduleMatch(match.NewValue, false), + new OsuSpriteText + { + Text = "Start Time " + match.NewValue.Date.Value.ToUniversalTime().ToString("HH:mm UTC"), + Colour = Color4.Black, + Font = OsuFont.GetFont(size: 20) + }, + } + } + } + }; + } + + public class ScheduleMatch : DrawableTournamentMatch + { + public ScheduleMatch(TournamentMatch match, bool showTimestamp = true) + : base(match) + { + Flow.Direction = FillDirection.Horizontal; + + bool conditional = match is ConditionalTournamentMatch; + + if (conditional) + Colour = OsuColour.Gray(0.5f); + + if (showTimestamp) + { + AddInternal(new DrawableDate(Match.Date.Value) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopLeft, + Colour = Color4.Black, + Alpha = conditional ? 0.6f : 1, + Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, + }); + AddInternal(new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomLeft, + Colour = Color4.Black, + Alpha = conditional ? 0.6f : 1, + Margin = new MarginPadding { Horizontal = 10, Vertical = 5 }, + Text = match.Date.Value.ToUniversalTime().ToString("HH:mm UTC") + (conditional ? " (conditional)" : "") + }); + } + } + } + + public class ScheduleContainer : Container + { + protected override Container Content => content; + + private readonly FillFlowContainer content; + + public ScheduleContainer(string title) + { + Padding = new MarginPadding { Left = 30, Top = 30 }; + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + X = 30, + Text = title, + Colour = Color4.Black, + Spacing = new Vector2(10, 0), + Font = OsuFont.GetFont(size: 30) + }, + content = new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.Both, + Margin = new MarginPadding(40) + }, + new Circle + { + Colour = new Color4(233, 187, 79, 255), + Width = 5, + RelativeSizeAxes = Axes.Y, + } + }; + } + } + } +} diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs new file mode 100644 index 0000000000..d809dfc994 --- /dev/null +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; + +namespace osu.Game.Tournament.Screens.Showcase +{ + public class ShowcaseScreen : BeatmapInfoScreen + { + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new TournamentLogo()); + } + } +} diff --git a/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs new file mode 100644 index 0000000000..1fee2b29e8 --- /dev/null +++ b/osu.Game.Tournament/Screens/Showcase/TournamentLogo.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Tournament.Screens.Showcase +{ + public class TournamentLogo : CompositeDrawable + { + public TournamentLogo(bool includeRoundBackground = true) + { + RelativeSizeAxes = Axes.X; + Margin = new MarginPadding { Vertical = 5 }; + + if (includeRoundBackground) + { + AutoSizeAxes = Axes.Y; + } + else + { + Masking = true; + Height = 100; + } + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + InternalChild = new Sprite + { + Texture = textures.Get("game-screen-logo"), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }; + } + } +} diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs new file mode 100644 index 0000000000..c901a5c7ef --- /dev/null +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -0,0 +1,221 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Showcase; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.TeamIntro +{ + public class TeamIntroScreen : TournamentScreen, IProvideVideo + { + private Container mainContainer; + + private readonly Bindable currentMatch = new Bindable(); + + [BackgroundDependencyLoader] + private void load(Storage storage) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new TourneyVideo(storage.GetStream(@"BG Team - Both OWC.m4v")) + { + RelativeSizeAxes = Axes.Both, + Loop = true, + }, + new TournamentLogo(false), + mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + } + }; + + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(LadderInfo.CurrentMatch); + } + + private void matchChanged(ValueChangedEvent match) + { + if (match.NewValue == null) + { + mainContainer.Clear(); + return; + } + + mainContainer.Children = new Drawable[] + { + new TeamWithPlayers(match.NewValue.Team1.Value, true) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight + }, + new TeamWithPlayers(match.NewValue.Team2.Value) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft + }, + new RoundDisplay(match.NewValue) + { + RelativeSizeAxes = Axes.Both, + Height = 0.25f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + } + }; + } + + private class RoundDisplay : CompositeDrawable + { + public RoundDisplay(TournamentMatch match) + { + var col = OsuColour.Gray(0.33f); + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = col, + Text = "COMING UP NEXT", + Spacing = new Vector2(2, 0), + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Black) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = col, + Text = match.Round.Value?.Name.Value ?? "Unknown Round", + Spacing = new Vector2(10, 0), + Font = OsuFont.GetFont(size: 50, weight: FontWeight.Light) + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = col, + Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), + Font = OsuFont.GetFont(size: 20) + }, + } + } + }; + } + } + + private class TeamWithPlayers : CompositeDrawable + { + private readonly Color4 red = new Color4(129, 68, 65, 255); + private readonly Color4 blue = new Color4(41, 91, 97, 255); + + public TeamWithPlayers(TournamentTeam team, bool left = false) + { + FillFlowContainer players; + var colour = left ? red : blue; + InternalChildren = new Drawable[] + { + new TeamDisplay(team, left ? "Team Red" : "Team Blue", colour) + { + Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + X = (left ? -1 : 1) * 0.36f, + }, + players = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(0, 5), + Padding = new MarginPadding(20), + Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, + RelativePositionAxes = Axes.Both, + X = (left ? -1 : 1) * 0.66f, + }, + }; + + if (team != null) + { + foreach (var p in team.Players) + players.Add(new OsuSpriteText + { + Text = p.Username, + Font = OsuFont.GetFont(size: 24), + Colour = colour, + Anchor = left ? Anchor.CentreRight : Anchor.CentreLeft, + Origin = left ? Anchor.CentreRight : Anchor.CentreLeft, + }); + } + } + + private class TeamDisplay : DrawableTournamentTeam + { + public TeamDisplay(TournamentTeam team, string teamName, Color4 colour) + : base(team) + { + AutoSizeAxes = Axes.Both; + + Flag.Anchor = Flag.Origin = Anchor.TopCentre; + Flag.RelativeSizeAxes = Axes.None; + Flag.Size = new Vector2(300, 200); + Flag.Scale = new Vector2(0.4f); + Flag.Margin = new MarginPadding { Bottom = 20 }; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + Flag, + new OsuSpriteText + { + Text = team?.FullName.Value.ToUpper() ?? "???", + Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 40, FontWeight.Light), + Colour = Color4.Black, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + new OsuSpriteText + { + Text = teamName.ToUpper(), + Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 20, FontWeight.Regular), + Colour = colour, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + } + } + }; + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs new file mode 100644 index 0000000000..efe4ee92fc --- /dev/null +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -0,0 +1,225 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens.Showcase; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament.Screens.TeamWin +{ + public class TeamWinScreen : TournamentScreen, IProvideVideo + { + private Container mainContainer; + + private readonly Bindable currentMatch = new Bindable(); + private readonly Bindable currentCompleted = new Bindable(); + + private TourneyVideo blueWinVideo; + private TourneyVideo redWinVideo; + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder, Storage storage) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + blueWinVideo = new TourneyVideo(storage.GetStream(@"BG Team - Win Blue.m4v")) + { + Alpha = 1, + RelativeSizeAxes = Axes.Both, + Loop = true, + }, + redWinVideo = new TourneyVideo(storage.GetStream(@"BG Team - Win Red.m4v")) + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Loop = true, + }, + new TournamentLogo + { + Y = 40, + }, + mainContainer = new Container + { + RelativeSizeAxes = Axes.Both, + } + }; + + currentMatch.BindValueChanged(matchChanged); + currentMatch.BindTo(ladder.CurrentMatch); + + currentCompleted.BindValueChanged(_ => update()); + } + + private void matchChanged(ValueChangedEvent match) + { + currentCompleted.UnbindBindings(); + currentCompleted.BindTo(match.NewValue.Completed); + + update(); + } + + private void update() + { + var match = currentMatch.Value; + + if (match.Winner == null) + { + mainContainer.Clear(); + return; + } + + bool redWin = match.Winner == match.Team1.Value; + redWinVideo.Alpha = redWin ? 1 : 0; + blueWinVideo.Alpha = redWin ? 0 : 1; + + mainContainer.Children = new Drawable[] + { + new TeamWithPlayers(match.Winner, redWin) + { + RelativeSizeAxes = Axes.Both, + Width = 0.5f, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + new RoundDisplay(match) + { + RelativeSizeAxes = Axes.Both, + Height = 0.25f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + } + }; + } + + private class RoundDisplay : CompositeDrawable + { + public RoundDisplay(TournamentMatch match) + { + var col = OsuColour.Gray(0.33f); + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = col, + Text = "WINNER", + Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 15, FontWeight.Regular), + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = col, + Text = match.Round.Value?.Name.Value ?? "Unknown Round", + Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 50, FontWeight.Light), + Spacing = new Vector2(10, 0), + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = col, + Text = match.Date.Value.ToUniversalTime().ToString("dd MMMM HH:mm UTC"), + Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 20, FontWeight.Light), + }, + } + } + }; + } + } + + private class TeamWithPlayers : CompositeDrawable + { + private readonly Color4 red = new Color4(129, 68, 65, 255); + private readonly Color4 blue = new Color4(41, 91, 97, 255); + + public TeamWithPlayers(TournamentTeam team, bool left = false) + { + var colour = left ? red : blue; + InternalChildren = new Drawable[] + { + new TeamDisplay(team, left ? "Team Red" : "Team Blue", colour) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(0, 5), + Padding = new MarginPadding(20), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + }, + }; + } + + private class TeamDisplay : DrawableTournamentTeam + { + public TeamDisplay(TournamentTeam team, string teamName, Color4 colour) + : base(team) + { + AutoSizeAxes = Axes.Both; + + Flag.Anchor = Flag.Origin = Anchor.TopCentre; + Flag.RelativeSizeAxes = Axes.None; + Flag.Size = new Vector2(300, 200); + Flag.Scale = new Vector2(0.4f); + Flag.Margin = new MarginPadding { Bottom = 20 }; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + Flag, + new OsuSpriteText + { + Text = team?.FullName.Value.ToUpper() ?? "???", + Font = TournamentFont.GetFont(TournamentTypeface.Aquatico, 40, FontWeight.Light), + Colour = Color4.Black, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + }, + new OsuSpriteText + { + Text = teamName.ToUpper(), + Font = OsuFont.GetFont(size: 20), + Colour = colour, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + } + } + }; + } + } + } + } +} diff --git a/osu.Game.Tournament/Screens/TournamentScreen.cs b/osu.Game.Tournament/Screens/TournamentScreen.cs new file mode 100644 index 0000000000..9d58ca2240 --- /dev/null +++ b/osu.Game.Tournament/Screens/TournamentScreen.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Tournament.Models; + +namespace osu.Game.Tournament.Screens +{ + public abstract class TournamentScreen : CompositeDrawable + { + [Resolved] + protected LadderInfo LadderInfo { get; private set; } + + protected TournamentScreen() + { + RelativeSizeAxes = Axes.Both; + } + + public override void Hide() + { + this.FadeOut(200); + } + + public override void Show() + { + this.FadeIn(200); + } + } +} diff --git a/osu.Game.Tournament/TournamentFont.cs b/osu.Game.Tournament/TournamentFont.cs new file mode 100644 index 0000000000..f9e60ff2bc --- /dev/null +++ b/osu.Game.Tournament/TournamentFont.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; + +namespace osu.Game.Tournament +{ + public static class TournamentFont + { + /// + /// The default font size. + /// + public const float DEFAULT_FONT_SIZE = 16; + + /// + /// Retrieves a . + /// + /// The font typeface. + /// The size of the text in local space. For a value of 16, a single line will have a height of 16px. + /// The font weight. + /// Whether the font is italic. + /// Whether all characters should be spaced the same distance apart. + /// The . + public static FontUsage GetFont(TournamentTypeface typeface = TournamentTypeface.Aquatico, float size = DEFAULT_FONT_SIZE, FontWeight weight = FontWeight.Medium, bool italics = false, bool fixedWidth = false) + => new FontUsage(GetFamilyString(typeface), size, GetWeightString(typeface, weight), italics, fixedWidth); + + /// + /// Retrieves the string representation of a . + /// + /// The . + /// The string representation. + public static string GetFamilyString(TournamentTypeface typeface) + { + switch (typeface) + { + case TournamentTypeface.Aquatico: + return "Aquatico"; + } + + return null; + } + + /// + /// Retrieves the string representation of a . + /// + /// The . + /// The . + /// The string representation of in the specified . + public static string GetWeightString(TournamentTypeface typeface, FontWeight weight) + => GetWeightString(GetFamilyString(typeface), weight); + + /// + /// Retrieves the string representation of a . + /// + /// The family string. + /// The . + /// The string representation of in the specified . + public static string GetWeightString(string family, FontWeight weight) + { + string weightString = weight.ToString(); + + // Only exo has an explicit "regular" weight, other fonts do not + if (weight == FontWeight.Regular && family != GetFamilyString(TournamentTypeface.Aquatico) && family != GetFamilyString(TournamentTypeface.Aquatico)) + weightString = string.Empty; + + return weightString; + } + } + + public enum TournamentTypeface + { + Aquatico + } +} diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs new file mode 100644 index 0000000000..7dbcf37af6 --- /dev/null +++ b/osu.Game.Tournament/TournamentGame.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Graphics.Cursor; + +namespace osu.Game.Tournament +{ + public class TournamentGame : TournamentGameBase + { + protected override void LoadComplete() + { + base.LoadComplete(); + + Add(new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new TournamentSceneManager() + }); + + // we don't want to show the menu cursor as it would appear on stream output. + MenuCursorContainer.Cursor.Alpha = 0; + } + } +} diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs new file mode 100644 index 0000000000..06fb52da77 --- /dev/null +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -0,0 +1,260 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Drawing; +using System.IO; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osuTK.Input; + +namespace osu.Game.Tournament +{ + public abstract class TournamentGameBase : OsuGameBase + { + private const string bracket_filename = "bracket.json"; + + private LadderInfo ladder; + + private Storage storage; + + private DependencyContainer dependencies; + + private Bindable windowSize; + private FileBasedIPC ipc; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + } + + [BackgroundDependencyLoader] + private void load(Storage storage, FrameworkConfigManager frameworkConfig) + { + Resources.AddStore(new DllResourceStore(@"osu.Game.Tournament.dll")); + + Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Regular")); + Fonts.AddStore(new GlyphStore(Resources, @"Resources/Fonts/Aquatico-Light")); + + Textures.AddStore(new TextureLoaderStore(new ResourceStore(new StorageBackedResourceStore(storage)))); + + this.storage = storage; + + windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); + + readBracket(); + + ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); + + dependencies.CacheAs(ipc = new FileBasedIPC()); + Add(ipc); + + Add(new OsuButton + { + Text = "Save Changes", + Width = 140, + Height = 50, + Depth = float.MinValue, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Padding = new MarginPadding(10), + Action = SaveChanges, + }); + } + + private void readBracket() + { + if (storage.Exists(bracket_filename)) + { + using (Stream stream = storage.GetStream(bracket_filename, FileAccess.Read, FileMode.Open)) + using (var sr = new StreamReader(stream)) + ladder = JsonConvert.DeserializeObject(sr.ReadToEnd()); + } + else + { + ladder = new LadderInfo(); + } + + dependencies.Cache(ladder); + + bool addedInfo = false; + + // assign teams + foreach (var match in ladder.Matches) + { + match.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == match.Team1Acronym); + match.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == match.Team2Acronym); + + foreach (var conditional in match.ConditionalMatches) + { + conditional.Team1.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == conditional.Team1Acronym); + conditional.Team2.Value = ladder.Teams.FirstOrDefault(t => t.Acronym.Value == conditional.Team2Acronym); + conditional.Round.Value = match.Round.Value; + } + } + + // assign progressions + foreach (var pair in ladder.Progressions) + { + var src = ladder.Matches.FirstOrDefault(p => p.ID == pair.SourceID); + var dest = ladder.Matches.FirstOrDefault(p => p.ID == pair.TargetID); + + if (src == null) + continue; + + if (dest != null) + { + if (pair.Losers) + src.LosersProgression.Value = dest; + else + src.Progression.Value = dest; + } + } + + // link matches to rounds + foreach (var round in ladder.Rounds) + foreach (var id in round.Matches) + { + var found = ladder.Matches.FirstOrDefault(p => p.ID == id); + + if (found != null) + { + found.Round.Value = round; + if (round.StartDate.Value > found.Date.Value) + found.Date.Value = round.StartDate.Value; + } + } + + addedInfo |= addPlayers(); + addedInfo |= addBeatmaps(); + + if (addedInfo) + SaveChanges(); + } + + /// + /// Add missing player info based on user IDs. + /// + /// + private bool addPlayers() + { + bool addedInfo = false; + + foreach (var t in ladder.Teams) + foreach (var p in t.Players) + if (string.IsNullOrEmpty(p.Username)) + { + var req = new GetUserRequest(p.Id); + req.Perform(API); + p.Username = req.Result.Username; + + addedInfo = true; + } + + return addedInfo; + } + + /// + /// Add missing beatmap info based on beatmap IDs + /// + private bool addBeatmaps() + { + bool addedInfo = false; + + foreach (var r in ladder.Rounds) + foreach (var b in r.Beatmaps) + if (b.BeatmapInfo == null) + { + var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); + req.Perform(API); + b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); + + addedInfo = true; + } + + return addedInfo; + } + + protected override void LoadComplete() + { + MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display + MenuCursorContainer.Cursor.Alpha = 0; + + base.LoadComplete(); + } + + protected override void Update() + { + base.Update(); + var minWidth = (int)(windowSize.Value.Height / 9f * 16 + 400); + + if (windowSize.Value.Width < minWidth) + { + // todo: can be removed after ppy/osu-framework#1975 + windowSize.Value = Host.Window.ClientSize = new Size(minWidth, windowSize.Value.Height); + } + } + + protected virtual void SaveChanges() + { + foreach (var r in ladder.Rounds) + r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList(); + + ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat( + ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true))) + .ToList(); + + using (var stream = storage.GetStream(bracket_filename, FileAccess.Write, FileMode.Create)) + using (var sw = new StreamWriter(stream)) + { + sw.Write(JsonConvert.SerializeObject(ladder, + new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + })); + } + } + + protected override UserInputManager CreateUserInputManager() => new TournamentInputManager(); + + private class TournamentInputManager : UserInputManager + { + protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button) + { + switch (button) + { + case MouseButton.Right: + return new RightMouseManager(button); + } + + return base.CreateButtonManagerFor(button); + } + + private class RightMouseManager : MouseButtonEventManager + { + public RightMouseManager(MouseButton button) + : base(button) + { + } + + public override bool EnableDrag => true; // allow right-mouse dragging for absolute scroll in scroll containers. + public override bool EnableClick => true; + public override bool ChangeFocusOnClick => false; + } + } + } +} diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs new file mode 100644 index 0000000000..4c255be463 --- /dev/null +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -0,0 +1,165 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Graphics.UserInterface; +using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; +using osu.Game.Tournament.Screens; +using osu.Game.Tournament.Screens.Drawings; +using osu.Game.Tournament.Screens.Editors; +using osu.Game.Tournament.Screens.Gameplay; +using osu.Game.Tournament.Screens.Ladder; +using osu.Game.Tournament.Screens.MapPool; +using osu.Game.Tournament.Screens.Schedule; +using osu.Game.Tournament.Screens.Showcase; +using osu.Game.Tournament.Screens.TeamIntro; +using osu.Game.Tournament.Screens.TeamWin; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tournament +{ + [Cached] + public class TournamentSceneManager : CompositeDrawable + { + private Container screens; + private TourneyVideo video; + + [Cached] + private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); + + private Container chatContainer; + + public TournamentSceneManager() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(LadderInfo ladder, Storage storage) + { + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + X = 200, + FillMode = FillMode.Fit, + FillAspectRatio = 16 / 9f, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Size = new Vector2(0.8f, 1), + //Masking = true, + Children = new Drawable[] + { + video = new TourneyVideo(storage.GetStream("BG Logoless - OWC.m4v")) + { + Loop = true, + RelativeSizeAxes = Axes.Both, + }, + screens = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new ScheduleScreen(), + new LadderScreen(), + new LadderEditorScreen(), + new TeamEditorScreen(), + new RoundEditorScreen(), + new ShowcaseScreen(), + new MapPoolScreen(), + new TeamIntroScreen(), + new DrawingsScreen(), + new GameplayScreen(), + new TeamWinScreen() + } + }, + chatContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = chat + }, + } + }, + new Container + { + RelativeSizeAxes = Axes.Y, + Width = 200, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, + new Container { RelativeSizeAxes = Axes.X, Height = 50 }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) }, + new Container { RelativeSizeAxes = Axes.X, Height = 50 }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) }, + new Container { RelativeSizeAxes = Axes.X, Height = 50 }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) }, + new Container { RelativeSizeAxes = Axes.X, Height = 50 }, + new OsuButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) }, + } + }, + }, + }, + }; + + SetScreen(typeof(ScheduleScreen)); + } + + public void SetScreen(Type screenType) + { + var screen = screens.FirstOrDefault(s => s.GetType() == screenType); + if (screen == null) return; + + foreach (var s in screens.Children) + { + if (s == screen) + { + s.Show(); + if (s is IProvideVideo) + video.FadeOut(200); + else + video.Show(); + } + else + s.Hide(); + } + + switch (screen) + { + case GameplayScreen _: + case MapPoolScreen _: + chatContainer.FadeIn(100); + break; + + default: + chatContainer.FadeOut(100); + break; + } + } + } +} diff --git a/osu.Game.Tournament/osu.Game.Tournament.csproj b/osu.Game.Tournament/osu.Game.Tournament.csproj new file mode 100644 index 0000000000..4790fcbcde --- /dev/null +++ b/osu.Game.Tournament/osu.Game.Tournament.csproj @@ -0,0 +1,16 @@ + + + + netstandard2.0 + Library + AnyCPU + true + tools for tournaments. + + + + + + + + \ No newline at end of file diff --git a/osu.Game.props b/osu.Game.props index 4bcac479a0..1a3c0aec3e 100644 --- a/osu.Game.props +++ b/osu.Game.props @@ -16,7 +16,7 @@ ppy Pty Ltd - ppy Pty Ltd 2007-2018 + Copyright (c) 2019 ppy Pty Ltd NU1701 diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs new file mode 100644 index 0000000000..23a74d3fa6 --- /dev/null +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; + +namespace osu.Game.Audio +{ + /// + /// Describes a gameplay hit sample. + /// + [Serializable] + public class HitSampleInfo : ISampleInfo + { + public const string HIT_WHISTLE = @"hitwhistle"; + public const string HIT_FINISH = @"hitfinish"; + public const string HIT_NORMAL = @"hitnormal"; + public const string HIT_CLAP = @"hitclap"; + + /// + /// An optional ruleset namespace. + /// + public string Namespace; + + /// + /// The bank to load the sample from. + /// + public string Bank; + + /// + /// The name of the sample to load. + /// + public string Name; + + /// + /// An optional suffix to provide priority lookup. Falls back to non-suffixed . + /// + public string Suffix; + + /// + /// The sample volume. + /// + public int Volume { get; set; } + + /// + /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). + /// + public virtual IEnumerable LookupNames + { + get + { + if (!string.IsNullOrEmpty(Namespace)) + { + if (!string.IsNullOrEmpty(Suffix)) + yield return $"{Namespace}/{Bank}-{Name}{Suffix}"; + + yield return $"{Namespace}/{Bank}-{Name}"; + } + + // check non-namespace as a fallback even when we have a namespace + if (!string.IsNullOrEmpty(Suffix)) + yield return $"{Bank}-{Name}{Suffix}"; + + yield return $"{Bank}-{Name}"; + } + } + + public HitSampleInfo Clone() => (HitSampleInfo)MemberwiseClone(); + } +} diff --git a/osu.Game/Audio/IPreviewTrackOwner.cs b/osu.Game/Audio/IPreviewTrackOwner.cs index f166096601..8ab93257a5 100644 --- a/osu.Game/Audio/IPreviewTrackOwner.cs +++ b/osu.Game/Audio/IPreviewTrackOwner.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Audio { /// - /// Interface for objects that can own s. + /// Interface for objects that can own s. /// /// /// s can cancel the currently playing through the diff --git a/osu.Game/Audio/ISampleInfo.cs b/osu.Game/Audio/ISampleInfo.cs new file mode 100644 index 0000000000..4f81d37e78 --- /dev/null +++ b/osu.Game/Audio/ISampleInfo.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; + +namespace osu.Game.Audio +{ + public interface ISampleInfo + { + /// + /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). + /// + IEnumerable LookupNames { get; } + + int Volume { get; } + } +} diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 3c9122b941..22ce7d4711 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -28,6 +28,8 @@ namespace osu.Game.Audio private void load() { track = GetTrack(); + if (track != null) + track.Completed += () => Schedule(Stop); } /// @@ -50,32 +52,30 @@ namespace osu.Game.Audio /// public bool IsRunning => track?.IsRunning ?? false; - protected override void Update() - { - base.Update(); - - // Todo: Track currently doesn't signal its completion, so we have to handle it manually - if (hasStarted && track.HasCompleted) - Stop(); - } - private ScheduledDelegate startDelegate; /// /// Starts playing this . /// - public void Start() => startDelegate = Schedule(() => + /// Whether the track is started or already playing. + public bool Start() { if (track == null) - return; + return false; - if (hasStarted) - return; - hasStarted = true; + startDelegate = Schedule(() => + { + if (hasStarted) + return; - track.Restart(); - Started?.Invoke(); - }); + hasStarted = true; + + track.Restart(); + Started?.Invoke(); + }); + + return true; + } /// /// Stops playing this . @@ -89,6 +89,7 @@ namespace osu.Game.Audio if (!hasStarted) return; + hasStarted = false; track.Stop(); diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index 07fbe86ff4..e12c46ef16 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -1,37 +1,40 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; namespace osu.Game.Audio { - /// - /// A central store for the retrieval of s. - /// public class PreviewTrackManager : Component { private readonly BindableDouble muteBindable = new BindableDouble(); private AudioManager audio; - private TrackManager trackManager; + private PreviewTrackStore trackStore; private TrackManagerPreviewTrack current; [BackgroundDependencyLoader] - private void load(AudioManager audio, FrameworkConfigManager config) + private void load(AudioManager audio) { - trackManager = new TrackManager(new OnlineStore()); + // this is a temporary solution to get around muting ourselves. + // todo: update this once we have a BackgroundTrackManager or similar. + trackStore = new PreviewTrackStore(new OnlineStore()); + + audio.AddItem(trackStore); + trackStore.AddAdjustment(AdjustableProperty.Volume, audio.VolumeTrack); this.audio = audio; - audio.AddItem(trackManager); - - config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume); } /// @@ -41,19 +44,19 @@ namespace osu.Game.Audio /// The playable . public PreviewTrack Get(BeatmapSetInfo beatmapSetInfo) { - var track = CreatePreviewTrack(beatmapSetInfo, trackManager); + var track = CreatePreviewTrack(beatmapSetInfo, trackStore); track.Started += () => { current?.Stop(); current = track; - audio.Track.AddAdjustment(AdjustableProperty.Volume, muteBindable); + audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); }; track.Stopped += () => { current = null; - audio.Track.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); + audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); }; return track; @@ -80,16 +83,16 @@ namespace osu.Game.Audio /// /// Creates the . /// - protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) => new TrackManagerPreviewTrack(beatmapSetInfo, trackManager); + protected virtual TrackManagerPreviewTrack CreatePreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackStore) => new TrackManagerPreviewTrack(beatmapSetInfo, trackStore); protected class TrackManagerPreviewTrack : PreviewTrack { public IPreviewTrackOwner Owner { get; private set; } private readonly BeatmapSetInfo beatmapSetInfo; - private readonly TrackManager trackManager; + private readonly ITrackStore trackManager; - public TrackManagerPreviewTrack(BeatmapSetInfo beatmapSetInfo, TrackManager trackManager) + public TrackManagerPreviewTrack(BeatmapSetInfo beatmapSetInfo, ITrackStore trackManager) { this.beatmapSetInfo = beatmapSetInfo; this.trackManager = trackManager; @@ -103,5 +106,46 @@ namespace osu.Game.Audio protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo?.OnlineBeatmapSetID}.mp3"); } + + private class PreviewTrackStore : AudioCollectionManager, ITrackStore + { + private readonly IResourceStore store; + + internal PreviewTrackStore(IResourceStore store) + { + this.store = store; + } + + public Track GetVirtual(double length = double.PositiveInfinity) + { + if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}"); + + var track = new TrackVirtual(length); + AddItem(track); + return track; + } + + public Track Get(string name) + { + if (IsDisposed) throw new ObjectDisposedException($"Cannot retrieve items for an already disposed {nameof(PreviewTrackStore)}"); + + if (string.IsNullOrEmpty(name)) return null; + + var dataStream = store.GetStream(name); + + if (dataStream == null) + return null; + + Track track = new TrackBass(dataStream); + AddItem(track); + return track; + } + + public Task GetAsync(string name) => Task.Run(() => Get(name)); + + public Stream GetStream(string name) => store.GetStream(name); + + public IEnumerable GetAvailableResources() => store.GetAvailableResources(); + } } } diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 4345d09e05..66c07209f3 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.cs @@ -1,65 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; namespace osu.Game.Audio { - [Serializable] - public class SampleInfo + /// + /// Describes a gameplay sample. + /// + public class SampleInfo : ISampleInfo { - public const string HIT_WHISTLE = @"hitwhistle"; - public const string HIT_FINISH = @"hitfinish"; - public const string HIT_NORMAL = @"hitnormal"; - public const string HIT_CLAP = @"hitclap"; + private readonly string sampleName; - /// - /// An optional ruleset namespace. - /// - public string Namespace; - - /// - /// The bank to load the sample from. - /// - public string Bank; - - /// - /// The name of the sample to load. - /// - public string Name; - - /// - /// An optional suffix to provide priority lookup. Falls back to non-suffixed . - /// - public string Suffix; - - /// - /// The sample volume. - /// - public int Volume; - - /// - /// Retrieve all possible filenames that can be used as a source, returned in order of preference (highest first). - /// - public virtual IEnumerable LookupNames + public SampleInfo(string sampleName) { - get - { - if (!string.IsNullOrEmpty(Namespace)) - { - if (!string.IsNullOrEmpty(Suffix)) - yield return $"{Namespace}/{Bank}-{Name}{Suffix}"; - yield return $"{Namespace}/{Bank}-{Name}"; - } - - // check non-namespace as a fallback even when we have a namespace - if (!string.IsNullOrEmpty(Suffix)) - yield return $"{Bank}-{Name}{Suffix}"; - yield return $"{Bank}-{Name}"; - } + this.sampleName = sampleName; } - public SampleInfo Clone() => (SampleInfo)MemberwiseClone(); + public IEnumerable LookupNames => new[] { sampleName }; + + public int Volume { get; set; } = 100; } } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 4ef7b28d49..4ebeee40bf 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 3cb7833a12..7922843626 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -16,6 +16,7 @@ namespace osu.Game.Beatmaps where T : HitObject { private event Action> ObjectConverted; + event Action> IBeatmapConverter.ObjectConverted { add => ObjectConverted += value; @@ -97,7 +98,7 @@ namespace osu.Game.Beatmaps protected abstract IEnumerable ValidConversionTypes { get; } /// - /// Creates the that will be returned by this . + /// Creates the that will be returned by this . /// protected virtual Beatmap CreateBeatmap() => new Beatmap(); diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 508232dbfe..8727431e0e 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -1,20 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.ComponentModel.DataAnnotations.Schema; -using Newtonsoft.Json; +using osu.Game.Database; namespace osu.Game.Beatmaps { - public class BeatmapDifficulty + public class BeatmapDifficulty : IHasPrimaryKey { /// /// The default value used for all difficulty settings except and . /// public const float DEFAULT_DIFFICULTY = 5; - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int ID { get; set; } public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; @@ -51,6 +48,7 @@ namespace osu.Game.Beatmaps return mid + (max - mid) * (difficulty - 5) / 5; if (difficulty < 5) return mid - (mid - min) * (5 - difficulty) / 5; + return mid; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3e1f3bdf54..3c082bb71e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; @@ -9,14 +10,13 @@ using Newtonsoft.Json; using osu.Game.Database; using osu.Game.IO.Serialization; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Beatmaps { [Serializable] public class BeatmapInfo : IEquatable, IJsonSerializable, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int ID { get; set; } public int BeatmapVersion; @@ -114,7 +114,12 @@ namespace osu.Game.Beatmaps [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } - public override string ToString() => $"{Metadata} [{Version}]"; + /// + /// Currently only populated for beatmap deletion. Use to query scores. + /// + public List Scores { get; set; } + + public override string ToString() => $"{Metadata} [{Version}]".Trim(); public bool Equals(BeatmapInfo other) { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 24c68d392b..fe8fef3e07 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -1,27 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Linq.Expressions; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Extensions; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.Beatmaps.Formats; using osu.Game.Database; -using osu.Game.Graphics; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; namespace osu.Game.Beatmaps @@ -29,7 +28,7 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// - public partial class BeatmapManager : ArchiveModelManager + public partial class BeatmapManager : DownloadableArchiveModelManager { /// /// Fired when a single difficulty has been hidden. @@ -41,53 +40,48 @@ namespace osu.Game.Beatmaps /// public event Action BeatmapRestored; - /// - /// Fired when a beatmap download begins. - /// - public event Action BeatmapDownloadBegan; - - /// - /// Fired when a beatmap download is interrupted, due to user cancellation or other failures. - /// - public event Action BeatmapDownloadFailed; - - /// - /// Fired when a beatmap load is requested (into the interactive game UI). - /// - public Action PresentBeatmap; - /// /// A default representation of a WorkingBeatmap to use when no beatmap is available. /// - public WorkingBeatmap DefaultBeatmap { private get; set; } + public readonly WorkingBeatmap DefaultBeatmap; public override string[] HandledExtensions => new[] { ".osz" }; + protected override string[] HashableFileTypes => new[] { ".osu" }; + protected override string ImportFromStablePath => "Songs"; private readonly RulesetStore rulesets; private readonly BeatmapStore beatmaps; - private readonly APIAccess api; - private readonly AudioManager audioManager; - private readonly List currentDownloads = new List(); + private readonly GameHost host; - public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null) - : base(storage, contextFactory, new BeatmapStore(contextFactory), importHost) + private readonly BeatmapUpdateQueue updateQueue; + + public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, + WorkingBeatmap defaultBeatmap = null) + : base(storage, contextFactory, api, new BeatmapStore(contextFactory), host) { + this.rulesets = rulesets; + this.audioManager = audioManager; + this.host = host; + + DefaultBeatmap = defaultBeatmap; + beatmaps = (BeatmapStore)ModelStore; beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); - this.rulesets = rulesets; - this.api = api; - this.audioManager = audioManager; + updateQueue = new BeatmapUpdateQueue(api); } - protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive) + protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => + new DownloadBeatmapSetRequest(set, minimiseDownloadSize); + + protected override Task Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive, CancellationToken cancellationToken = default) { if (archive != null) beatmapSet.Beatmaps = createBeatmapDifficulties(archive); @@ -101,148 +95,57 @@ namespace osu.Game.Beatmaps b.BeatmapSet = beatmapSet; } - validateOnlineIds(beatmapSet.Beatmaps); + validateOnlineIds(beatmapSet); - foreach (BeatmapInfo b in beatmapSet.Beatmaps) - fetchAndPopulateOnlineValues(b, beatmapSet.Beatmaps); + return updateQueue.UpdateAsync(beatmapSet, cancellationToken); + } + + protected override void PreImport(BeatmapSetInfo beatmapSet) + { + if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) + throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); // check if a set already exists with the same online id, delete if it does. if (beatmapSet.OnlineBeatmapSetID != null) { var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + if (existingOnlineId != null) { Delete(existingOnlineId); beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); - Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database); + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been purged."); } } } - private void validateOnlineIds(List beatmaps) + private void validateOnlineIds(BeatmapSetInfo beatmapSet) { - var beatmapIds = beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); + var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); - // ensure all IDs are unique in this set and none match existing IDs in the local beatmap store. - if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1) || QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).Any()) - // remove all online IDs if any problems were found. - beatmaps.ForEach(b => b.OnlineBeatmapID = null); - } - - protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) - { - // check if this beatmap has already been imported and exit early if so - var existingHashMatch = beatmaps.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); - if (existingHashMatch != null) + // ensure all IDs are unique + if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) { - Undelete(existingHashMatch); - return existingHashMatch; + resetIds(); + return; } - return null; - } + // find any existing beatmaps in the database that have matching online ids + var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList(); - /// - /// Downloads a beatmap. - /// This will post notifications tracking progress. - /// - /// The to be downloaded. - /// Whether the beatmap should be downloaded without video. Defaults to false. - /// Downloading can happen - public bool Download(BeatmapSetInfo beatmapSetInfo, bool noVideo = false) - { - var existing = GetExistingDownload(beatmapSetInfo); - - if (existing != null || api == null) return false; - - if (!api.LocalUser.Value.IsSupporter) + if (existingBeatmaps.Count > 0) { - PostNotification?.Invoke(new SimpleNotification - { - Icon = FontAwesome.fa_superpowers, - Text = "You gotta be an osu!supporter to download for now 'yo" - }); - return false; + // reset the import ids (to force a re-fetch) *unless* they match the candidate CheckForExisting set. + // we can ignore the case where the new ids are contained by the CheckForExisting set as it will either be used (import skipped) or deleted. + var existing = CheckForExisting(beatmapSet); + if (existing == null || existingBeatmaps.Any(b => !existing.Beatmaps.Contains(b))) + resetIds(); } - var downloadNotification = new DownloadNotification - { - CompletionText = $"Imported {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}!", - Text = $"Downloading {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}", - }; - - var request = new DownloadBeatmapSetRequest(beatmapSetInfo, noVideo); - - request.DownloadProgressed += progress => - { - downloadNotification.State = ProgressNotificationState.Active; - downloadNotification.Progress = progress; - }; - - request.Success += data => - { - downloadNotification.Text = $"Importing {beatmapSetInfo.Metadata.Artist} - {beatmapSetInfo.Metadata.Title}"; - - Task.Factory.StartNew(() => - { - BeatmapSetInfo importedBeatmap; - - // This gets scheduled back to the update thread, but we want the import to run in the background. - using (var stream = new MemoryStream(data)) - using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) - importedBeatmap = Import(archive); - - downloadNotification.CompletionClickAction = () => - { - PresentCompletedImport(importedBeatmap.Yield()); - return true; - }; - downloadNotification.State = ProgressNotificationState.Completed; - - currentDownloads.Remove(request); - }, TaskCreationOptions.LongRunning); - }; - - request.Failure += error => - { - BeatmapDownloadFailed?.Invoke(request); - - if (error is OperationCanceledException) return; - - downloadNotification.State = ProgressNotificationState.Cancelled; - Logger.Error(error, "Beatmap download failed!"); - currentDownloads.Remove(request); - }; - - downloadNotification.CancelRequested += () => - { - request.Cancel(); - currentDownloads.Remove(request); - downloadNotification.State = ProgressNotificationState.Cancelled; - return true; - }; - - currentDownloads.Add(request); - PostNotification?.Invoke(downloadNotification); - - // don't run in the main api queue as this is a long-running task. - Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); - BeatmapDownloadBegan?.Invoke(request); - return true; + void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); } - protected override void PresentCompletedImport(IEnumerable imported) - { - base.PresentCompletedImport(imported); - PresentBeatmap?.Invoke(imported.LastOrDefault()); - } - - /// - /// Get an existing download request if it exists. - /// - /// The whose download request is wanted. - /// The object if it exists, or null. - public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); + protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) => items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); /// /// Delete a beatmap difficulty. @@ -260,17 +163,20 @@ namespace osu.Game.Beatmaps /// Retrieve a instance for the provided /// /// The beatmap to lookup. - /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. + /// The currently loaded . Allows for optimisation where elements are shared with the new beatmap. May be returned if beatmapInfo requested matches /// A instance correlating to the provided . public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo, WorkingBeatmap previous = null) { + if (beatmapInfo?.ID > 0 && previous != null && previous.BeatmapInfo?.ID == beatmapInfo.ID) + return previous; + if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo) return DefaultBeatmap; if (beatmapInfo.Metadata == null) beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; - WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo, audioManager); + WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager); previous?.TransferTo(working); @@ -284,6 +190,18 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); + protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import) + { + if (!base.CanUndelete(existing, import)) + return false; + + var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + + // force re-import if we are not in a sane state. + return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); + } + /// /// Returns a list of all usable s. /// @@ -317,24 +235,13 @@ namespace osu.Game.Beatmaps /// Results from the provided query. public IQueryable QueryBeatmaps(Expression> query) => beatmaps.Beatmaps.AsNoTracking().Where(query); - /// - /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content. - /// - private string computeBeatmapSetHash(ArchiveReader reader) - { - // for now, concatenate all .osu files in the set to create a unique hash. - MemoryStream hashable = new MemoryStream(); - foreach (string file in reader.Filenames.Where(f => f.EndsWith(".osu"))) - using (Stream s = reader.GetStream(file)) - s.CopyTo(hashable); - - return hashable.ComputeSHA2Hash(); - } + protected override string HumanisedModelName => "beatmap"; protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); + if (string.IsNullOrEmpty(mapName)) { Logger.Log($"No beatmap files found in the beatmap archive ({reader.Name}).", LoggingTarget.Database); @@ -349,8 +256,8 @@ namespace osu.Game.Beatmaps { OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, Beatmaps = new List(), - Hash = computeBeatmapSetHash(reader), Metadata = beatmap.Metadata, + DateAdded = DateTimeOffset.UtcNow }; } @@ -364,7 +271,7 @@ namespace osu.Game.Beatmaps foreach (var name in reader.Filenames.Where(f => f.EndsWith(".osu"))) { using (var raw = reader.GetStream(name)) - using (var ms = new MemoryStream()) //we need a memory stream so we can seek and shit + using (var ms = new MemoryStream()) //we need a memory stream so we can seek using (var sr = new StreamReader(ms)) { raw.CopyTo(ms); @@ -389,55 +296,6 @@ namespace osu.Game.Beatmaps return beatmapInfos; } - /// - /// Query the API to populate missing values like OnlineBeatmapID / OnlineBeatmapSetID or (Rank-)Status. - /// - /// The beatmap to populate. - /// The other beatmaps contained within this set. - /// Whether to re-query if the provided beatmap already has populated values. - /// True if population was successful. - private bool fetchAndPopulateOnlineValues(BeatmapInfo beatmap, IEnumerable otherBeatmaps, bool force = false) - { - if (api?.State != APIState.Online) - return false; - - if (!force && beatmap.OnlineBeatmapID != null && beatmap.BeatmapSet.OnlineBeatmapSetID != null - && beatmap.Status != BeatmapSetOnlineStatus.None && beatmap.BeatmapSet.Status != BeatmapSetOnlineStatus.None) - return true; - - Logger.Log("Attempting online lookup for the missing values...", LoggingTarget.Database); - - try - { - var req = new GetBeatmapRequest(beatmap); - - req.Perform(api); - - var res = req.Result; - - Logger.Log($"Successfully mapped to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.", LoggingTarget.Database); - - beatmap.Status = res.Status; - beatmap.BeatmapSet.Status = res.BeatmapSet.Status; - - if (otherBeatmaps.Any(b => b.OnlineBeatmapID == res.OnlineBeatmapID)) - { - Logger.Log("Another beatmap in the same set already mapped to this ID. We'll skip adding it this time.", LoggingTarget.Database); - return false; - } - - beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmap.OnlineBeatmapID = res.OnlineBeatmapID; - - return true; - } - catch (Exception e) - { - Logger.Log($"Failed ({e})", LoggingTarget.Database); - return false; - } - } - /// /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. /// @@ -446,7 +304,7 @@ namespace osu.Game.Beatmaps private readonly IBeatmap beatmap; public DummyConversionBeatmap(IBeatmap beatmap) - : base(beatmap.BeatmapInfo) + : base(beatmap.BeatmapInfo, null) { this.beatmap = beatmap; } @@ -456,19 +314,53 @@ namespace osu.Game.Beatmaps protected override Track GetTrack() => null; } - private class DownloadNotification : ProgressNotification + private class BeatmapUpdateQueue { - public override bool IsImportant => false; + private readonly IAPIProvider api; - protected override Notification CreateCompletionNotification() => new SilencedProgressCompletionNotification - { - Activated = CompletionClickAction, - Text = CompletionText - }; + private const int update_queue_request_concurrency = 4; - private class SilencedProgressCompletionNotification : ProgressCompletionNotification + private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdateQueue)); + + public BeatmapUpdateQueue(IAPIProvider api) { - public override bool IsImportant => false; + this.api = api; + } + + public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) + { + if (api?.State != APIState.Online) + return Task.CompletedTask; + + LogForModel(beatmapSet, "Performing online lookups..."); + return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); + } + + // todo: expose this when we need to do individual difficulty lookups. + protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken) + => Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler); + + private void update(BeatmapSetInfo set, BeatmapInfo beatmap) + { + if (api?.State != APIState.Online) + return; + + var req = new GetBeatmapRequest(beatmap); + + req.Success += res => + { + LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); + + beatmap.Status = res.Status; + beatmap.BeatmapSet.Status = res.BeatmapSet.Status; + beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; + beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + }; + + req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap}", e); }; + + // intentionally blocking to limit web request concurrency + req.Perform(api); } } } diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 77ff53b893..5657b8fb8a 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.IO; @@ -20,13 +20,12 @@ namespace osu.Game.Beatmaps protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap { private readonly IResourceStore store; - private readonly AudioManager audioManager; - public BeatmapManagerWorkingBeatmap(IResourceStore store, BeatmapInfo beatmapInfo, AudioManager audioManager) - : base(beatmapInfo) + public BeatmapManagerWorkingBeatmap(IResourceStore store, TextureStore textureStore, BeatmapInfo beatmapInfo, AudioManager audioManager) + : base(beatmapInfo, audioManager) { this.store = store; - this.audioManager = audioManager; + this.textureStore = textureStore; } protected override IBeatmap GetBeatmap() @@ -42,9 +41,11 @@ namespace osu.Game.Beatmaps } } - private string getPathForFile(string filename) => BeatmapSetInfo.Files.First(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; + private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; - private LargeTextureStore textureStore; + private TextureStore textureStore; + + private ITrackStore trackStore; protected override bool BackgroundStillValid(Texture b) => false; // bypass lazy logic. we want to return a new background each time for refcounting purposes. @@ -55,7 +56,7 @@ namespace osu.Game.Beatmaps try { - return (textureStore ?? (textureStore = new LargeTextureStore(new TextureLoaderStore(store)))).Get(getPathForFile(Metadata.BackgroundFile)); + return textureStore.Get(getPathForFile(Metadata.BackgroundFile)); } catch { @@ -67,8 +68,7 @@ namespace osu.Game.Beatmaps { try { - var trackData = store.GetStream(getPathForFile(Metadata.AudioFile)); - return trackData == null ? null : new TrackBass(trackData); + return (trackStore ?? (trackStore = AudioManager.GetTrackStore(store))).Get(getPathForFile(Metadata.AudioFile)); } catch { @@ -76,6 +76,14 @@ namespace osu.Game.Beatmaps } } + public override void RecycleTrack() + { + base.RecycleTrack(); + + trackStore?.Dispose(); + trackStore = null; + } + public override void TransferTo(WorkingBeatmap other) { base.TransferTo(other); @@ -100,6 +108,7 @@ namespace osu.Game.Beatmaps protected override Storyboard GetStoryboard() { Storyboard storyboard; + try { using (var stream = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) @@ -130,9 +139,10 @@ namespace osu.Game.Beatmaps protected override Skin GetSkin() { Skin skin; + try { - skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager); + skin = new LegacyBeatmapSkin(BeatmapInfo, store, AudioManager); } catch (Exception e) { diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 57983ec568..001f319307 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; +using osu.Game.Database; using osu.Game.Users; namespace osu.Game.Beatmaps { [Serializable] - public class BeatmapMetadata : IEquatable + public class BeatmapMetadata : IEquatable, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Title { get; set; } @@ -34,8 +34,8 @@ namespace osu.Game.Beatmaps [Column("Author")] public string AuthorString { - get { return Author?.Username; } - set { Author = new User { Username = value }; } + get => Author?.Username; + set => Author = new User { Username = value }; } /// @@ -48,6 +48,7 @@ namespace osu.Game.Beatmaps [JsonProperty(@"tags")] public string Tags { get; set; } + public int PreviewTime { get; set; } public string AudioFile { get; set; } public string BackgroundFile { get; set; } @@ -72,15 +73,15 @@ namespace osu.Game.Beatmaps return false; return Title == other.Title - && TitleUnicode == other.TitleUnicode - && Artist == other.Artist - && ArtistUnicode == other.ArtistUnicode - && AuthorString == other.AuthorString - && Source == other.Source - && Tags == other.Tags - && PreviewTime == other.PreviewTime - && AudioFile == other.AudioFile - && BackgroundFile == other.BackgroundFile; + && TitleUnicode == other.TitleUnicode + && Artist == other.Artist + && ArtistUnicode == other.ArtistUnicode + && AuthorString == other.AuthorString + && Source == other.Source + && Tags == other.Tags + && PreviewTime == other.PreviewTime + && AudioFile == other.AudioFile + && BackgroundFile == other.BackgroundFile; } } } diff --git a/osu.Game/Beatmaps/BeatmapMetrics.cs b/osu.Game/Beatmaps/BeatmapMetrics.cs index 427c2b50ac..b164aa6b30 100644 --- a/osu.Game/Beatmaps/BeatmapMetrics.cs +++ b/osu.Game/Beatmaps/BeatmapMetrics.cs @@ -1,31 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using System; using Newtonsoft.Json; namespace osu.Game.Beatmaps { /// - /// Beatmap metrics based on acculumated online data from community plays. + /// Beatmap metrics based on accumulated online data from community plays. /// public class BeatmapMetrics { - /// - /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?). - /// - public IEnumerable Ratings { get; set; } - /// /// Points of failure on a relative time scale (usually 0..100). /// [JsonProperty(@"fail")] - public IEnumerable Fails { get; set; } + public int[] Fails { get; set; } = Array.Empty(); /// /// Points of retry on a relative time scale (usually 0..100). /// [JsonProperty(@"exit")] - public IEnumerable Retries { get; set; } + public int[] Retries { get; set; } = Array.Empty(); } } diff --git a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs index 3c1c2b10ce..faae74db88 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Beatmaps { diff --git a/osu.Game/Beatmaps/BeatmapProcessor.cs b/osu.Game/Beatmaps/BeatmapProcessor.cs index 9db2c5f08e..7a612893c9 100644 --- a/osu.Game/Beatmaps/BeatmapProcessor.cs +++ b/osu.Game/Beatmaps/BeatmapProcessor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Game.Rulesets.Objects; diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index 4fa286cbec..3a55dc1577 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -1,16 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetFileInfo : INamedFileInfo + public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int BeatmapSetInfoID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7a7d010a31..524ed0ed56 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; @@ -8,9 +9,8 @@ using osu.Game.Database; namespace osu.Game.Beatmaps { - public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete + public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } private int? onlineBeatmapSetID; @@ -21,6 +21,8 @@ namespace osu.Game.Beatmaps set => onlineBeatmapSetID = value > 0 ? value : null; } + public DateTimeOffset DateAdded { get; set; } + public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None; public BeatmapMetadata Metadata { get; set; } @@ -30,6 +32,9 @@ namespace osu.Game.Beatmaps [NotMapped] public BeatmapSetOnlineInfo OnlineInfo { get; set; } + [NotMapped] + public BeatmapSetMetrics Metrics { get; set; } + public double MaxStarDifficulty => Beatmaps?.Max(b => b.StarDifficulty) ?? 0; [NotMapped] @@ -37,12 +42,14 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - public string StoryboardFile => Files?.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename; + public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb"))?.Filename; public List Files { get; set; } public override string ToString() => Metadata?.ToString() ?? base.ToString(); public bool Protected { get; set; } + + public bool Equals(BeatmapSetInfo other) => OnlineBeatmapSetID == other?.OnlineBeatmapSetID; } } diff --git a/osu.Game/Beatmaps/BeatmapSetMetrics.cs b/osu.Game/Beatmaps/BeatmapSetMetrics.cs new file mode 100644 index 0000000000..51c5de19a6 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapSetMetrics.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; + +namespace osu.Game.Beatmaps +{ + public class BeatmapSetMetrics + { + /// + /// Total vote counts of user ratings on a scale of 0..10 where 0 is unused (probably will be fixed at API?). + /// + [JsonProperty("ratings")] + public int[] Ratings { get; set; } = Array.Empty(); + } +} diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs index 8f985306e3..ea3f0b61b9 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using Newtonsoft.Json; @@ -65,6 +65,11 @@ namespace osu.Game.Beatmaps /// The amount of people who have favourited this beatmap set. /// public int FavouriteCount { get; set; } + + /// + /// The availability of this beatmap set. + /// + public BeatmapSetOnlineAvailability Availability { get; set; } } public class BeatmapSetOnlineCovers @@ -84,4 +89,13 @@ namespace osu.Game.Beatmaps [JsonProperty(@"list@2x")] public string List { get; set; } } + + public class BeatmapSetOnlineAvailability + { + [JsonProperty(@"download_disabled")] + public bool DownloadDisabled { get; set; } + + [JsonProperty(@"more_information")] + public string ExternalLink { get; set; } + } } diff --git a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs index 4f97ee4133..5864975a2e 100644 --- a/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineStatus.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Beatmaps { diff --git a/osu.Game/Beatmaps/BeatmapStatistic.cs b/osu.Game/Beatmaps/BeatmapStatistic.cs index aebe2573d7..0745ec5222 100644 --- a/osu.Game/Beatmaps/BeatmapStatistic.cs +++ b/osu.Game/Beatmaps/BeatmapStatistic.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Beatmaps { public class BeatmapStatistic { - public FontAwesome Icon; + public IconUsage Icon; public string Content; public string Name; } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 5bdc42cdf3..a2279fdb14 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -12,7 +12,7 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing /// - public class BeatmapStore : MutableDatabaseBackedStore + public class BeatmapStore : MutableDatabaseBackedStoreWithFileIncludes { public event Action BeatmapHidden; public event Action BeatmapRestored; @@ -34,6 +34,7 @@ namespace osu.Game.Beatmaps Refresh(ref beatmap, Beatmaps); if (beatmap.Hidden) return false; + beatmap.Hidden = true; } @@ -53,6 +54,7 @@ namespace osu.Game.Beatmaps Refresh(ref beatmap, Beatmaps); if (!beatmap.Hidden) return false; + beatmap.Hidden = false; } @@ -62,17 +64,16 @@ namespace osu.Game.Beatmaps protected override IQueryable AddIncludesForDeletion(IQueryable query) => base.AddIncludesForDeletion(query) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Metadata); + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); protected override IQueryable AddIncludesForConsumption(IQueryable query) => base.AddIncludesForConsumption(query) .Include(s => s.Metadata) .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Files).ThenInclude(f => f.FileInfo); + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata); protected override void Purge(List items, OsuDbContext context) { diff --git a/osu.Game/Beatmaps/BindableBeatmap.cs b/osu.Game/Beatmaps/BindableBeatmap.cs index 34d9cd3d25..af627cc6a9 100644 --- a/osu.Game/Beatmaps/BindableBeatmap.cs +++ b/osu.Game/Beatmaps/BindableBeatmap.cs @@ -1,47 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using System.Diagnostics; -using JetBrains.Annotations; -using osu.Framework.Audio; -using osu.Framework.Audio.Track; -using osu.Framework.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps { /// - /// A for the beatmap. - /// This should be used sparingly in-favour of . + /// A for the beatmap. + /// This should be used sparingly in-favour of . /// - public abstract class BindableBeatmap : NonNullableBindable, IBindableBeatmap + public abstract class BindableBeatmap : NonNullableBindable { - private AudioManager audioManager; private WorkingBeatmap lastBeatmap; protected BindableBeatmap(WorkingBeatmap defaultValue) : base(defaultValue) { + BindValueChanged(b => updateAudioTrack(b.NewValue), true); } - /// - /// 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) + private void updateAudioTrack(WorkingBeatmap beatmap) { var trackLoaded = lastBeatmap?.TrackLoaded ?? false; @@ -55,21 +34,9 @@ namespace osu.Game.Beatmaps 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/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 9ed476d97c..abe7e5e803 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; @@ -12,17 +12,14 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double Time; + /// + /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. + /// + internal bool AutoGenerated; + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - /// - /// Whether this provides the same parametric changes as another . - /// Basically an equality check without considering the . - /// - /// The to compare to. - /// Whether this is equivalent to . - public virtual bool EquivalentTo(ControlPoint other) => true; - public bool Equals(ControlPoint other) - => EquivalentTo(other) && Time.Equals(other?.Time); + => Time.Equals(other?.Time); } } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index f064d53358..855084ad02 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index dc981cd934..a3e3121575 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osuTK; namespace osu.Game.Beatmaps.ControlPoints { - public class DifficultyControlPoint : ControlPoint + public class DifficultyControlPoint : ControlPoint, IEquatable { /// /// The speed multiplier at this control point. @@ -18,9 +19,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double speedMultiplier = 1; - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is DifficultyControlPoint difficulty - && SpeedMultiplier.Equals(difficulty.SpeedMultiplier); + public bool Equals(DifficultyControlPoint other) + => base.Equals(other) + && SpeedMultiplier.Equals(other?.SpeedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index dd9d568133..354d86dc13 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,9 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint + public class EffectControlPoint : ControlPoint, IEquatable { /// /// Whether this control point enables Kiai mode. @@ -15,10 +17,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public bool OmitFirstBarLine; - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is EffectControlPoint effect - && KiaiMode.Equals(effect.KiaiMode) - && OmitFirstBarLine.Equals(effect.OmitFirstBarLine); + public bool Equals(EffectControlPoint other) + => base.Equals(other) + && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 18680f9985..7bc7a9056d 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints { - public class SampleControlPoint : ControlPoint + public class SampleControlPoint : ControlPoint, IEquatable { public const string DEFAULT_BANK = "normal"; @@ -23,8 +24,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// Create a SampleInfo based on the sample settings in this control point. /// /// The name of the same. - /// A populated . - public SampleInfo GetSampleInfo(string sampleName = SampleInfo.HIT_NORMAL) => new SampleInfo + /// A populated . + public HitSampleInfo GetSampleInfo(string sampleName = HitSampleInfo.HIT_NORMAL) => new HitSampleInfo { Bank = SampleBank, Name = sampleName, @@ -32,22 +33,20 @@ namespace osu.Game.Beatmaps.ControlPoints }; /// - /// Applies and to a if necessary, returning the modified . + /// Applies and to a if necessary, returning the modified . /// - /// The . This will not be modified. - /// The modified . This does not share a reference with . - public virtual SampleInfo ApplyTo(SampleInfo sampleInfo) + /// The . This will not be modified. + /// The modified . This does not share a reference with . + public virtual HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo) { - var newSampleInfo = sampleInfo.Clone(); - newSampleInfo.Bank = sampleInfo.Bank ?? SampleBank; - newSampleInfo.Volume = sampleInfo.Volume > 0 ? sampleInfo.Volume : SampleVolume; + var newSampleInfo = hitSampleInfo.Clone(); + newSampleInfo.Bank = hitSampleInfo.Bank ?? SampleBank; + newSampleInfo.Volume = hitSampleInfo.Volume > 0 ? hitSampleInfo.Volume : SampleVolume; return newSampleInfo; } - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is SampleControlPoint sample - && SampleBank.Equals(sample.SampleBank) - && SampleVolume.Equals(sample.SampleVolume); + public bool Equals(SampleControlPoint other) + => base.Equals(other) + && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ecd7ff252c..e5815a3f3b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osuTK; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint + public class TimingControlPoint : ControlPoint, IEquatable { /// /// The time signature at this control point. @@ -24,10 +25,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double beatLength = 1000; - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is TimingControlPoint timing - && TimeSignature.Equals(timing.TimeSignature) - && BeatLength.Equals(timing.BeatLength); + public bool Equals(TimingControlPoint other) + => base.Equals(other) + && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength); } } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs index b747857e11..0c59eec1ef 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -13,8 +13,8 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapBackgroundSprite(WorkingBeatmap working) { - if (working == null) - throw new ArgumentNullException(nameof(working)); + if (working == null) + throw new ArgumentNullException(nameof(working)); this.working = working; } diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index 883c05f1e4..d0db7765c2 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(LargeTextureStore textures) { string resource = null; @@ -32,9 +32,11 @@ namespace osu.Game.Beatmaps.Drawables case BeatmapSetCoverType.Cover: resource = set.OnlineInfo.Covers.Cover; break; + case BeatmapSetCoverType.Card: resource = set.OnlineInfo.Covers.Card; break; + case BeatmapSetCoverType.List: resource = set.OnlineInfo.Covers.List; break; diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs deleted file mode 100644 index 160d081b8a..0000000000 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetDownloader.cs +++ /dev/null @@ -1,112 +0,0 @@ -// 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; -using osu.Game.Online.API.Requests; - -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; - - /// - /// Holds the current download state of the beatmap, whether is has already been downloaded, is in progress, or is not downloaded. - /// - public readonly Bindable DownloadState = new Bindable(); - - 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; - beatmaps.BeatmapDownloadBegan += downloadBegan; - beatmaps.BeatmapDownloadFailed += downloadFailed; - - // initial value - if (set.OnlineBeatmapSetID != null && beatmaps.QueryBeatmapSets(s => s.OnlineBeatmapSetID == set.OnlineBeatmapSetID && !s.DeletePending).Any()) - DownloadState.Value = DownloadStatus.Downloaded; - else if (beatmaps.GetExistingDownload(set) != null) - DownloadState.Value = DownloadStatus.Downloading; - else - DownloadState.Value = DownloadStatus.NotDownloaded; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmaps != null) - { - beatmaps.ItemAdded -= setAdded; - beatmaps.ItemRemoved -= setRemoved; - beatmaps.BeatmapDownloadBegan -= downloadBegan; - beatmaps.BeatmapDownloadFailed -= downloadFailed; - } - } - - /// - /// Begin downloading the associated beatmap set. - /// - /// True if downloading began. False if an existing download is active or completed. - public void Download() - { - if (DownloadState.Value > DownloadStatus.NotDownloaded) - return; - - if (beatmaps.Download(set, noVideo)) - { - // Only change state if download can happen - DownloadState.Value = DownloadStatus.Downloading; - } - } - - private void setAdded(BeatmapSetInfo s) => Schedule(() => - { - if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) - DownloadState.Value = DownloadStatus.Downloaded; - }); - - private void setRemoved(BeatmapSetInfo s) => Schedule(() => - { - if (s.OnlineBeatmapSetID == set.OnlineBeatmapSetID) - DownloadState.Value = DownloadStatus.NotDownloaded; - }); - - private void downloadBegan(DownloadBeatmapSetRequest d) - { - if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID) - DownloadState.Value = DownloadStatus.Downloading; - } - - private void downloadFailed(DownloadBeatmapSetRequest d) - { - if (d.BeatmapSet.OnlineBeatmapSetID == set.OnlineBeatmapSetID) - DownloadState.Value = DownloadStatus.NotDownloaded; - } - - public enum DownloadStatus - { - NotDownloaded, - Downloading, - Downloaded, - } - } -} diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs index a7dff1aefc..351e5df17a 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetOnlineStatusPill.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK.Graphics; @@ -22,6 +23,7 @@ namespace osu.Game.Beatmaps.Drawables { if (status == value) return; + status = value; Alpha = value == BeatmapSetOnlineStatus.None ? 0 : 1; @@ -31,8 +33,8 @@ namespace osu.Game.Beatmaps.Drawables public float TextSize { - get => statusText.TextSize; - set => statusText.TextSize = value; + get => statusText.Font.Size; + set => statusText.Font = statusText.Font.With(size: value); } public MarginPadding TextPadding @@ -58,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold) }, }; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs index 0d421ff690..26ffcca1ec 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyColouredContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -48,11 +48,12 @@ namespace osu.Game.Beatmaps.Drawables var rating = beatmap.StarDifficulty; - if (rating < 1.5) return DifficultyRating.Easy; - if (rating < 2.25) return DifficultyRating.Normal; - if (rating < 3.75) return DifficultyRating.Hard; - if (rating < 5.25) return DifficultyRating.Insane; - if (rating < 6.75) return DifficultyRating.Expert; + if (rating < 2.0) return DifficultyRating.Easy; + if (rating < 2.7) return DifficultyRating.Normal; + if (rating < 4.0) return DifficultyRating.Hard; + if (rating < 5.3) return DifficultyRating.Insane; + if (rating < 6.5) return DifficultyRating.Expert; + return DifficultyRating.ExpertPlus; } @@ -62,15 +63,20 @@ namespace osu.Game.Beatmaps.Drawables { case DifficultyRating.Easy: return palette.Green; + default: case DifficultyRating.Normal: return palette.Blue; + case DifficultyRating.Hard: return palette.Yellow; + case DifficultyRating.Insane: return palette.Pink; + case DifficultyRating.Expert: return palette.Purple; + case DifficultyRating.ExpertPlus: return palette.Gray0; } diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 14162c35c0..0a0ad28fdf 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -1,14 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -16,15 +18,16 @@ namespace osu.Game.Beatmaps.Drawables { public class DifficultyIcon : DifficultyColouredContainer { - private readonly BeatmapInfo beatmap; + private readonly RulesetInfo ruleset; - public DifficultyIcon(BeatmapInfo beatmap) + public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null) : base(beatmap) { if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); - this.beatmap = beatmap; + this.ruleset = ruleset ?? beatmap.Ruleset; + Size = new Vector2(20); } @@ -58,7 +61,7 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = beatmap.Ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } + Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } } }; } diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs new file mode 100644 index 0000000000..1fd3502799 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Beatmaps.Drawables +{ + /// + /// Display a beatmap background from a local source, but fallback to online source if not available. + /// + public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable + { + public readonly Bindable Beatmap = new Bindable(); + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + private readonly BeatmapSetCoverType beatmapSetCoverType; + + public UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover) + { + Beatmap.BindValueChanged(b => Model = b.NewValue); + this.beatmapSetCoverType = beatmapSetCoverType; + } + + /// + /// Delay before the background is unloaded while off-screen. + /// + protected virtual double UnloadDelay => 10000; + + protected override DelayedLoadWrapper CreateDelayedLoadWrapper(Func createContentFunc, double timeBeforeLoad) + => new DelayedLoadUnloadWrapper(createContentFunc, timeBeforeLoad, UnloadDelay); + + protected override Drawable CreateDrawable(BeatmapInfo model) + { + Drawable drawable = getDrawableForModel(model); + + drawable.RelativeSizeAxes = Axes.Both; + drawable.Anchor = Anchor.Centre; + drawable.Origin = Anchor.Centre; + drawable.FillMode = FillMode.Fill; + drawable.OnLoadComplete += d => d.FadeInFromZero(400); + + return drawable; + } + + private Drawable getDrawableForModel(BeatmapInfo model) + { + // prefer online cover where available. + if (model?.BeatmapSet?.OnlineInfo != null) + return new BeatmapSetCover(model.BeatmapSet, beatmapSetCoverType); + + return model?.ID > 0 + ? new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(model)) + : new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs index 9a1617b550..16eecb7198 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapSetCover.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osuTK.Graphics; +using osu.Game.Graphics; namespace osu.Game.Beatmaps.Drawables { @@ -13,12 +14,14 @@ namespace osu.Game.Beatmaps.Drawables private Drawable displayedCover; private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet { - get { return beatmapSet; } + get => beatmapSet; set { if (value == beatmapSet) return; + beatmapSet = value; if (IsLoaded) @@ -27,12 +30,14 @@ namespace osu.Game.Beatmaps.Drawables } private BeatmapSetCoverType coverType = BeatmapSetCoverType.Cover; + public BeatmapSetCoverType CoverType { - get { return coverType; } + get => coverType; set { if (value == coverType) return; + coverType = value; if (IsLoaded) @@ -45,7 +50,7 @@ namespace osu.Game.Beatmaps.Drawables Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, + Colour = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.1f)), }; } @@ -63,16 +68,19 @@ namespace osu.Game.Beatmaps.Drawables if (beatmapSet != null) { + BeatmapSetCover cover; + Add(displayedCover = new DelayedLoadWrapper( - new BeatmapSetCover(beatmapSet, coverType) + cover = new BeatmapSetCover(beatmapSet, coverType) { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), }) ); + + cover.OnLoadComplete += d => d.FadeInFromZero(400, Easing.Out); } } } diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 5c129f76ec..3a4c677bd1 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -1,9 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; +using osu.Framework.Audio; using osu.Framework.Audio.Track; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; @@ -15,9 +17,9 @@ namespace osu.Game.Beatmaps { public class DummyWorkingBeatmap : WorkingBeatmap { - private readonly OsuGameBase game; + private readonly TextureStore textures; - public DummyWorkingBeatmap(OsuGameBase game = null) + public DummyWorkingBeatmap(AudioManager audio, TextureStore textures) : base(new BeatmapInfo { Metadata = new BeatmapMetadata @@ -26,18 +28,23 @@ namespace osu.Game.Beatmaps Title = "no beatmaps available!" }, BeatmapSet = new BeatmapSetInfo(), - BaseDifficulty = new BeatmapDifficulty(), + BaseDifficulty = new BeatmapDifficulty + { + DrainRate = 0, + CircleSize = 0, + OverallDifficulty = 0, + }, Ruleset = new DummyRulesetInfo() - }) + }, audio) { - this.game = game; + this.textures = textures; } protected override IBeatmap GetBeatmap() => new Beatmap(); - protected override Texture GetBackground() => game?.Textures.Get(@"Backgrounds/bg4"); + protected override Texture GetBackground() => textures?.Get(@"Backgrounds/bg4"); - protected override Track GetTrack() => new TrackVirtual { Length = 1000 }; + protected override Track GetTrack() => GetVirtualTrack(); private class DummyRulesetInfo : RulesetInfo { @@ -47,7 +54,7 @@ namespace osu.Game.Beatmaps { public override IEnumerable GetModsFor(ModType type) => new Mod[] { }; - public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) + public override DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods) { throw new NotImplementedException(); } @@ -68,9 +75,18 @@ namespace osu.Game.Beatmaps private class DummyBeatmapConverter : IBeatmapConverter { public event Action> ObjectConverted; + public IBeatmap Beatmap { get; set; } + public bool CanConvert => true; - public IBeatmap Convert() => Beatmap; + + public IBeatmap Convert() + { + foreach (var obj in Beatmap.HitObjects) + ObjectConverted?.Invoke(obj, obj.Yield()); + + return Beatmap; + } } } } diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index 6f45718390..953e50eadc 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -49,6 +49,7 @@ namespace osu.Game.Beatmaps.Formats throw new IOException(@"Unknown decoder type"); string line; + do { line = stream.ReadLine()?.Trim(); diff --git a/osu.Game/Beatmaps/Formats/IHasComboColours.cs b/osu.Game/Beatmaps/Formats/IHasComboColours.cs index d47377dc8d..4c15cb96d1 100644 --- a/osu.Game/Beatmaps/Formats/IHasComboColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasComboColours.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osuTK.Graphics; diff --git a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs index a319d0e48e..8f6c7dc328 100644 --- a/osu.Game/Beatmaps/Formats/IHasCustomColours.cs +++ b/osu.Game/Beatmaps/Formats/IHasCustomColours.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osuTK.Graphics; diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index fba89b8ac1..d8482b200f 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.IO; using osu.Game.IO.Serialization; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 71b7a65ccb..3cd425ea44 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using System.Globalization; using System.IO; using System.Linq; using osu.Framework.IO.File; +using osu.Framework.Logging; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Beatmaps.ControlPoints; @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps.Formats public static void Register() { - AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(int.Parse(m.Split('v').Last()))); + AddDecoder(@"osu file format v", m => new LegacyBeatmapDecoder(Parsing.ParseInt(m.Split('v').Last()))); } /// @@ -70,21 +70,27 @@ namespace osu.Game.Beatmaps.Formats case Section.General: handleGeneral(strippedLine); return; + case Section.Editor: handleEditor(strippedLine); return; + case Section.Metadata: handleMetadata(line); return; + case Section.Difficulty: handleDifficulty(strippedLine); return; + case Section.Events: handleEvent(strippedLine); return; + case Section.TimingPoints: handleTimingPoint(strippedLine); return; + case Section.HitObjects: handleHitObject(strippedLine); return; @@ -98,57 +104,71 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"AudioFilename": metadata.AudioFile = FileSafety.PathStandardise(pair.Value); break; + case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); + beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); break; + case @"PreviewTime": - metadata.PreviewTime = getOffsetTime(int.Parse(pair.Value)); + metadata.PreviewTime = getOffsetTime(Parsing.ParseInt(pair.Value)); break; + case @"Countdown": - beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.Countdown = Parsing.ParseInt(pair.Value) == 1; break; + case @"SampleSet": defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; + case @"SampleVolume": - defaultSampleVolume = int.Parse(pair.Value); + defaultSampleVolume = Parsing.ParseInt(pair.Value); break; + case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); break; + case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); + beatmap.BeatmapInfo.RulesetID = Parsing.ParseInt(pair.Value); switch (beatmap.BeatmapInfo.RulesetID) { case 0: parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 1: parser = new Rulesets.Objects.Legacy.Taiko.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 2: parser = new Rulesets.Objects.Legacy.Catch.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; + case 3: parser = new Rulesets.Objects.Legacy.Mania.ConvertHitObjectParser(getOffsetTime(), FormatVersion); break; } break; + case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; + case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; + case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; + beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; } } @@ -162,17 +182,21 @@ namespace osu.Game.Beatmaps.Formats case @"Bookmarks": beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; + case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; + case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); + beatmap.BeatmapInfo.BeatDivisor = Parsing.ParseInt(pair.Value); break; + case @"GridSize": - beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); + beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); break; + case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; } } @@ -182,37 +206,47 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var metadata = beatmap.BeatmapInfo.Metadata; + switch (pair.Key) { case @"Title": metadata.Title = pair.Value; break; + case @"TitleUnicode": metadata.TitleUnicode = pair.Value; break; + case @"Artist": metadata.Artist = pair.Value; break; + case @"ArtistUnicode": metadata.ArtistUnicode = pair.Value; break; + case @"Creator": metadata.AuthorString = pair.Value; break; + case @"Version": beatmap.BeatmapInfo.Version = pair.Value; break; + case @"Source": beatmap.BeatmapInfo.Metadata.Source = pair.Value; break; + case @"Tags": beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; + case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); + beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); break; + case @"BeatmapSetID": - beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = int.Parse(pair.Value) }; + beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; break; } } @@ -222,25 +256,31 @@ namespace osu.Game.Beatmaps.Formats var pair = SplitKeyVal(line); var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.DrainRate = Parsing.ParseFloat(pair.Value); break; + case @"CircleSize": - difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.CircleSize = Parsing.ParseFloat(pair.Value); break; + case @"OverallDifficulty": - difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.OverallDifficulty = Parsing.ParseFloat(pair.Value); break; + case @"ApproachRate": - difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.ApproachRate = Parsing.ParseFloat(pair.Value); break; + case @"SliderMultiplier": - difficulty.SliderMultiplier = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.SliderMultiplier = Parsing.ParseDouble(pair.Value); break; + case @"SliderTickRate": - difficulty.SliderTickRate = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); + difficulty.SliderTickRate = Parsing.ParseDouble(pair.Value); break; } } @@ -259,11 +299,14 @@ namespace osu.Game.Beatmaps.Formats string filename = split[2].Trim('"'); beatmap.BeatmapInfo.Metadata.BackgroundFile = FileSafety.PathStandardise(filename); break; + case EventType.Break: + double start = getOffsetTime(Parsing.ParseDouble(split[1])); + var breakEvent = new BreakPeriod { - StartTime = getOffsetTime(double.Parse(split[1], NumberFormatInfo.InvariantInfo)), - EndTime = getOffsetTime(double.Parse(split[2], NumberFormatInfo.InvariantInfo)) + StartTime = start, + EndTime = Math.Max(start, getOffsetTime(Parsing.ParseDouble(split[2]))) }; if (!breakEvent.HasEffect) @@ -280,25 +323,25 @@ namespace osu.Game.Beatmaps.Formats { string[] split = line.Split(','); - double time = getOffsetTime(double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo)); - double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); + double time = getOffsetTime(Parsing.ParseDouble(split[0].Trim())); + double beatLength = Parsing.ParseDouble(split[1].Trim()); double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1; TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple; if (split.Length >= 3) - timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)int.Parse(split[2]); + timeSignature = split[2][0] == '0' ? TimeSignatures.SimpleQuadruple : (TimeSignatures)Parsing.ParseInt(split[2]); LegacySampleBank sampleSet = defaultSampleBank; if (split.Length >= 4) - sampleSet = (LegacySampleBank)int.Parse(split[3]); + sampleSet = (LegacySampleBank)Parsing.ParseInt(split[3]); int customSampleBank = 0; if (split.Length >= 5) - customSampleBank = int.Parse(split[4]); + customSampleBank = Parsing.ParseInt(split[4]); int sampleVolume = defaultSampleVolume; if (split.Length >= 6) - sampleVolume = int.Parse(split[5]); + sampleVolume = Parsing.ParseInt(split[5]); bool timingChange = true; if (split.Length >= 7) @@ -306,9 +349,10 @@ namespace osu.Game.Beatmaps.Formats bool kiaiMode = false; bool omitFirstBarSignature = false; + if (split.Length >= 8) { - EffectFlags effectFlags = (EffectFlags)int.Parse(split[7]); + EffectFlags effectFlags = (EffectFlags)Parsing.ParseInt(split[7]); kiaiMode = effectFlags.HasFlag(EffectFlags.Kiai); omitFirstBarSignature = effectFlags.HasFlag(EffectFlags.OmitFirstBarLine); } @@ -330,14 +374,16 @@ namespace osu.Game.Beatmaps.Formats handleDifficultyControlPoint(new DifficultyControlPoint { Time = time, - SpeedMultiplier = speedMultiplier + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange }); handleEffectControlPoint(new EffectControlPoint { Time = time, KiaiMode = kiaiMode, - OmitFirstBarLine = omitFirstBarSignature + OmitFirstBarLine = omitFirstBarSignature, + AutoGenerated = timingChange }); handleSampleControlPoint(new LegacySampleControlPoint @@ -345,11 +391,17 @@ namespace osu.Game.Beatmaps.Formats Time = time, SampleBank = stringSampleSet, SampleVolume = sampleVolume, - CustomSampleBank = customSampleBank + CustomSampleBank = customSampleBank, + AutoGenerated = timingChange }); } - catch (FormatException e) + catch (FormatException) { + Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + } + catch (OverflowException) + { + Logger.Log("A timing point could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } } @@ -358,7 +410,14 @@ namespace osu.Game.Beatmaps.Formats var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time); if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.TimingPoints.Remove(existing); + } beatmap.ControlPointInfo.TimingPoints.Add(newPoint); } @@ -367,11 +426,15 @@ namespace osu.Game.Beatmaps.Formats { var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); - if (newPoint.EquivalentTo(existing)) - return; - if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); + } beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); } @@ -380,11 +443,15 @@ namespace osu.Game.Beatmaps.Formats { var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); - if (newPoint.EquivalentTo(existing)) - return; - if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.EffectPoints.Remove(existing); + } beatmap.ControlPointInfo.EffectPoints.Add(newPoint); } @@ -393,11 +460,15 @@ namespace osu.Game.Beatmaps.Formats { var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); - if (newPoint.EquivalentTo(existing)) - return; - if (existing.Time == newPoint.Time) + { + // autogenerated points should not replace non-autogenerated. + // this allows for incorrectly ordered timing points to still be correctly handled. + if (newPoint.AutoGenerated && !existing.AutoGenerated) + return; + beatmap.ControlPointInfo.SamplePoints.Remove(existing); + } beatmap.ControlPointInfo.SamplePoints.Add(newPoint); } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 217f16b263..7999c82761 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -26,6 +26,7 @@ namespace osu.Game.Beatmaps.Formats Section section = Section.None; string line; + while ((line = stream.ReadLine()) != null) { if (ShouldSkipLine(line)) @@ -53,7 +54,7 @@ namespace osu.Game.Beatmaps.Formats } } - protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.StartsWith("//", StringComparison.Ordinal); + protected virtual bool ShouldSkipLine(string line) => string.IsNullOrWhiteSpace(line) || line.AsSpan().TrimStart().StartsWith("//".AsSpan(), StringComparison.Ordinal); protected virtual void ParseLine(T output, Section section, string line) { @@ -72,6 +73,7 @@ namespace osu.Game.Beatmaps.Formats var index = line.AsSpan().IndexOf("//".AsSpan()); if (index > 0) return line.Substring(0, index); + return line; } @@ -94,7 +96,7 @@ namespace osu.Game.Beatmaps.Formats { colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); } - catch (Exception e) + catch { throw new InvalidOperationException(@"Color must be specified with 8-bit integer components"); } @@ -115,6 +117,7 @@ namespace osu.Game.Beatmaps.Formats else { if (!(output is IHasCustomColours tHasCustomColours)) return; + tHasCustomColours.CustomColours[pair.Key] = colour; } } @@ -186,13 +189,13 @@ namespace osu.Game.Beatmaps.Formats Foreground = 3 } - internal class LegacySampleControlPoint : SampleControlPoint + internal class LegacySampleControlPoint : SampleControlPoint, IEquatable { public int CustomSampleBank; - public override SampleInfo ApplyTo(SampleInfo sampleInfo) + public override HitSampleInfo ApplyTo(HitSampleInfo hitSampleInfo) { - var baseInfo = base.ApplyTo(sampleInfo); + var baseInfo = base.ApplyTo(hitSampleInfo); if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 1) baseInfo.Suffix = CustomSampleBank.ToString(); @@ -200,10 +203,9 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public override bool EquivalentTo(ControlPoint other) - => base.EquivalentTo(other) - && other is LegacySampleControlPoint legacy - && CustomSampleBank == legacy.CustomSampleBank; + public bool Equals(LegacySampleControlPoint other) + => base.Equals(other) + && CustomSampleBank == other?.CustomSampleBank; } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 13a71aac3d..540f616ea9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index d30c78cb17..f6e2bf6966 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Globalization; using System.IO; +using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -38,6 +39,10 @@ namespace osu.Game.Beatmaps.Formats { this.storyboard = storyboard; base.ParseStreamInto(stream, storyboard); + + // OrderBy is used to guarantee that the parsing order of elements with equal start times is maintained (stably-sorted) + foreach (StoryboardLayer layer in storyboard.Layers) + layer.Elements = layer.Elements.OrderBy(h => h.StartTime).ToList(); } protected override void ParseLine(Storyboard storyboard, Section section, string line) @@ -49,6 +54,7 @@ namespace osu.Game.Beatmaps.Formats case Section.Events: handleEvents(line); return; + case Section.Variables: handleVariables(line); return; @@ -60,6 +66,7 @@ namespace osu.Game.Beatmaps.Formats private void handleEvents(string line) { var depth = 0; + while (line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal)) { ++depth; @@ -89,8 +96,9 @@ namespace osu.Game.Beatmaps.Formats var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo); storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y)); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Animation: { var layer = parseLayer(split[1]); @@ -103,8 +111,9 @@ namespace osu.Game.Beatmaps.Formats var loopType = split.Length > 8 ? (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), split[8]) : AnimationLoopType.LoopForever; storyboardSprite = new StoryboardAnimation(path, origin, new Vector2(x, y), frameCount, frameDelay, loopType); storyboard.GetLayer(layer).Add(storyboardSprite); - } break; + } + case EventType.Sample: { var time = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -112,8 +121,8 @@ namespace osu.Game.Beatmaps.Formats var path = cleanFilename(split[3]); var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100; storyboard.GetLayer(layer).Add(new StoryboardSample(path, time, volume)); - } break; + } } } else @@ -122,6 +131,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.TimelineGroup; var commandType = split[0]; + switch (commandType) { case "T": @@ -133,6 +143,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddTrigger(triggerName, startTime, endTime, groupNumber); } break; + case "L": { var startTime = double.Parse(split[1], CultureInfo.InvariantCulture); @@ -140,6 +151,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); } break; + default: { if (string.IsNullOrEmpty(split[3])) @@ -158,6 +170,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Alpha.Add(easing, startTime, endTime, startValue, endValue); } break; + case "S": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -165,6 +178,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startValue), new Vector2(endValue)); } break; + case "V": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -174,6 +188,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Scale.Add(easing, startTime, endTime, new Vector2(startX, startY), new Vector2(endX, endY)); } break; + case "R": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -181,6 +196,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Rotation.Add(easing, startTime, endTime, MathHelper.RadiansToDegrees(startValue), MathHelper.RadiansToDegrees(endValue)); } break; + case "M": { var startX = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -191,6 +207,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startY, endY); } break; + case "MX": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -198,6 +215,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.X.Add(easing, startTime, endTime, startValue, endValue); } break; + case "MY": { var startValue = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -205,6 +223,7 @@ namespace osu.Game.Beatmaps.Formats timelineGroup?.Y.Add(easing, startTime, endTime, startValue, endValue); } break; + case "C": { var startRed = float.Parse(split[4], CultureInfo.InvariantCulture); @@ -218,23 +237,28 @@ namespace osu.Game.Beatmaps.Formats new Color4(endRed / 255f, endGreen / 255f, endBlue / 255f, 1)); } break; + case "P": { var type = split[4]; + switch (type) { case "A": timelineGroup?.BlendingMode.Add(easing, startTime, endTime, BlendingMode.Additive, startTime == endTime ? BlendingMode.Additive : BlendingMode.Inherit); break; + case "H": timelineGroup?.FlipH.Add(easing, startTime, endTime, true, startTime == endTime); break; + case "V": timelineGroup?.FlipV.Add(easing, startTime, endTime, true, startTime == endTime); break; } } break; + default: throw new InvalidDataException($@"Unknown command type: {commandType}"); } @@ -249,26 +273,36 @@ namespace osu.Game.Beatmaps.Formats private Anchor parseOrigin(string value) { var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + switch (origin) { case LegacyOrigins.TopLeft: return Anchor.TopLeft; + case LegacyOrigins.TopCentre: return Anchor.TopCentre; + case LegacyOrigins.TopRight: return Anchor.TopRight; + case LegacyOrigins.CentreLeft: return Anchor.CentreLeft; + case LegacyOrigins.Centre: return Anchor.Centre; + case LegacyOrigins.CentreRight: return Anchor.CentreRight; + case LegacyOrigins.BottomLeft: return Anchor.BottomLeft; + case LegacyOrigins.BottomCentre: return Anchor.BottomCentre; + case LegacyOrigins.BottomRight: return Anchor.BottomRight; + default: return Anchor.TopLeft; } diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs new file mode 100644 index 0000000000..c3efb8c760 --- /dev/null +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; + +namespace osu.Game.Beatmaps.Formats +{ + /// + /// Helper methods to parse from string to number and perform very basic validation. + /// + public static class Parsing + { + public const int MAX_COORDINATE_VALUE = 65536; + + public const double MAX_PARSE_VALUE = int.MaxValue; + + public static float ParseFloat(string input, float parseLimit = (float)MAX_PARSE_VALUE) + { + var output = float.Parse(input, CultureInfo.InvariantCulture); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + if (float.IsNaN(output)) throw new FormatException("Not a number"); + + return output; + } + + public static double ParseDouble(string input, double parseLimit = MAX_PARSE_VALUE) + { + var output = double.Parse(input, CultureInfo.InvariantCulture); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + if (double.IsNaN(output)) throw new FormatException("Not a number"); + + return output; + } + + public static int ParseInt(string input, int parseLimit = (int)MAX_PARSE_VALUE) + { + var output = int.Parse(input, CultureInfo.InvariantCulture); + + if (output < -parseLimit) throw new OverflowException("Value is too low"); + if (output > parseLimit) throw new OverflowException("Value is too high"); + + return output; + } + } +} diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3d8b94dc46..512fe25809 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game/Beatmaps/IBeatmapConverter.cs b/osu.Game/Beatmaps/IBeatmapConverter.cs index a710afad16..f2213b85f1 100644 --- a/osu.Game/Beatmaps/IBeatmapConverter.cs +++ b/osu.Game/Beatmaps/IBeatmapConverter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game/Beatmaps/IBeatmapProcessor.cs b/osu.Game/Beatmaps/IBeatmapProcessor.cs index 4d634d7912..014dccf5e3 100644 --- a/osu.Game/Beatmaps/IBeatmapProcessor.cs +++ b/osu.Game/Beatmaps/IBeatmapProcessor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; diff --git a/osu.Game/Beatmaps/IBindableBeatmap.cs b/osu.Game/Beatmaps/IBindableBeatmap.cs deleted file mode 100644 index 329c0b6a3c..0000000000 --- a/osu.Game/Beatmaps/IBindableBeatmap.cs +++ /dev/null @@ -1,19 +0,0 @@ -// 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/Beatmaps/Legacy/LegacyMods.cs b/osu.Game/Beatmaps/Legacy/LegacyMods.cs index 0983610ba0..8e53c24e7b 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyMods.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyMods.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; diff --git a/osu.Game/Beatmaps/Timing/BreakPeriod.cs b/osu.Game/Beatmaps/Timing/BreakPeriod.cs index 0524704b8c..856a5fefd4 100644 --- a/osu.Game/Beatmaps/Timing/BreakPeriod.cs +++ b/osu.Game/Beatmaps/Timing/BreakPeriod.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Beatmaps.Timing { @@ -29,5 +29,12 @@ namespace osu.Game.Beatmaps.Timing /// Whether the break has any effect. Breaks that are too short are culled before they are added to the beatmap. /// public bool HasEffect => Duration >= MIN_BREAK_DURATION; + + /// + /// Whether this break contains a specified time. + /// + /// The time to check in milliseconds. + /// Whether the time falls within this . + public bool Contains(double time) => time >= StartTime && time <= EndTime; } } diff --git a/osu.Game/Beatmaps/Timing/TimeSignatures.cs b/osu.Game/Beatmaps/Timing/TimeSignatures.cs index aa25c76fc5..147f6239b4 100644 --- a/osu.Game/Beatmaps/Timing/TimeSignatures.cs +++ b/osu.Game/Beatmaps/Timing/TimeSignatures.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Beatmaps.Timing { diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 5b76122616..328763fc9f 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -1,26 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio.Track; -using osu.Framework.Configuration; using osu.Framework.Graphics.Textures; using osu.Game.Rulesets.Mods; using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Storyboards; using osu.Framework.IO.File; using System.IO; +using System.Linq; using System.Threading; +using osu.Framework.Audio; using osu.Game.IO.Serialization; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.UI; using osu.Game.Skinning; namespace osu.Game.Beatmaps { - public abstract partial class WorkingBeatmap : IDisposable + public abstract class WorkingBeatmap : IDisposable { public readonly BeatmapInfo BeatmapInfo; @@ -28,16 +29,15 @@ namespace osu.Game.Beatmaps public readonly BeatmapMetadata Metadata; - public readonly Bindable> Mods = new Bindable>(new Mod[] { }); + protected AudioManager AudioManager { get; } - protected WorkingBeatmap(BeatmapInfo beatmapInfo) + protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) { + AudioManager = audioManager; BeatmapInfo = beatmapInfo; BeatmapSetInfo = beatmapInfo.BeatmapSet; Metadata = beatmapInfo.Metadata ?? BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - Mods.ValueChanged += mods => applyRateAdjustments(); - beatmap = new RecyclableLazy(() => { var b = GetBeatmap() ?? new Beatmap(); @@ -51,20 +51,39 @@ namespace osu.Game.Beatmaps return b; }); - track = new RecyclableLazy(() => - { - // we want to ensure that we always have a track, even if it's a fake one. - var t = GetTrack() ?? new VirtualBeatmapTrack(Beatmap); - applyRateAdjustments(t); - return t; - }); - + track = new RecyclableLazy(() => GetTrack() ?? GetVirtualTrack()); background = new RecyclableLazy(GetBackground, BackgroundStillValid); waveform = new RecyclableLazy(GetWaveform); storyboard = new RecyclableLazy(GetStoryboard); skin = new RecyclableLazy(GetSkin); } + protected virtual Track GetVirtualTrack() + { + const double excess_length = 1000; + + var lastObject = Beatmap.HitObjects.LastOrDefault(); + + double length; + + switch (lastObject) + { + case null: + length = excess_length; + break; + + case IHasEndTime endTime: + length = endTime.EndTime + excess_length; + break; + + default: + length = lastObject.StartTime + excess_length; + break; + } + + return AudioManager.Tracks.GetVirtual(length); + } + /// /// Saves the . /// @@ -85,9 +104,10 @@ namespace osu.Game.Beatmaps /// /// /// The to create a playable for. + /// The s to apply to the . /// The converted . /// If could not be converted to . - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset) + public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods) { var rulesetInstance = ruleset.CreateInstance(); @@ -98,19 +118,19 @@ namespace osu.Game.Beatmaps throw new BeatmapInvalidForRulesetException($"{nameof(Beatmaps.Beatmap)} can not be converted for the ruleset (ruleset: {ruleset.InstantiationInfo}, converter: {converter})."); // Apply conversion mods - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToBeatmapConverter(converter); // Convert IBeatmap converted = converter.Convert(); // Apply difficulty mods - if (Mods.Value.Any(m => m is IApplicableToDifficulty)) + if (mods.Any(m => m is IApplicableToDifficulty)) { converted.BeatmapInfo = converted.BeatmapInfo.Clone(); converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone(); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); } @@ -122,7 +142,7 @@ namespace osu.Game.Beatmaps foreach (var obj in converted.HitObjects) obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in mods.OfType()) foreach (var obj in converted.HitObjects) mod.ApplyToHitObject(obj); @@ -151,7 +171,7 @@ namespace osu.Game.Beatmaps public bool WaveformLoaded => waveform.IsResultAvailable; public Waveform Waveform => waveform.Value; - protected virtual Waveform GetWaveform() => new Waveform(); + protected virtual Waveform GetWaveform() => new Waveform(null); private readonly RecyclableLazy waveform; public bool StoryboardLoaded => storyboard.IsResultAvailable; @@ -161,6 +181,7 @@ namespace osu.Game.Beatmaps public bool SkinLoaded => skin.IsResultAvailable; public Skin Skin => skin.Value; + protected virtual Skin GetSkin() => new DefaultSkin(); private readonly RecyclableLazy skin; @@ -186,17 +207,7 @@ namespace osu.Game.Beatmaps /// Eagerly dispose of the audio track associated with this (if any). /// Accessing track again will load a fresh instance. /// - public void RecycleTrack() => track.Recycle(); - - private void applyRateAdjustments(Track t = null) - { - if (t == null && track.IsResultAvailable) t = Track; - if (t == null) return; - - t.ResetSpeedAdjustments(); - foreach (var mod in Mods.Value.OfType()) - mod.ApplyToClock(t); - } + public virtual void RecycleTrack() => track.Recycle(); public class RecyclableLazy { diff --git a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs b/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs deleted file mode 100644 index d2e6ac1db8..0000000000 --- a/osu.Game/Beatmaps/WorkingBeatmap_VirtualBeatmapTrack.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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.Audio.Track; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Types; - -namespace osu.Game.Beatmaps -{ - public partial class WorkingBeatmap - { - /// - /// A type of which provides a valid length based on the s of an . - /// - protected class VirtualBeatmapTrack : TrackVirtual - { - private const double excess_length = 1000; - - public VirtualBeatmapTrack(IBeatmap beatmap) - { - var lastObject = beatmap.HitObjects.LastOrDefault(); - - switch (lastObject) - { - case null: - Length = excess_length; - break; - case IHasEndTime endTime: - Length = endTime.EndTime + excess_length; - break; - default: - Length = lastObject.StartTime + excess_length; - break; - } - } - } - } -} diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 0ede6de0f2..d5cdd7e4bc 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Game.Rulesets; @@ -19,6 +20,8 @@ namespace osu.Game.Configuration private readonly RulesetInfo ruleset; + private readonly bool legacySettingsExist; + protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int? variant = null) { this.settings = settings; @@ -26,6 +29,7 @@ namespace osu.Game.Configuration this.variant = variant; databasedSettings = settings.Query(ruleset?.ID, variant); + legacySettingsExist = databasedSettings.Any(s => int.TryParse(s.Key, out var _)); InitialiseDefaults(); } @@ -43,7 +47,19 @@ namespace osu.Game.Configuration { base.AddBindable(lookup, bindable); - var setting = databasedSettings.FirstOrDefault(s => (int)s.Key == (int)(object)lookup); + if (legacySettingsExist) + { + var legacySetting = databasedSettings.Find(s => s.Key == ((int)(object)lookup).ToString()); + + if (legacySetting != null) + { + bindable.Parse(legacySetting.Value); + settings.Delete(legacySetting); + } + } + + var setting = databasedSettings.Find(s => s.Key == lookup.ToString()); + if (setting != null) { bindable.Parse(setting.Value); @@ -52,7 +68,7 @@ namespace osu.Game.Configuration { settings.Update(setting = new DatabasedSetting { - Key = lookup, + Key = lookup.ToString(), Value = bindable.Value, RulesetID = ruleset?.ID, Variant = variant, @@ -61,9 +77,9 @@ namespace osu.Game.Configuration databasedSettings.Add(setting); } - bindable.ValueChanged += v => + bindable.ValueChanged += b => { - setting.Value = v; + setting.Value = b.NewValue; settings.Update(setting); }; } diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index 62990991b0..f5c92b3029 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; @@ -9,19 +9,16 @@ namespace osu.Game.Configuration [Table("Settings")] public class DatabasedSetting : IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int? RulesetID { get; set; } public int? Variant { get; set; } + public int? SkinInfoID { get; set; } + [Column("Key")] - public int IntKey - { - get => (int)Key; - private set => Key = value; - } + public string Key { get; set; } [Column("Value")] public string StringValue @@ -30,10 +27,9 @@ namespace osu.Game.Configuration set => Value = value; } - public object Key; public object Value; - public DatabasedSetting(object key, object value) + public DatabasedSetting(string key, object value) { Key = key; Value = value; diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8975ab8a0e..795f0b43f7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; @@ -32,14 +33,14 @@ namespace osu.Game.Configuration Set(OsuSetting.Username, string.Empty); Set(OsuSetting.Token, string.Empty); - Set(OsuSetting.SavePassword, false).ValueChanged += val => + Set(OsuSetting.SavePassword, false).ValueChanged += enabled => { - if (val) Set(OsuSetting.SaveUsername, true); + if (enabled.NewValue) Set(OsuSetting.SaveUsername, true); }; - Set(OsuSetting.SaveUsername, true).ValueChanged += val => + Set(OsuSetting.SaveUsername, true).ValueChanged += enabled => { - if (!val) Set(OsuSetting.SavePassword, false); + if (!enabled.NewValue) Set(OsuSetting.SavePassword, false); }; Set(OsuSetting.ExternalLinkWarning, true); @@ -54,7 +55,7 @@ namespace osu.Game.Configuration // Input Set(OsuSetting.MenuCursorSize, 1.0, 0.5f, 2, 0.01); - Set(OsuSetting.GameplayCursorSize, 1.0, 0.5f, 2, 0.01); + Set(OsuSetting.GameplayCursorSize, 1.0, 0.1f, 2, 0.01); Set(OsuSetting.AutoCursorSize, false); Set(OsuSetting.MouseDisableButtons, false); @@ -71,9 +72,6 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuParallax, true); - Set(OsuSetting.SnakingInSliders, true); - Set(OsuSetting.SnakingOutSliders, true); - // Gameplay Set(OsuSetting.DimLevel, 0.3, 0, 1, 0.01); Set(OsuSetting.BlurLevel, 0, 0, 1, 0.01); @@ -96,15 +94,27 @@ namespace osu.Game.Configuration Set(OsuSetting.ScreenshotCaptureMenuCursor, false); Set(OsuSetting.SongSelectRightMouseScroll, false); + + Set(OsuSetting.Scaling, ScalingMode.Off); + + Set(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); + Set(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); + + Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); + Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); + + Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); } - public OsuConfigManager(Storage storage) : base(storage) + public OsuConfigManager(Storage storage) + : base(storage) { } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { - new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")) + new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")), + new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())), }; } @@ -137,8 +147,6 @@ namespace osu.Game.Configuration DisplayStarsMinimum, DisplayStarsMaximum, RandomSelectAlgorithm, - SnakingInSliders, - SnakingOutSliders, ShowFpsDisplay, ChatDisplayHeight, Version, @@ -151,6 +159,12 @@ namespace osu.Game.Configuration BeatmapHitsounds, IncreaseFirstObjectVisibility, ScoreDisplayMode, - ExternalLinkWarning + ExternalLinkWarning, + Scaling, + ScalingPositionX, + ScalingPositionY, + ScalingSizeX, + ScalingSizeY, + UIScale } } diff --git a/osu.Game/Configuration/RandomSelectAlgorithm.cs b/osu.Game/Configuration/RandomSelectAlgorithm.cs index d994f59b34..8d0c87374f 100644 --- a/osu.Game/Configuration/RandomSelectAlgorithm.cs +++ b/osu.Game/Configuration/RandomSelectAlgorithm.cs @@ -1,14 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; namespace osu.Game.Configuration { - public enum RandomSelectAlgorithm + public enum RandomSelectAlgorithm { [Description("Never repeat")] RandomPermutation, + [Description("Random")] Random } diff --git a/osu.Game/Configuration/RankingType.cs b/osu.Game/Configuration/RankingType.cs index 32fe934100..7701e1dd1d 100644 --- a/osu.Game/Configuration/RankingType.cs +++ b/osu.Game/Configuration/RankingType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; @@ -8,8 +8,10 @@ namespace osu.Game.Configuration public enum RankingType { Local, + [Description("Global")] Top, + [Description("Selected Mods")] SelectedMod, Friends, diff --git a/osu.Game/Configuration/ReleaseStream.cs b/osu.Game/Configuration/ReleaseStream.cs index ca13d0cac3..ed0bee1dd8 100644 --- a/osu.Game/Configuration/ReleaseStream.cs +++ b/osu.Game/Configuration/ReleaseStream.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Configuration { diff --git a/osu.Game/Configuration/ScalingMode.cs b/osu.Game/Configuration/ScalingMode.cs new file mode 100644 index 0000000000..0bcc908f71 --- /dev/null +++ b/osu.Game/Configuration/ScalingMode.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum ScalingMode + { + Off, + Everything, + + [Description("Excluding overlays")] + ExcludeOverlays, + Gameplay, + } +} diff --git a/osu.Game/Configuration/ScoreMeterType.cs b/osu.Game/Configuration/ScoreMeterType.cs index 0333c3406f..21a63fb3ed 100644 --- a/osu.Game/Configuration/ScoreMeterType.cs +++ b/osu.Game/Configuration/ScoreMeterType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Configuration { diff --git a/osu.Game/Configuration/ScreenshotFormat.cs b/osu.Game/Configuration/ScreenshotFormat.cs index 43901281dc..6d4c96bfa9 100644 --- a/osu.Game/Configuration/ScreenshotFormat.cs +++ b/osu.Game/Configuration/ScreenshotFormat.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; @@ -9,6 +9,7 @@ namespace osu.Game.Configuration { [Description("JPG (web-friendly)")] Jpg = 1, + [Description("PNG (lossless)")] Png = 2 } diff --git a/osu.Game/Configuration/ScrollVisualisationMethod.cs b/osu.Game/Configuration/ScrollVisualisationMethod.cs index cc7dcdbc0e..5f48fe8bfd 100644 --- a/osu.Game/Configuration/ScrollVisualisationMethod.cs +++ b/osu.Game/Configuration/ScrollVisualisationMethod.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; @@ -9,8 +9,10 @@ namespace osu.Game.Configuration { [Description("Sequential")] Sequential, + [Description("Overlapping")] Overlapping, + [Description("Constant")] Constant } diff --git a/osu.Game/Configuration/SettingsStore.cs b/osu.Game/Configuration/SettingsStore.cs index 7914e34147..f8c9bdeaf8 100644 --- a/osu.Game/Configuration/SettingsStore.cs +++ b/osu.Game/Configuration/SettingsStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -37,5 +37,11 @@ namespace osu.Game.Configuration SettingChanged?.Invoke(); } + + public void Delete(DatabasedSetting setting) + { + using (var usage = ContextFactory.GetForWrite()) + usage.Context.Remove(setting); + } } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3686b702c4..01455e7d50 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -1,16 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore; +using osu.Framework; +using osu.Framework.Extensions; using osu.Framework.IO.File; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.IO; using osu.Game.IO.Archives; using osu.Game.IPC; @@ -27,7 +31,7 @@ namespace osu.Game.Database /// /// The model type. /// The associated file join type. - public abstract class ArchiveModelManager : ICanAcceptFiles + public abstract class ArchiveModelManager : ArchiveModelManager, ICanAcceptFiles, IModelManager where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { @@ -50,6 +54,8 @@ namespace osu.Game.Database public virtual string[] HandledExtensions => new[] { ".zip" }; + public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + protected readonly FileStore Files; protected readonly IDatabaseContextFactory ContextFactory; @@ -59,55 +65,12 @@ 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 queuedEvents = 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) - { - Action[] events; - lock (queuedEvents) - { - events = queuedEvents.ToArray(); - queuedEvents.Clear(); - } - - if (perform) - { - foreach (var a in events) - a.Invoke(); - } - - delayingEvents = false; - } - - private void handleEvent(Action a) - { - if (delayingEvents) - lock (queuedEvents) - queuedEvents.Add(a); - else - a.Invoke(); - } - - protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore modelStore, IIpcHost importHost = null) + protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) { ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += s => handleEvent(() => ItemAdded?.Invoke(s)); + ModelStore.ItemAdded += item => handleEvent(() => ItemAdded?.Invoke(item)); ModelStore.ItemRemoved += s => handleEvent(() => ItemRemoved?.Invoke(s)); Files = new FileStore(contextFactory, storage); @@ -123,54 +86,50 @@ namespace osu.Game.Database /// This will post notifications tracking progress. /// /// One or more archive locations on disk. - public void Import(params string[] paths) + public Task Import(params string[] paths) { - var notification = new ProgressNotification - { - Text = "Import is initialising...", - Progress = 0, - State = ProgressNotificationState.Active, - }; + var notification = new ProgressNotification { State = ProgressNotificationState.Active }; PostNotification?.Invoke(notification); - List imported = new List(); + return Import(notification, paths); + } + + protected async Task Import(ProgressNotification notification, params string[] paths) + { + notification.Progress = 0; + notification.Text = "Import is initialising..."; int current = 0; - foreach (string path in paths) + + var imported = new List(); + + await Task.WhenAll(paths.Select(async path => { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; + notification.CancellationToken.ThrowIfCancellationRequested(); try { - notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}"; - using (ArchiveReader reader = getReaderFrom(path)) - imported.Add(Import(reader)); + var model = await Import(path, notification.CancellationToken); - notification.Progress = (float)current / paths.Length; + lock (imported) + { + imported.Add(model); + current++; - // We may or may not want to delete the file depending on where it is stored. - // e.g. reconstructing/repairing database with items from default storage. - // Also, not always a single file, i.e. for LegacyFilesystemReader - // TODO: Add a check to prevent files from storage to be deleted. - try - { - if (File.Exists(path)) - File.Delete(path); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); + notification.Text = $"Imported {current} of {paths.Length} {HumanisedModelName}s"; + notification.Progress = (float)current / paths.Length; } } + catch (TaskCanceledException) + { + throw; + } catch (Exception e) { - e = e.InnerException ?? e; - Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})"); + Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})", LoggingTarget.Database); } - } + })); if (imported.Count == 0) { @@ -179,37 +138,121 @@ namespace osu.Game.Database } else { - notification.CompletionText = $"Imported {current} {typeof(TModel).Name.Replace("Info", "").ToLower()}s!"; - notification.CompletionClickAction += () => + notification.CompletionText = imported.Count == 1 + ? $"Imported {imported.First()}!" + : $"Imported {current} {HumanisedModelName}s!"; + + if (imported.Count > 0 && PresentImport != null) { - if (imported.Count > 0) - PresentCompletedImport(imported); - return true; - }; + notification.CompletionText += " Click to view."; + notification.CompletionClickAction = () => + { + PresentImport?.Invoke(imported); + return true; + }; + } + notification.State = ProgressNotificationState.Completed; } } - protected virtual void PresentCompletedImport(IEnumerable imported) + /// + /// Import one from the filesystem and delete the file on success. + /// + /// The archive location on disk. + /// An optional cancellation token. + /// The imported model, if successful. + public async Task Import(string path, CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); + + TModel import; + using (ArchiveReader reader = getReaderFrom(path)) + import = await Import(reader, cancellationToken); + + // We may or may not want to delete the file depending on where it is stored. + // e.g. reconstructing/repairing database with items from default storage. + // Also, not always a single file, i.e. for LegacyFilesystemReader + // TODO: Add a check to prevent files from storage to be deleted. + try + { + if (import != null && File.Exists(path)) + File.Delete(path); + } + catch (Exception e) + { + LogForModel(import, $@"Could not delete original file after import ({Path.GetFileName(path)})", e); + } + + return import; } + /// + /// Fired when the user requests to view the resulting import. + /// + public Action> PresentImport; + /// /// Import an item from an . /// /// The archive to be imported. - public TModel Import(ArchiveReader archive) + /// An optional cancellation token. + public Task Import(ArchiveReader archive, CancellationToken cancellationToken = default) { + cancellationToken.ThrowIfCancellationRequested(); + + TModel model = null; + try { - var model = CreateModel(archive); - return model == null ? null : Import(model, archive); + model = CreateModel(archive); + + if (model == null) return null; + + model.Hash = computeHash(archive); + } + catch (TaskCanceledException) + { + throw; } catch (Exception e) { - Logger.Error(e, $"Model creation of {archive.Name} failed.", LoggingTarget.Database); + LogForModel(model, $"Model creation of {archive.Name} failed.", e); return null; } + + return Import(model, archive, cancellationToken); + } + + /// + /// Any file extensions which should be included in hash creation. + /// Generally should include all file types which determine the file's uniqueness. + /// Large files should be avoided if possible. + /// + protected abstract string[] HashableFileTypes { get; } + + protected static void LogForModel(TModel model, string message, Exception e = null) + { + string prefix = $"[{(model?.Hash ?? "?????").Substring(0, 5)}]"; + + if (e != null) + Logger.Error(e, $"{prefix} {message}", LoggingTarget.Database); + else + Logger.Log($"{prefix} {message}", LoggingTarget.Database); + } + + /// + /// Create a SHA-2 hash from the provided archive based on file content of all files matching . + /// + private string computeHash(ArchiveReader reader) + { + // for now, concatenate all .osu files in the set to create a unique hash. + MemoryStream hashable = new MemoryStream(); + foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith))) + using (Stream s = reader.GetStream(file)) + s.CopyTo(hashable); + + return hashable.ComputeSHA2Hash(); } /// @@ -217,13 +260,30 @@ namespace osu.Game.Database /// /// The model to be imported. /// An optional archive to use for model population. - public TModel Import(TModel item, ArchiveReader archive = null) + /// An optional cancellation token. + public async Task Import(TModel item, ArchiveReader archive = null, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => { + cancellationToken.ThrowIfCancellationRequested(); + delayEvents(); + void rollback() + { + if (!Delete(item)) + { + // We may have not yet added the model to the underlying table, but should still clean up files. + LogForModel(item, "Dereferencing files for incomplete import."); + Files.Dereference(item.Files.Select(f => f.FileInfo).ToArray()); + } + } + try { - Logger.Log($"Importing {item}...", LoggingTarget.Database); + LogForModel(item, "Beginning import..."); + + item.Files = archive != null ? createFileInfos(archive, Files) : new List(); + + await Populate(item, archive, cancellationToken); using (var write = ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { @@ -235,14 +295,21 @@ namespace osu.Game.Database if (existing != null) { - Logger.Log($"Found existing {typeof(TModel)} for {item} (ID {existing.ID}). Skipping import.", LoggingTarget.Database); - return existing; + if (CanUndelete(existing, item)) + { + Undelete(existing); + LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); + // existing item will be used; rollback new import and exit early. + rollback(); + flushEvents(true); + return existing; + } + + Delete(existing); + ModelStore.PurgeDeletable(s => s.ID == existing.ID); } - if (archive != null) - item.Files = createFileInfos(archive, Files); - - Populate(item, archive); + PreImport(item); // import to store ModelStore.Add(item); @@ -254,21 +321,21 @@ namespace osu.Game.Database } } - Logger.Log($"Import of {item} successfully completed!", LoggingTarget.Database); + LogForModel(item, "Import successfully completed!"); } catch (Exception e) { - Logger.Error(e, $"Import of {item} 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); + if (!(e is TaskCanceledException)) + LogForModel(item, "Database import or population failed and has been rolled back.", e); + + rollback(); + flushEvents(false); + throw; } + flushEvents(true); return item; - } + }, cancellationToken, TaskCreationOptions.HideScheduler, IMPORT_SCHEDULER).Unwrap(); /// /// Perform an update of the specified item. @@ -302,18 +369,20 @@ namespace osu.Game.Database /// Delete multiple items. /// This will post notifications tracking progress. /// - public void Delete(List items) + public void Delete(List items, bool silent = false) { if (items.Count == 0) return; var notification = new ProgressNotification { Progress = 0, - CompletionText = $"Deleted all {typeof(TModel).Name.Replace("Info", "").ToLower()}s!", + Text = $"Preparing to delete all {HumanisedModelName}s...", + CompletionText = $"Deleted all {HumanisedModelName}s!", State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; @@ -325,7 +394,7 @@ namespace osu.Game.Database // user requested abort return; - notification.Text = $"Deleting ({++i} of {items.Count})"; + notification.Text = $"Deleting {HumanisedModelName}s ({++i} of {items.Count})"; Delete(b); @@ -340,7 +409,7 @@ namespace osu.Game.Database /// Restore multiple items that were previously deleted. /// This will post notifications tracking progress. /// - public void Undelete(List items) + public void Undelete(List items, bool silent = false) { if (!items.Any()) return; @@ -351,7 +420,8 @@ namespace osu.Game.Database State = ProgressNotificationState.Active, }; - PostNotification?.Invoke(notification); + if (!silent) + PostNotification?.Invoke(notification); int i = 0; @@ -448,7 +518,7 @@ namespace osu.Game.Database return Task.CompletedTask; } - return Task.Factory.StartNew(() => Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray()), TaskCreationOptions.LongRunning); + return Task.Run(async () => await Import(stable.GetDirectories(ImportFromStablePath).Select(f => stable.GetFullPath(f)).ToArray())); } #endregion @@ -467,14 +537,37 @@ namespace osu.Game.Database /// /// The model to populate. /// The archive to use as a reference for population. May be null. - protected virtual void Populate(TModel model, [CanBeNull] ArchiveReader archive) + /// An optional cancellation token. + protected virtual Task Populate(TModel model, [CanBeNull] ArchiveReader archive, CancellationToken cancellationToken = default) => Task.CompletedTask; + + /// + /// Perform any final actions before the import to database executes. + /// + /// The model prepared for import. + protected virtual void PreImport(TModel model) { } - protected virtual TModel CheckForExisting(TModel model) => null; + /// + /// Check whether an existing model already exists for a new import item. + /// + /// The new model proposed for import. + /// An existing model which matches the criteria to skip importing, else null. + protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); + + /// + /// After an existing is found during an import process, the default behaviour is to restore the existing + /// item and skip the import. This method allows changing that behaviour. + /// + /// The existing model. + /// The newly imported model. + /// Whether the existing model should be restored and used. Returning false will delete the existing a force a re-import. + protected virtual bool CanUndelete(TModel existing, TModel import) => true; private DbSet queryModel() => ContextFactory.Get().Set(); + protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}"; + /// /// Creates an from a valid storage path. /// @@ -485,8 +578,73 @@ namespace osu.Game.Database if (ZipUtils.IsZipArchive(path)) return new ZipArchiveReader(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read), Path.GetFileName(path)); if (Directory.Exists(path)) - return new LegacyFilesystemReader(path); + return new LegacyDirectoryArchiveReader(path); + if (File.Exists(path)) + return new LegacyFileArchiveReader(path); + throw new InvalidFormatException($"{path} is not a valid archive"); } + + #region Event handling / delaying + + private readonly List queuedEvents = 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) + { + Action[] events; + + lock (queuedEvents) + { + events = queuedEvents.ToArray(); + queuedEvents.Clear(); + } + + if (perform) + { + foreach (var a in events) + a.Invoke(); + } + + delayingEvents = false; + } + + private void handleEvent(Action a) + { + if (delayingEvents) + lock (queuedEvents) + queuedEvents.Add(a); + else + a.Invoke(); + } + + #endregion + } + + public abstract class ArchiveModelManager + { + private const int import_queue_request_concurrency = 1; + + /// + /// A singleton scheduler shared by all . + /// + /// + /// This scheduler generally performs IO and CPU intensive work so concurrency is limited harshly. + /// It is mainly being used as a queue mechanism for large imports. + /// + protected static readonly ThreadedTaskScheduler IMPORT_SCHEDULER = new ThreadedTaskScheduler(import_queue_request_concurrency, nameof(ArchiveModelManager)); } } diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index c6fb51c056..e6b6a0ac2f 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using Microsoft.EntityFrameworkCore; diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 2037612a09..554337c477 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using System.Threading; using Microsoft.EntityFrameworkCore.Storage; @@ -67,7 +66,7 @@ namespace osu.Game.Database context = threadContexts.Value; } } - catch (Exception e) + catch { // retrieval of a context could trigger a fatal error. Monitor.Exit(writeLock); diff --git a/osu.Game/Database/DatabaseWriteUsage.cs b/osu.Game/Database/DatabaseWriteUsage.cs index 64ab24e824..1fd2f23d50 100644 --- a/osu.Game/Database/DatabaseWriteUsage.cs +++ b/osu.Game/Database/DatabaseWriteUsage.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -31,6 +31,7 @@ namespace osu.Game.Database protected void Dispose(bool disposing) { if (isDisposed) return; + isDisposed = true; try diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs new file mode 100644 index 0000000000..78c0837ce9 --- /dev/null +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -0,0 +1,143 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Online.API; +using osu.Game.Overlays.Notifications; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace osu.Game.Database +{ + /// + /// An that has the ability to download models using an and + /// import them into the store. + /// + /// The model type. + /// The associated file join type. + public abstract class DownloadableArchiveModelManager : ArchiveModelManager, IModelDownloader + where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete, IEquatable + where TFileModel : INamedFileInfo, new() + { + public event Action> DownloadBegan; + + public event Action> DownloadFailed; + + private readonly IAPIProvider api; + + private readonly List> currentDownloads = new List>(); + + private readonly MutableDatabaseBackedStoreWithFileIncludes modelStore; + + protected DownloadableArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, IAPIProvider api, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) + : base(storage, contextFactory, modelStore, importHost) + { + this.api = api; + this.modelStore = modelStore; + } + + /// + /// Creates the download request for this . + /// + /// The to be downloaded. + /// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle. + /// The request object. + protected abstract ArchiveDownloadRequest CreateDownloadRequest(TModel model, bool minimiseDownloadSize); + + /// + /// Begin a download for the requested . + /// + /// The to be downloaded. + /// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle. + /// Whether the download was started. + public bool Download(TModel model, bool minimiseDownloadSize = false) + { + if (!canDownload(model)) return false; + + var request = CreateDownloadRequest(model, minimiseDownloadSize); + + DownloadNotification notification = new DownloadNotification + { + Text = $"Downloading {request.Model}", + }; + + request.DownloadProgressed += progress => + { + notification.State = ProgressNotificationState.Active; + notification.Progress = progress; + }; + + request.Success += filename => + { + Task.Factory.StartNew(async () => + { + // This gets scheduled back to the update thread, but we want the import to run in the background. + await Import(notification, filename); + currentDownloads.Remove(request); + }, TaskCreationOptions.LongRunning); + }; + + request.Failure += error => + { + DownloadFailed?.Invoke(request); + + if (error is OperationCanceledException) return; + + notification.State = ProgressNotificationState.Cancelled; + Logger.Error(error, $"{HumanisedModelName.Titleize()} download failed!"); + currentDownloads.Remove(request); + }; + + notification.CancelRequested += () => + { + request.Cancel(); + currentDownloads.Remove(request); + notification.State = ProgressNotificationState.Cancelled; + return true; + }; + + currentDownloads.Add(request); + PostNotification?.Invoke(notification); + + Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); + + DownloadBegan?.Invoke(request); + + return true; + } + + public bool IsAvailableLocally(TModel model) => CheckLocalAvailability(model, modelStore.ConsumableItems.Where(m => !m.DeletePending)); + + /// + /// Performs implementation specific comparisons to determine whether a given model is present in the local store. + /// + /// The whose existence needs to be checked. + /// The usable items present in the store. + /// Whether the exists. + protected abstract bool CheckLocalAvailability(TModel model, IQueryable items); + + public ArchiveDownloadRequest GetExistingDownload(TModel model) => currentDownloads.Find(r => r.Model.Equals(model)); + + private bool canDownload(TModel model) => GetExistingDownload(model) == null && api != null; + + private class DownloadNotification : ProgressNotification + { + public override bool IsImportant => false; + + protected override Notification CreateCompletionNotification() => new SilencedProgressCompletionNotification + { + Activated = CompletionClickAction, + Text = CompletionText + }; + + private class SilencedProgressCompletionNotification : ProgressCompletionNotification + { + public override bool IsImportant => false; + } + } + } +} diff --git a/osu.Game/Database/ICanAcceptFiles.cs b/osu.Game/Database/ICanAcceptFiles.cs index a3766ef5aa..b9f882468d 100644 --- a/osu.Game/Database/ICanAcceptFiles.cs +++ b/osu.Game/Database/ICanAcceptFiles.cs @@ -1,5 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading.Tasks; namespace osu.Game.Database { @@ -12,7 +14,7 @@ namespace osu.Game.Database /// Import the specified paths. /// /// The files which should be imported. - void Import(params string[] paths); + Task Import(params string[] paths); /// /// An array of accepted file extensions (in the standard format of ".abc"). diff --git a/osu.Game/Database/IDatabaseContextFactory.cs b/osu.Game/Database/IDatabaseContextFactory.cs index d38d15b252..72b70a208f 100644 --- a/osu.Game/Database/IDatabaseContextFactory.cs +++ b/osu.Game/Database/IDatabaseContextFactory.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Database { diff --git a/osu.Game/Database/IHasFiles.cs b/osu.Game/Database/IHasFiles.cs index 3aaba37efc..f6aa941ec2 100644 --- a/osu.Game/Database/IHasFiles.cs +++ b/osu.Game/Database/IHasFiles.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; @@ -11,8 +11,9 @@ namespace osu.Game.Database /// The model representing a file. public interface IHasFiles where TFile : INamedFileInfo - { List Files { get; set; } + + string Hash { get; set; } } } diff --git a/osu.Game/Database/IHasPrimaryKey.cs b/osu.Game/Database/IHasPrimaryKey.cs index 2ee356baea..3c0fc94418 100644 --- a/osu.Game/Database/IHasPrimaryKey.cs +++ b/osu.Game/Database/IHasPrimaryKey.cs @@ -1,10 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel.DataAnnotations.Schema; +using Newtonsoft.Json; namespace osu.Game.Database { public interface IHasPrimaryKey { + [JsonIgnore] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] int ID { get; set; } } } diff --git a/osu.Game/Database/IModelDownloader.cs b/osu.Game/Database/IModelDownloader.cs new file mode 100644 index 0000000000..f6f4b0aa42 --- /dev/null +++ b/osu.Game/Database/IModelDownloader.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API; +using System; + +namespace osu.Game.Database +{ + /// + /// Represents a that can download new models from an external source. + /// + /// The model type. + public interface IModelDownloader : IModelManager + where TModel : class + { + /// + /// Fired when a download begins. + /// + event Action> DownloadBegan; + + /// + /// Fired when a download is interrupted, either due to user cancellation or failure. + /// + event Action> DownloadFailed; + + /// + /// Checks whether a given is already available in the local store. + /// + /// The whose existence needs to be checked. + /// Whether the exists. + bool IsAvailableLocally(TModel model); + + /// + /// Begin a download for the requested . + /// + /// The to be downloaded. + /// Whether this download should be optimised for slow connections. Generally means extras are not included in the download bundle.. + /// Whether the download was started. + bool Download(TModel model, bool minimiseDownloadSize); + + /// + /// Gets an existing download request if it exists. + /// + /// The whose request is wanted. + /// The object if it exists, otherwise null. + ArchiveDownloadRequest GetExistingDownload(TModel model); + } +} diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs new file mode 100644 index 0000000000..884814cb38 --- /dev/null +++ b/osu.Game/Database/IModelManager.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Database +{ + /// + /// Represents a model manager that publishes events when s are added or removed. + /// + /// The model type. + public interface IModelManager + where TModel : class + { + event Action ItemAdded; + + event Action ItemRemoved; + } +} diff --git a/osu.Game/Database/INamedFileInfo.cs b/osu.Game/Database/INamedFileInfo.cs index 751024d184..d95f228440 100644 --- a/osu.Game/Database/INamedFileInfo.cs +++ b/osu.Game/Database/INamedFileInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.IO; @@ -10,7 +10,11 @@ namespace osu.Game.Database /// public interface INamedFileInfo { + // An explicit foreign key property isn't required but is recommended and may be helpful to have + int FileInfoID { get; set; } + FileInfo FileInfo { get; set; } + string Filename { get; set; } } } diff --git a/osu.Game/Database/ISoftDelete.cs b/osu.Game/Database/ISoftDelete.cs index 38fba62421..afa42c2002 100644 --- a/osu.Game/Database/ISoftDelete.cs +++ b/osu.Game/Database/ISoftDelete.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Database { diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 69a1f57cc4..39a48b5be6 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -68,6 +68,7 @@ namespace osu.Game.Database Refresh(ref item); if (item.DeletePending) return false; + item.DeletePending = true; } @@ -86,6 +87,7 @@ namespace osu.Game.Database Refresh(ref item, ConsumableItems); if (!item.DeletePending) return false; + item.DeletePending = false; } diff --git a/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs b/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs new file mode 100644 index 0000000000..5d6ff6b09b --- /dev/null +++ b/osu.Game/Database/MutableDatabaseBackedStoreWithFileIncludes.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Platform; + +namespace osu.Game.Database +{ + public abstract class MutableDatabaseBackedStoreWithFileIncludes : MutableDatabaseBackedStore + where T : class, IHasPrimaryKey, ISoftDelete, IHasFiles + where U : INamedFileInfo + { + protected MutableDatabaseBackedStoreWithFileIncludes(IDatabaseContextFactory contextFactory, Storage storage = null) + : base(contextFactory, storage) + { + } + + protected override IQueryable AddIncludesForConsumption(IQueryable query) => + base.AddIncludesForConsumption(query) + .Include(s => s.Files).ThenInclude(f => f.FileInfo); + + protected override IQueryable AddIncludesForDeletion(IQueryable query) => + base.AddIncludesForDeletion(query) + .Include(s => s.Files); // don't include FileInfo. these are handled by the FileStore itself. + } +} diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 20e144c033..d31d7cbff7 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using Microsoft.EntityFrameworkCore; @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO; using osu.Game.Rulesets; +using osu.Game.Scoring; using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; using osu.Game.Skinning; @@ -27,6 +28,7 @@ namespace osu.Game.Database public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } public DbSet SkinInfo { get; set; } + public DbSet ScoreInfo { get; set; } private readonly string connectionString; @@ -58,6 +60,7 @@ namespace osu.Game.Database this.connectionString = connectionString; var connection = Database.GetDbConnection(); + try { connection.Open(); @@ -68,7 +71,7 @@ namespace osu.Game.Database cmd.ExecuteNonQuery(); } } - catch (Exception e) + catch { connection.Close(); throw; @@ -105,6 +108,9 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.DeletePending); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.DeletePending); + modelBuilder.Entity().HasIndex(b => new { b.RulesetID, b.Variant }); modelBuilder.Entity().HasIndex(b => b.IntAction); @@ -117,6 +123,8 @@ namespace osu.Game.Database modelBuilder.Entity().HasIndex(b => b.ShortName).IsUnique(); modelBuilder.Entity().HasOne(b => b.BaseDifficulty); + + modelBuilder.Entity().HasIndex(b => b.OnlineScoreID).IsUnique(); } private class OsuDbLoggerFactory : ILoggerFactory @@ -163,9 +171,11 @@ namespace osu.Game.Database default: frameworkLogLevel = Framework.Logging.LogLevel.Debug; break; + case LogLevel.Warning: frameworkLogLevel = Framework.Logging.LogLevel.Important; break; + case LogLevel.Error: case LogLevel.Critical: frameworkLogLevel = Framework.Logging.LogLevel.Error; diff --git a/osu.Game/Graphics/Backgrounds/Background.cs b/osu.Game/Graphics/Backgrounds/Background.cs index d3d530a540..db055d15e5 100644 --- a/osu.Game/Graphics/Backgrounds/Background.cs +++ b/osu.Game/Graphics/Backgrounds/Background.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Graphics/Backgrounds/Triangles.cs b/osu.Game/Graphics/Backgrounds/Triangles.cs index eb8dc7243e..29113e0e2f 100644 --- a/osu.Game/Graphics/Backgrounds/Triangles.cs +++ b/osu.Game/Graphics/Backgrounds/Triangles.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.MathUtils; @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.Backgrounds private readonly SortedList parts = new SortedList(Comparer.Default); - private Shader shader; + private IShader shader; private readonly Texture texture; public Triangles() @@ -75,7 +75,7 @@ namespace osu.Game.Graphics.Backgrounds [BackgroundDependencyLoader] private void load(ShaderManager shaders) { - shader = shaders?.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } protected override void LoadComplete() @@ -86,7 +86,7 @@ namespace osu.Game.Graphics.Backgrounds public float TriangleScale { - get { return triangleScale; } + get => triangleScale; set { float change = value / triangleScale; @@ -110,10 +110,10 @@ namespace osu.Game.Graphics.Backgrounds if (CreateNewTriangles) addTriangles(false); - float adjustedAlpha = HideAlphaDiscrepancies ? + float adjustedAlpha = HideAlphaDiscrepancies // Cubically scale alpha to make it drop off more sharply. - (float)Math.Pow(DrawColourInfo.Colour.AverageColour.Linear.A, 3) : - 1; + ? (float)Math.Pow(DrawColourInfo.Colour.AverageColour.Linear.A, 3) + : 1; float elapsedSeconds = (float)Time.Elapsed / 1000; // Since position is relative, the velocity needs to scale inversely with DrawHeight. @@ -178,71 +178,75 @@ namespace osu.Game.Graphics.Backgrounds /// The colour. protected virtual Color4 CreateTriangleShade() => Interpolation.ValueAt(RNG.NextSingle(), ColourDark, ColourLight, 0, 1); - protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(); - - private readonly TrianglesDrawNodeSharedData sharedData = new TrianglesDrawNodeSharedData(); - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var trianglesNode = (TrianglesDrawNode)node; - - trianglesNode.Shader = shader; - trianglesNode.Texture = texture; - trianglesNode.Size = DrawSize; - trianglesNode.Shared = sharedData; - - trianglesNode.Parts.Clear(); - trianglesNode.Parts.AddRange(parts); - } - - private class TrianglesDrawNodeSharedData - { - public readonly LinearBatch VertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); - } + protected override DrawNode CreateDrawNode() => new TrianglesDrawNode(this); private class TrianglesDrawNode : DrawNode { - public Shader Shader; - public Texture Texture; + protected new Triangles Source => (Triangles)base.Source; - public TrianglesDrawNodeSharedData Shared; + private IShader shader; + private Texture texture; - public readonly List Parts = new List(); - public Vector2 Size; + private readonly List parts = new List(); + private Vector2 size; + + private readonly LinearBatch vertexBatch = new LinearBatch(100 * 3, 10, PrimitiveType.Triangles); + + public TrianglesDrawNode(Triangles source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize; + + parts.Clear(); + parts.AddRange(Source.parts); + } public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); Vector2 localInflationAmount = edge_smoothness * DrawInfo.MatrixInverse.ExtractScale().Xy; - foreach (TriangleParticle particle in Parts) + foreach (TriangleParticle particle in parts) { var offset = triangle_size * new Vector2(particle.Scale * 0.5f, particle.Scale * 0.866f); - var size = new Vector2(2 * offset.X, offset.Y); var triangle = new Triangle( - Vector2Extensions.Transform(particle.Position * Size, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + offset, DrawInfo.Matrix), - Vector2Extensions.Transform(particle.Position * Size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) + Vector2Extensions.Transform(particle.Position * size, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + offset, DrawInfo.Matrix), + Vector2Extensions.Transform(particle.Position * size + new Vector2(-offset.X, offset.Y), DrawInfo.Matrix) ); ColourInfo colourInfo = DrawColourInfo.Colour; colourInfo.ApplyChild(particle.Colour); - Texture.DrawTriangle( + DrawTriangle( + texture, triangle, colourInfo, null, - Shared.VertexBatch.AddAction, - Vector2.Divide(localInflationAmount, size)); + vertexBatch.AddAction, + Vector2.Divide(localInflationAmount, new Vector2(2 * offset.X, offset.Y))); } - Shader.Unbind(); + shader.Unbind(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBatch.Dispose(); } } diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index f0d49af988..621eeea2b7 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -74,7 +74,7 @@ namespace osu.Game.Graphics.Containers } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap) + private void load(IBindable beatmap) { Beatmap.BindTo(beatmap); } diff --git a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs index aae05ca767..9d3f342a70 100644 --- a/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs +++ b/osu.Game/Graphics/Containers/ConstrainedIconContainer.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; namespace osu.Game.Graphics.Containers @@ -15,15 +16,9 @@ namespace osu.Game.Graphics.Containers { public Drawable Icon { - get - { - return InternalChild; - } + get => InternalChild; - set - { - InternalChild = value; - } + set => InternalChild = value; } /// @@ -33,13 +28,14 @@ namespace osu.Game.Graphics.Containers /// public new EdgeEffectParameters EdgeEffect { - get { return base.EdgeEffect; } - set { base.EdgeEffect = value; } + get => base.EdgeEffect; + set => base.EdgeEffect = value; } protected override void Update() { base.Update(); + if (InternalChildren.Count > 0 && InternalChild.DrawSize.X > 0) { // We're modifying scale here for a few reasons diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index adfc258f61..cda5e150de 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,7 +27,7 @@ namespace osu.Game.Graphics.Containers protected void BeginConfirm() { - if (confirming || !AllowMultipleFires && fired) return; + if (confirming || (!AllowMultipleFires && fired)) return; confirming = true; diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 54a2ea47f9..15068d81c0 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Online.Chat; using System; @@ -7,9 +7,11 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Users; namespace osu.Game.Graphics.Containers { @@ -34,7 +36,7 @@ namespace osu.Game.Graphics.Containers showNotImplementedError = () => notifications?.Post(new SimpleNotification { Text = @"This link type is not yet supported!", - Icon = FontAwesome.fa_life_saver, + Icon = FontAwesome.Solid.LifeRing, }); } @@ -50,6 +52,7 @@ namespace osu.Game.Graphics.Containers } int previousLinkEnd = 0; + foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); @@ -60,12 +63,30 @@ namespace osu.Game.Graphics.Containers AddText(text.Substring(previousLinkEnd)); } - public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) + public IEnumerable AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText); + + public IEnumerable AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action); + + public IEnumerable AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) { - AddInternal(new DrawableLinkCompiler(AddText(text, creationParameters).ToList()) + foreach (var t in text) + AddArbitraryDrawable(t); + + return createLink(text, null, url, linkType, linkArgument, tooltipText); + } + + public IEnumerable AddUserLink(User user, Action creationParameters = null) + => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile"); + + private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) + { + AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) { + RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = () => + Action = action ?? (() => { switch (linkType) { @@ -74,38 +95,51 @@ namespace osu.Game.Graphics.Containers if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) game?.ShowBeatmap(beatmapId); break; + case LinkAction.OpenBeatmapSet: if (int.TryParse(linkArgument, out int setId)) game?.ShowBeatmapSet(setId); break; + case LinkAction.OpenChannel: try { channelManager?.OpenChannel(linkArgument); } - catch (ChannelNotFoundException e) + catch (ChannelNotFoundException) { Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); } break; + case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: showNotImplementedError?.Invoke(); break; + case LinkAction.External: game?.OpenUrlExternally(url); break; + case LinkAction.OpenUserProfile: if (long.TryParse(linkArgument, out long userId)) game?.ShowUser(userId); break; + default: throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); } - }, + }), }); + + return drawables; } + + // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. + // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. + // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. + public override IEnumerable FlowingChildren => base.FlowingChildren.Where(c => !(c is DrawableLinkCompiler)); } } diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs new file mode 100644 index 0000000000..23015e8bf5 --- /dev/null +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that handles tracking of an through different layout scenarios. + /// + public class LogoTrackingContainer : Container + { + public Facade LogoFacade => facade; + + protected OsuLogo Logo { get; private set; } + + private readonly InternalFacade facade = new InternalFacade(); + + private Easing easing; + private Vector2? startPosition; + private double? startTime; + private double duration; + + /// + /// Assign the logo that should track the facade's position, as well as how it should transform to its initial position. + /// + /// The instance of the logo to be used for tracking. + /// The duration of the initial transform. Default is instant. + /// The easing type of the initial transform. + public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) + { + if (logo == null) + throw new ArgumentNullException(nameof(logo)); + + if (logo.IsTracking && Logo == null) + throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); + + if (Logo != logo && Logo != null) + { + // If we're replacing the logo to be tracked, the old one no longer has a tracking container + Logo.IsTracking = false; + } + + Logo = logo; + Logo.IsTracking = true; + + this.duration = duration; + this.easing = easing; + + startTime = null; + startPosition = null; + } + + /// + /// Stops the logo assigned in from tracking the facade's position. + /// + public void StopTracking() + { + if (Logo != null) + { + Logo.IsTracking = false; + Logo = null; + } + } + + /// + /// Gets the position that the logo should move to with respect to the . + /// Manually performs a conversion of the Facade's position to the Logo's parent's relative space. + /// + /// Will only be correct if the logo's are set to Axes.Both + protected Vector2 ComputeLogoTrackingPosition() + { + var absolutePos = Logo.Parent.ToLocalSpace(LogoFacade.ScreenSpaceDrawQuad.Centre); + + return new Vector2(absolutePos.X / Logo.Parent.RelativeToAbsoluteFactor.X, + absolutePos.Y / Logo.Parent.RelativeToAbsoluteFactor.Y); + } + + protected override void Update() + { + base.Update(); + + if (Logo == null) + return; + + if (Logo.RelativePositionAxes != Axes.Both) + throw new InvalidOperationException($"Tracking logo must have {nameof(RelativePositionAxes)} = Axes.Both"); + + // Account for the scale of the actual OsuLogo, as SizeForFlow only accounts for the sprite scale. + facade.SetSize(new Vector2(Logo.SizeForFlow * Logo.Scale.X)); + + var localPos = ComputeLogoTrackingPosition(); + + if (LogoFacade.Parent != null && Logo.Position != localPos) + { + // If this is our first update since tracking has started, initialize our starting values for interpolation + if (startTime == null || startPosition == null) + { + startTime = Time.Current; + startPosition = Logo.Position; + } + + if (duration != 0) + { + double elapsedDuration = (double)(Time.Current - startTime); + + var amount = (float)Interpolation.ApplyEasing(easing, Math.Min(elapsedDuration / duration, 1)); + + // Interpolate the position of the logo, where amount 0 is where the logo was when it first began interpolating, and amount 1 is the target location. + Logo.Position = Vector2.Lerp(startPosition.Value, localPos, amount); + } + else + { + Logo.Position = localPos; + } + } + } + + protected override void Dispose(bool isDisposing) + { + if (Logo != null) + Logo.IsTracking = false; + + base.Dispose(isDisposing); + } + + private class InternalFacade : Facade + { + public new void SetSize(Vector2 size) + { + base.SetSize(size); + } + } + + /// + /// A dummy object used to denote another object's location. + /// + public abstract class Facade : Drawable + { + public override Vector2 Size + { + get => base.Size; + set => throw new InvalidOperationException($"Cannot set the Size of a {typeof(Facade)} outside of a {typeof(LogoTrackingContainer)}"); + } + + protected void SetSize(Vector2 size) + { + base.Size = size; + } + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index cf80a549a4..1f31e4cdda 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -1,14 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers { - public class OsuClickableContainer : ClickableContainer + public class OsuClickableContainer : ClickableContainer, IHasTooltip { private readonly HoverSampleSet sampleSet; @@ -23,12 +24,14 @@ namespace osu.Game.Graphics.Containers this.sampleSet = sampleSet; } + public virtual string TooltipText { get; set; } + [BackgroundDependencyLoader] private void load() { if (AutoSizeAxes != Axes.None) { - content.RelativeSizeAxes = RelativeSizeAxes; + content.RelativeSizeAxes = (Axes.Both & ~AutoSizeAxes); content.AutoSizeAxes = AutoSizeAxes; } diff --git a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs index a24b6594e0..f6db3102f2 100644 --- a/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs +++ b/osu.Game/Graphics/Containers/OsuFocusedOverlayContainer.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osuTK; -using osu.Framework.Configuration; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Audio; @@ -24,8 +24,17 @@ namespace osu.Game.Graphics.Containers protected override bool BlockNonPositionalInput => true; - private PreviewTrackManager previewTrackManager; + /// + /// Temporary to allow for overlays in the main screen content to not dim theirselves. + /// Should be eventually replaced by dimming which is aware of the target dim container (traverse parent for certain interface type?). + /// + protected virtual bool DimMainContent => true; + [Resolved(CanBeNull = true)] + private OsuGame osuGame { get; set; } + + [Resolved] + private PreviewTrackManager previewTrackManager { get; set; } protected readonly Bindable OverlayActivationMode = new Bindable(OverlayActivation.All); @@ -37,17 +46,15 @@ namespace osu.Game.Graphics.Containers } [BackgroundDependencyLoader(true)] - private void load(OsuGame osuGame, AudioManager audio, PreviewTrackManager previewTrackManager) + private void load(AudioManager audio) { - this.previewTrackManager = previewTrackManager; - if (osuGame != null) OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); - samplePopIn = audio.Sample.Get(@"UI/overlay-pop-in"); - samplePopOut = audio.Sample.Get(@"UI/overlay-pop-out"); + samplePopIn = audio.Samples.Get(@"UI/overlay-pop-in"); + samplePopOut = audio.Samples.Get(@"UI/overlay-pop-out"); - StateChanged += onStateChanged; + State.ValueChanged += onStateChanged; } /// @@ -63,7 +70,7 @@ namespace osu.Game.Graphics.Containers { if (!base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition)) { - State = Visibility.Hidden; + Hide(); return true; } @@ -75,8 +82,9 @@ namespace osu.Game.Graphics.Containers switch (action) { case GlobalAction.Back: - State = Visibility.Hidden; + Hide(); return true; + case GlobalAction.Select: return true; } @@ -86,20 +94,24 @@ namespace osu.Game.Graphics.Containers public bool OnReleased(GlobalAction action) => false; - private void onStateChanged(Visibility visibility) + private void onStateChanged(ValueChangedEvent state) { - switch (visibility) + switch (state.NewValue) { case Visibility.Visible: - if (OverlayActivationMode != OverlayActivation.Disabled) + if (OverlayActivationMode.Value != OverlayActivation.Disabled) { if (PlaySamplesOnStateChange) samplePopIn?.Play(); + if (BlockScreenWideMouse && DimMainContent) osuGame?.AddBlockingOverlay(this); } else - State = Visibility.Hidden; + Hide(); + break; + case Visibility.Hidden: if (PlaySamplesOnStateChange) samplePopOut?.Play(); + if (BlockScreenWideMouse) osuGame?.RemoveBlockingOverlay(this); break; } } @@ -109,5 +121,11 @@ namespace osu.Game.Graphics.Containers base.PopOut(); previewTrackManager.StopAnyPlaying(this); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + osuGame?.RemoveBlockingOverlay(this); + } } } diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index af804735a8..67af79c763 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -1,39 +1,72 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osuTK.Graphics; +using System.Collections.Generic; namespace osu.Game.Graphics.Containers { public class OsuHoverContainer : OsuClickableContainer { + protected const float FADE_DURATION = 500; + protected Color4 HoverColour; protected Color4 IdleColour = Color4.White; protected virtual IEnumerable EffectTargets => new[] { Content }; + public OsuHoverContainer() + { + Enabled.ValueChanged += e => + { + if (isHovered) + { + if (e.NewValue) + fadeIn(); + else + fadeOut(); + } + }; + } + + private bool isHovered; + protected override bool OnHover(HoverEvent e) { - EffectTargets.ForEach(d => d.FadeColour(HoverColour, 500, Easing.OutQuint)); + if (isHovered) + return false; + + isHovered = true; + + if (!Enabled.Value) + return false; + + fadeIn(); + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - EffectTargets.ForEach(d => d.FadeColour(IdleColour, 500, Easing.OutQuint)); + if (!isHovered) + return; + + isHovered = false; + fadeOut(); + base.OnHoverLost(e); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - HoverColour = colours.Yellow; + if (HoverColour == default) + HoverColour = colours.Yellow; } protected override void LoadComplete() @@ -41,5 +74,9 @@ namespace osu.Game.Graphics.Containers base.LoadComplete(); EffectTargets.ForEach(d => d.FadeColour(IdleColour)); } + + private void fadeIn() => EffectTargets.ForEach(d => d.FadeColour(HoverColour, FADE_DURATION, Easing.OutQuint)); + + private void fadeOut() => EffectTargets.ForEach(d => d.FadeColour(IdleColour, FADE_DURATION, Easing.OutQuint)); } } diff --git a/osu.Game/Graphics/Containers/OsuScrollContainer.cs b/osu.Game/Graphics/Containers/OsuScrollContainer.cs index 8aeade9265..53092ddc9e 100644 --- a/osu.Game/Graphics/Containers/OsuScrollContainer.cs +++ b/osu.Game/Graphics/Containers/OsuScrollContainer.cs @@ -1,13 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osuTK; +using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Graphics.Containers { - public class OsuScrollContainer : ScrollContainer + public class OsuScrollContainer : ScrollContainer { /// /// Allows controlling the scroll bar from any position in the container using the right mouse button. @@ -28,6 +33,11 @@ namespace osu.Game.Graphics.Containers protected override bool IsDragging => base.IsDragging || mouseScrollBarDragging; + public OsuScrollContainer(Direction scrollDirection = Direction.Vertical) + : base(scrollDirection) + { + } + protected override bool OnMouseDown(MouseDownEvent e) { if (shouldPerformRightMouseScroll(e)) @@ -71,5 +81,87 @@ namespace osu.Game.Graphics.Containers return base.OnDragEnd(e); } + + protected override ScrollbarContainer CreateScrollbar(Direction direction) => new OsuScrollbar(direction); + + protected class OsuScrollbar : ScrollbarContainer + { + private const float dim_size = 10; + + private Color4 hoverColour; + private Color4 defaultColour; + private Color4 highlightColour; + + private readonly Box box; + + public OsuScrollbar(Direction scrollDir) + : base(scrollDir) + { + Blending = BlendingMode.Additive; + + CornerRadius = 5; + + const float margin = 3; + + Margin = new MarginPadding + { + Left = scrollDir == Direction.Vertical ? margin : 0, + Right = scrollDir == Direction.Vertical ? margin : 0, + Top = scrollDir == Direction.Horizontal ? margin : 0, + Bottom = scrollDir == Direction.Horizontal ? margin : 0, + }; + + Masking = true; + Child = box = new Box { RelativeSizeAxes = Axes.Both }; + + ResizeTo(1); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = defaultColour = colours.Gray8; + hoverColour = colours.GrayF; + highlightColour = colours.Green; + } + + public override void ResizeTo(float val, int duration = 0, Easing easing = Easing.None) + { + Vector2 size = new Vector2(dim_size) + { + [(int)ScrollDirection] = val + }; + this.ResizeTo(size, duration, easing); + } + + protected override bool OnHover(HoverEvent e) + { + this.FadeColour(hoverColour, 100); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.FadeColour(defaultColour, 100); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (!base.OnMouseDown(e)) return false; + + //note that we are changing the colour of the box here as to not interfere with the hover effect. + box.FadeColour(highlightColour, 100); + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + if (e.Button != MouseButton.Left) return false; + + box.FadeColour(Color4.White, 100); + + return base.OnMouseUp(e); + } + } } } diff --git a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs index e77e075fe2..6a87a4b8b9 100644 --- a/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuTextFlowContainer.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -11,7 +12,8 @@ namespace osu.Game.Graphics.Containers { public class OsuTextFlowContainer : TextFlowContainer { - public OsuTextFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) + public OsuTextFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) { } @@ -19,6 +21,6 @@ namespace osu.Game.Graphics.Containers public void AddArbitraryDrawable(Drawable drawable) => AddInternal(drawable); - public void AddIcon(FontAwesome icon, Action creationParameters = null) => AddText(((char)icon).ToString(), creationParameters); + public IEnumerable AddIcon(IconUsage icon, Action creationParameters = null) => AddText(icon.Icon.ToString(), creationParameters); } } diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 97e12ec0f9..f65a0a469a 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Input; using osuTK; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Configuration; -using osu.Framework.Configuration; using osu.Framework.MathUtils; namespace osu.Game.Graphics.Containers @@ -45,7 +45,7 @@ namespace osu.Game.Graphics.Containers parallaxEnabled = config.GetBindable(OsuSetting.MenuParallax); parallaxEnabled.ValueChanged += delegate { - if (!parallaxEnabled) + if (!parallaxEnabled.Value) { content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, Easing.OutQuint); content.Scale = new Vector2(1 + System.Math.Abs(ParallaxAmount)); @@ -65,7 +65,7 @@ namespace osu.Game.Graphics.Containers { base.Update(); - if (parallaxEnabled) + if (parallaxEnabled.Value) { Vector2 offset = (input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.Position) - DrawSize / 2) * ParallaxAmount; diff --git a/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs b/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs index e6dcb336e7..6360e41bef 100644 --- a/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs +++ b/osu.Game/Graphics/Containers/ReverseChildIDFillFlowContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs new file mode 100644 index 0000000000..8f07c3a656 --- /dev/null +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -0,0 +1,173 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Configuration; +using osu.Game.Screens; +using osu.Game.Screens.Backgrounds; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// Handles user-defined scaling, allowing application at multiple levels defined by . + /// + public class ScalingContainer : Container + { + private Bindable sizeX; + private Bindable sizeY; + private Bindable posX; + private Bindable posY; + + private readonly ScalingMode? targetMode; + + private Bindable scalingMode; + + private readonly Container content; + protected override Container Content => content; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + private readonly Container sizableContainer; + + private BackgroundScreenStack backgroundStack; + + /// + /// Create a new instance. + /// + /// The mode which this container should be handling. Handles all modes if null. + public ScalingContainer(ScalingMode? targetMode = null) + { + this.targetMode = targetMode; + RelativeSizeAxes = Axes.Both; + + InternalChild = sizableContainer = new AlwaysInputContainer + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + CornerRadius = 10, + Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay) + }; + } + + private class ScalingDrawSizePreservingFillContainer : DrawSizePreservingFillContainer + { + private readonly bool applyUIScale; + private Bindable uiScale; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + public ScalingDrawSizePreservingFillContainer(bool applyUIScale) + { + this.applyUIScale = applyUIScale; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + if (applyUIScale) + { + uiScale = osuConfig.GetBindable(OsuSetting.UIScale); + uiScale.BindValueChanged(scaleChanged, true); + } + } + + private void scaleChanged(ValueChangedEvent args) + { + this.ScaleTo(new Vector2(args.NewValue), 500, Easing.Out); + this.ResizeTo(new Vector2(1 / args.NewValue), 500, Easing.Out); + } + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + scalingMode = config.GetBindable(OsuSetting.Scaling); + scalingMode.ValueChanged += _ => updateSize(); + + sizeX = config.GetBindable(OsuSetting.ScalingSizeX); + sizeX.ValueChanged += _ => updateSize(); + + sizeY = config.GetBindable(OsuSetting.ScalingSizeY); + sizeY.ValueChanged += _ => updateSize(); + + posX = config.GetBindable(OsuSetting.ScalingPositionX); + posX.ValueChanged += _ => updateSize(); + + posY = config.GetBindable(OsuSetting.ScalingPositionY); + posY.ValueChanged += _ => updateSize(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateSize(); + sizableContainer.FinishTransforms(); + } + + private bool requiresBackgroundVisible => (scalingMode.Value == ScalingMode.Everything || scalingMode.Value == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1); + + private void updateSize() + { + const float fade_time = 500; + + if (targetMode == ScalingMode.Everything) + { + // the top level scaling container manages the background to be displayed while scaling. + if (requiresBackgroundVisible) + { + if (backgroundStack == null) + { + AddInternal(backgroundStack = new BackgroundScreenStack + { + Colour = OsuColour.Gray(0.1f), + Alpha = 0, + Depth = float.MaxValue + }); + + backgroundStack.Push(new ScalingBackgroundScreen()); + } + + backgroundStack.FadeIn(fade_time); + } + else + backgroundStack?.FadeOut(fade_time); + } + + bool scaling = targetMode == null || scalingMode.Value == targetMode; + + var targetSize = scaling ? new Vector2(sizeX.Value, sizeY.Value) : Vector2.One; + var targetPosition = scaling ? new Vector2(posX.Value, posY.Value) * (Vector2.One - targetSize) : Vector2.Zero; + bool requiresMasking = scaling && targetSize != Vector2.One; + + if (requiresMasking) + sizableContainer.Masking = true; + + sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); + sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); + } + + private class ScalingBackgroundScreen : BackgroundScreenDefault + { + public override void OnEntering(IScreen last) + { + this.FadeInFromZero(4000, Easing.OutQuint); + } + } + + private class AlwaysInputContainer : Container + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + public AlwaysInputContainer() + { + RelativeSizeAxes = Axes.Both; + } + } + } +} diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 36fdbe6e94..9d886c457f 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.Containers where T : Drawable { private Drawable expandableHeader, fixedHeader, footer, headerBackground; - private readonly ScrollContainer scrollContainer; + private readonly OsuScrollContainer scrollContainer; private readonly Container headerBackgroundContainer; private readonly FlowContainer scrollContentContainer; @@ -24,7 +24,7 @@ namespace osu.Game.Graphics.Containers public Drawable ExpandableHeader { - get { return expandableHeader; } + get => expandableHeader; set { if (value == expandableHeader) return; @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Containers public Drawable FixedHeader { - get { return fixedHeader; } + get => fixedHeader; set { if (value == fixedHeader) return; @@ -56,7 +56,7 @@ namespace osu.Game.Graphics.Containers public Drawable Footer { - get { return footer; } + get => footer; set { if (value == footer) return; @@ -75,7 +75,7 @@ namespace osu.Game.Graphics.Containers public Drawable HeaderBackground { - get { return headerBackground; } + get => headerBackground; set { if (value == headerBackground) return; @@ -110,6 +110,7 @@ namespace osu.Game.Graphics.Containers private float headerHeight, footerHeight; private readonly MarginPadding originalSectionsMargin; + private void updateSectionsMargin() { if (!Children.Any()) return; @@ -123,7 +124,7 @@ namespace osu.Game.Graphics.Containers public SectionsContainer() { - AddInternal(scrollContainer = new ScrollContainer + AddInternal(scrollContainer = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Masking = true, @@ -141,13 +142,26 @@ namespace osu.Game.Graphics.Containers public void ScrollToTop() => scrollContainer.ScrollTo(0); + public override void InvalidateFromChild(Invalidation invalidation, Drawable source = null) + { + base.InvalidateFromChild(invalidation, source); + + if ((invalidation & Invalidation.DrawSize) != 0) + { + if (source == ExpandableHeader) //We need to recalculate the positions if the ExpandableHeader changed its size + lastKnownScroll = -1; + } + } + private float lastKnownScroll; + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); float headerH = (ExpandableHeader?.LayoutSize.Y ?? 0) + (FixedHeader?.LayoutSize.Y ?? 0); float footerH = Footer?.LayoutSize.Y ?? 0; + if (headerH != headerHeight || footerH != footerHeight) { headerHeight = headerH; @@ -179,6 +193,7 @@ namespace osu.Game.Graphics.Containers foreach (var section in Children) { float diff = Math.Abs(scrollContainer.GetChildPosInContent(section) - currentScroll - scrollOffset); + if (diff < minDiff) { minDiff = diff; diff --git a/osu.Game/Graphics/Containers/ShakeContainer.cs b/osu.Game/Graphics/Containers/ShakeContainer.cs index fde4d59f46..e5a6bcc28e 100644 --- a/osu.Game/Graphics/Containers/ShakeContainer.cs +++ b/osu.Game/Graphics/Containers/ShakeContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,29 +11,43 @@ namespace osu.Game.Graphics.Containers /// public class ShakeContainer : Container { + /// + /// The length of a single shake. + /// + public float ShakeDuration = 80; + + /// + /// Total number of shakes. May be shortened if possible. + /// + public float TotalShakes = 4; + + /// + /// Pixels of displacement per shake. + /// + public float ShakeMagnitude = 8; + /// /// Shake the contents of this container. /// /// The maximum length the shake should last. - public void Shake(double maximumLength) + public void Shake(double? maximumLength = null) { const float shake_amount = 8; - const float shake_duration = 30; // if we don't have enough time, don't bother shaking. - if (maximumLength < shake_duration * 2) + if (maximumLength < ShakeDuration * 2) return; - var sequence = this.MoveToX(shake_amount, shake_duration / 2, Easing.OutSine).Then() - .MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then(); + var sequence = this.MoveToX(shake_amount, ShakeDuration / 2, Easing.OutSine).Then() + .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); // if we don't have enough time for the second shake, skip it. - if (maximumLength > shake_duration * 4) + if (!maximumLength.HasValue || maximumLength >= ShakeDuration * 4) sequence = sequence - .MoveToX(shake_amount, shake_duration, Easing.InOutSine).Then() - .MoveToX(-shake_amount, shake_duration, Easing.InOutSine).Then(); + .MoveToX(shake_amount, ShakeDuration, Easing.InOutSine).Then() + .MoveToX(-shake_amount, ShakeDuration, Easing.InOutSine).Then(); - sequence.MoveToX(0, shake_duration / 2, Easing.InSine); + sequence.MoveToX(0, ShakeDuration / 2, Easing.InSine); } } } diff --git a/osu.Game/Graphics/Containers/UserDimContainer.cs b/osu.Game/Graphics/Containers/UserDimContainer.cs new file mode 100644 index 0000000000..fe9eb7baf4 --- /dev/null +++ b/osu.Game/Graphics/Containers/UserDimContainer.cs @@ -0,0 +1,135 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Screens.Play; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.Containers +{ + /// + /// A container that applies user-configured visual settings to its contents. + /// This container specifies behavior that applies to both Storyboards and Backgrounds. + /// + public class UserDimContainer : Container + { + private const float background_fade_duration = 800; + + /// + /// Whether or not user-configured dim levels should be applied to the container. + /// + public readonly Bindable EnableUserDim = new Bindable(); + + /// + /// Whether or not the storyboard loaded should completely hide the background behind it. + /// + public readonly Bindable StoryboardReplacesBackground = new Bindable(); + + /// + /// The amount of blur to be applied to the background in addition to user-specified blur. + /// + /// + /// Used in contexts where there can potentially be both user and screen-specified blurring occuring at the same time, such as in + /// + public readonly Bindable BlurAmount = new Bindable(); + + private Bindable userDimLevel { get; set; } + + private Bindable userBlurLevel { get; set; } + + private Bindable showStoryboard { get; set; } + + protected Container DimContainer { get; } + + protected override Container Content => DimContainer; + + private readonly bool isStoryboard; + + /// + /// As an optimisation, we add the two blur portions to be applied rather than actually applying two separate blurs. + /// + private Vector2 blurTarget => EnableUserDim.Value + ? new Vector2(BlurAmount.Value + (float)userBlurLevel.Value * 25) + : new Vector2(BlurAmount.Value); + + /// + /// Creates a new . + /// + /// Whether or not this instance contains a storyboard. + /// + /// While both backgrounds and storyboards allow user dim levels to be applied, storyboards can be toggled via + /// and can cause backgrounds to become hidden via . Storyboards are also currently unable to be blurred. + /// + /// + public UserDimContainer(bool isStoryboard = false) + { + this.isStoryboard = isStoryboard; + AddInternal(DimContainer = new Container { RelativeSizeAxes = Axes.Both }); + } + + private Background background; + + public Background Background + { + get => background; + set + { + base.Add(background = value); + background.BlurTo(blurTarget, 0, Easing.OutQuint); + } + } + + public override void Add(Drawable drawable) + { + if (drawable is Background) + throw new InvalidOperationException($"Use {nameof(Background)} to set a background."); + + base.Add(drawable); + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + userDimLevel = config.GetBindable(OsuSetting.DimLevel); + userBlurLevel = config.GetBindable(OsuSetting.BlurLevel); + showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); + + EnableUserDim.ValueChanged += _ => updateVisuals(); + userDimLevel.ValueChanged += _ => updateVisuals(); + userBlurLevel.ValueChanged += _ => updateVisuals(); + showStoryboard.ValueChanged += _ => updateVisuals(); + StoryboardReplacesBackground.ValueChanged += _ => updateVisuals(); + BlurAmount.ValueChanged += _ => updateVisuals(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateVisuals(); + } + + private void updateVisuals() + { + if (isStoryboard) + { + DimContainer.FadeTo(!showStoryboard.Value || userDimLevel.Value == 1 ? 0 : 1, background_fade_duration, Easing.OutQuint); + } + else + { + // The background needs to be hidden in the case of it being replaced by the storyboard + DimContainer.FadeTo(showStoryboard.Value && StoryboardReplacesBackground.Value ? 0 : 1, background_fade_duration, Easing.OutQuint); + + Background?.BlurTo(blurTarget, background_fade_duration, Easing.OutQuint); + } + + DimContainer.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)userDimLevel.Value) : Color4.White, background_fade_duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/Containers/WaveContainer.cs b/osu.Game/Graphics/Containers/WaveContainer.cs index 96a2304ed1..f87909ab17 100644 --- a/osu.Game/Graphics/Containers/WaveContainer.cs +++ b/osu.Game/Graphics/Containers/WaveContainer.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osuTK.Graphics; @@ -102,7 +103,7 @@ namespace osu.Game.Graphics.Containers protected override void PopIn() { foreach (var w in wavesContainer.Children) - w.State = Visibility.Visible; + w.Show(); this.FadeIn(100, Easing.OutQuint); contentContainer.MoveToY(0, APPEAR_DURATION, Easing.OutQuint); @@ -116,7 +117,7 @@ namespace osu.Game.Graphics.Containers contentContainer.MoveToY(DrawHeight * 2f, DISAPPEAR_DURATION, Easing.In); foreach (var w in wavesContainer.Children) - w.State = Visibility.Hidden; + w.Hide(); this.FadeOut(DISAPPEAR_DURATION, Easing.InQuint); } diff --git a/osu.Game/Graphics/Cursor/IProvideCursor.cs b/osu.Game/Graphics/Cursor/IProvideCursor.cs index c7b23bbc00..3a920ba976 100644 --- a/osu.Game/Graphics/Cursor/IProvideCursor.cs +++ b/osu.Game/Graphics/Cursor/IProvideCursor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 87d97806cd..092a23e787 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -1,9 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -11,6 +10,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using System; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osuTK.Input; @@ -45,10 +45,12 @@ namespace osu.Game.Graphics.Cursor { var position = e.MousePosition; var distance = Vector2Extensions.Distance(position, positionMouseDown); + // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. if (dragRotationState == DragRotationState.DragStarted && distance > 30) dragRotationState = DragRotationState.Rotating; + // don't rotate when distance is zero to avoid NaN if (dragRotationState == DragRotationState.Rotating && distance > 0) { @@ -80,11 +82,12 @@ namespace osu.Game.Graphics.Cursor activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); } - if (e.Button == MouseButton.Left && cursorRotate) + if (e.Button == MouseButton.Left && cursorRotate.Value) { dragRotationState = DragRotationState.DragStarted; positionMouseDown = e.MousePosition; } + return base.OnMouseDown(e); } @@ -102,6 +105,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); dragRotationState = DragRotationState.NotDragging; } + return base.OnMouseUp(e); } @@ -156,7 +160,7 @@ namespace osu.Game.Graphics.Cursor }; cursorScale = config.GetBindable(OsuSetting.MenuCursorSize); - cursorScale.ValueChanged += newScale => cursorContainer.Scale = new Vector2((float)newScale * base_scale); + cursorScale.ValueChanged += scale => cursorContainer.Scale = new Vector2((float)scale.NewValue * base_scale); cursorScale.TriggerChange(); } } diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 5823fad93a..b7ea1ba56a 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Graphics; @@ -29,7 +29,7 @@ namespace osu.Game.Graphics.Cursor { AddRangeInternal(new Drawable[] { - Cursor = new MenuCursor { State = Visibility.Hidden }, + Cursor = new MenuCursor { State = { Value = Visibility.Hidden } }, content = new Container { RelativeSizeAxes = Axes.Both } }); } @@ -43,6 +43,7 @@ namespace osu.Game.Graphics.Cursor } private IProvideCursor currentTarget; + protected override void Update() { base.Update(); @@ -50,6 +51,7 @@ namespace osu.Game.Graphics.Cursor if (!CanShowCursor) { currentTarget?.Cursor?.Hide(); + currentTarget = null; return; } diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index 9408d63573..fbb3fa0e6c 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index e7a9aab622..cfcda892fd 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; 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.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; @@ -17,7 +17,8 @@ namespace osu.Game.Graphics.Cursor { protected override ITooltip CreateTooltip() => new OsuTooltip(); - public OsuTooltipContainer(CursorContainer cursor) : base(cursor) + public OsuTooltipContainer(CursorContainer cursor) + : base(cursor) { } @@ -36,6 +37,7 @@ namespace osu.Game.Graphics.Cursor if (value == text.Text) return; text.Text = value; + if (IsPresent) { AutoSizeDuration = 250; @@ -46,8 +48,6 @@ namespace osu.Game.Graphics.Cursor } } - private const float text_size = 16; - public OsuTooltip() { AutoSizeEasing = Easing.OutQuint; @@ -69,9 +69,8 @@ namespace osu.Game.Graphics.Cursor }, text = new OsuSpriteText { - TextSize = text_size, Padding = new MarginPadding(5), - Font = @"Exo2.0-Regular", + Font = OsuFont.GetFont(weight: FontWeight.Regular) } }; } diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 28f8bdf82f..125c994c92 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Sprites; @@ -11,13 +12,27 @@ namespace osu.Game.Graphics { public class DrawableDate : OsuSpriteText, IHasTooltip { - protected readonly DateTimeOffset Date; + private DateTimeOffset date; + + public DateTimeOffset Date + { + get => date; + set + { + if (date == value) + return; + + date = value.ToLocalTime(); + + if (LoadState >= LoadState.Ready) + updateTime(); + } + } public DrawableDate(DateTimeOffset date) { - Font = "Exo2.0-RegularItalic"; - - Date = date.ToLocalTime(); + Font = OsuFont.GetFont(weight: FontWeight.Regular, italics: true); + Date = date; } [BackgroundDependencyLoader] @@ -39,14 +54,16 @@ namespace osu.Game.Graphics var diffToNow = DateTimeOffset.Now.Subtract(Date); double timeUntilNextUpdate = 1000; - if (diffToNow.TotalSeconds > 60) + + if (Math.Abs(diffToNow.TotalSeconds) > 120) { timeUntilNextUpdate *= 60; - if (diffToNow.TotalMinutes > 60) + + if (Math.Abs(diffToNow.TotalMinutes) > 120) { timeUntilNextUpdate *= 60; - if (diffToNow.TotalHours > 24) + if (Math.Abs(diffToNow.TotalHours) > 48) timeUntilNextUpdate *= 24; } } diff --git a/osu.Game/Graphics/IHasAccentColour.cs b/osu.Game/Graphics/IHasAccentColour.cs index 0718e84a34..1a66819379 100644 --- a/osu.Game/Graphics/IHasAccentColour.cs +++ b/osu.Game/Graphics/IHasAccentColour.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Graphics; diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index fc627fa501..63ec24f84f 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK.Graphics; @@ -20,12 +20,14 @@ namespace osu.Game.Graphics { default: throw new ArgumentException(@"Invalid hex string length!"); + case 3: return new Color4( (byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17), (byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17), 255); + case 6: return new Color4( Convert.ToByte(hex.Substring(0, 2), 16), @@ -38,8 +40,10 @@ namespace osu.Game.Graphics // See https://github.com/ppy/osu-web/blob/master/resources/assets/less/colors.less public readonly Color4 PurpleLighter = FromHex(@"eeeeff"); public readonly Color4 PurpleLight = FromHex(@"aa88ff"); + public readonly Color4 PurpleLightAlternative = FromHex(@"cba4da"); public readonly Color4 Purple = FromHex(@"8866ee"); public readonly Color4 PurpleDark = FromHex(@"6644cc"); + public readonly Color4 PurpleDarkAlternative = FromHex(@"312436"); public readonly Color4 PurpleDarker = FromHex(@"441188"); public readonly Color4 PinkLighter = FromHex(@"ffddee"); @@ -66,6 +70,48 @@ namespace osu.Game.Graphics public readonly Color4 GreenDark = FromHex(@"668800"); public readonly Color4 GreenDarker = FromHex(@"445500"); + public readonly Color4 Sky = FromHex(@"6bb5ff"); + public readonly Color4 GreySkyLighter = FromHex(@"c6e3f4"); + public readonly Color4 GreySkyLight = FromHex(@"8ab3cc"); + public readonly Color4 GreySky = FromHex(@"405461"); + public readonly Color4 GreySkyDark = FromHex(@"303d47"); + public readonly Color4 GreySkyDarker = FromHex(@"21272c"); + + public readonly Color4 Seafoam = FromHex(@"05ffa2"); + public readonly Color4 GreySeafoamLighter = FromHex(@"9ebab1"); + public readonly Color4 GreySeafoamLight = FromHex(@"4d7365"); + public readonly Color4 GreySeafoam = FromHex(@"33413c"); + public readonly Color4 GreySeafoamDark = FromHex(@"2c3532"); + public readonly Color4 GreySeafoamDarker = FromHex(@"1e2422"); + + public readonly Color4 Cyan = FromHex(@"05f4fd"); + public readonly Color4 GreyCyanLighter = FromHex(@"77b1b3"); + public readonly Color4 GreyCyanLight = FromHex(@"436d6f"); + public readonly Color4 GreyCyan = FromHex(@"293d3e"); + public readonly Color4 GreyCyanDark = FromHex(@"243536"); + public readonly Color4 GreyCyanDarker = FromHex(@"1e2929"); + + public readonly Color4 Lime = FromHex(@"82ff05"); + public readonly Color4 GreyLimeLighter = FromHex(@"deff87"); + public readonly Color4 GreyLimeLight = FromHex(@"657259"); + public readonly Color4 GreyLime = FromHex(@"3f443a"); + public readonly Color4 GreyLimeDark = FromHex(@"32352e"); + public readonly Color4 GreyLimeDarker = FromHex(@"2e302b"); + + public readonly Color4 Violet = FromHex(@"bf04ff"); + public readonly Color4 GreyVioletLighter = FromHex(@"ebb8fe"); + public readonly Color4 GreyVioletLight = FromHex(@"685370"); + public readonly Color4 GreyViolet = FromHex(@"46334d"); + public readonly Color4 GreyVioletDark = FromHex(@"2c2230"); + public readonly Color4 GreyVioletDarker = FromHex(@"201823"); + + public readonly Color4 Carmine = FromHex(@"ff0542"); + public readonly Color4 GreyCarmineLighter = FromHex(@"deaab4"); + public readonly Color4 GreyCarmineLight = FromHex(@"644f53"); + public readonly Color4 GreyCarmine = FromHex(@"342b2d"); + public readonly Color4 GreyCarmineDark = FromHex(@"302a2b"); + public readonly Color4 GreyCarmineDarker = FromHex(@"241d1e"); + public readonly Color4 Gray0 = FromHex(@"000"); public readonly Color4 Gray1 = FromHex(@"111"); public readonly Color4 Gray2 = FromHex(@"222"); diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs new file mode 100644 index 0000000000..22250d4a56 --- /dev/null +++ b/osu.Game/Graphics/OsuFont.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics +{ + public static class OsuFont + { + /// + /// The default font size. + /// + public const float DEFAULT_FONT_SIZE = 16; + + /// + /// The default font. + /// + public static FontUsage Default => GetFont(); + + public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Regular); + + /// + /// Retrieves a . + /// + /// The font typeface. + /// The size of the text in local space. For a value of 16, a single line will have a height of 16px. + /// The font weight. + /// Whether the font is italic. + /// Whether all characters should be spaced the same distance apart. + /// The . + public static FontUsage GetFont(Typeface typeface = Typeface.Exo, float size = DEFAULT_FONT_SIZE, FontWeight weight = FontWeight.Medium, bool italics = false, bool fixedWidth = false) + => new FontUsage(GetFamilyString(typeface), size, GetWeightString(typeface, weight), italics, fixedWidth); + + /// + /// Retrieves the string representation of a . + /// + /// The . + /// The string representation. + public static string GetFamilyString(Typeface typeface) + { + switch (typeface) + { + case Typeface.Exo: + return "Exo2.0"; + + case Typeface.Venera: + return "Venera"; + } + + return null; + } + + /// + /// Retrieves the string representation of a . + /// + /// The . + /// The . + /// The string representation of in the specified . + public static string GetWeightString(Typeface typeface, FontWeight weight) + => GetWeightString(GetFamilyString(typeface), weight); + + /// + /// Retrieves the string representation of a . + /// + /// The family string. + /// The . + /// The string representation of in the specified . + public static string GetWeightString(string family, FontWeight weight) + { + string weightString = weight.ToString(); + + // Only exo has an explicit "regular" weight, other fonts do not + if (family != GetFamilyString(Typeface.Exo) && weight == FontWeight.Regular) + weightString = string.Empty; + + return weightString; + } + } + + public static class OsuFontExtensions + { + /// + /// Creates a new by applying adjustments to this . + /// + /// The base . + /// The font typeface. If null, the value is copied from this . + /// The text size. If null, the value is copied from this . + /// The font weight. If null, the value is copied from this . + /// Whether the font is italic. If null, the value is copied from this . + /// Whether all characters should be spaced apart the same distance. If null, the value is copied from this . + /// The resulting . + public static FontUsage With(this FontUsage usage, Typeface? typeface = null, float? size = null, FontWeight? weight = null, bool? italics = null, bool? fixedWidth = null) + { + string familyString = typeface != null ? OsuFont.GetFamilyString(typeface.Value) : usage.Family; + string weightString = weight != null ? OsuFont.GetWeightString(familyString, weight.Value) : usage.Weight; + + return usage.With(familyString, size, weightString, italics, fixedWidth); + } + } + + public enum Typeface + { + Exo, + Venera, + } + + public enum FontWeight + { + Light, + Regular, + Medium, + SemiBold, + Bold, + Black + } +} diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs new file mode 100644 index 0000000000..982e9dacab --- /dev/null +++ b/osu.Game/Graphics/OsuIcon.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Graphics +{ + public static class OsuIcon + { + public static IconUsage Get(int icon) => new IconUsage((char)icon, "osuFont"); + + // ruleset icons in circles + public static IconUsage RulesetOsu => Get(0xe000); + public static IconUsage RulesetMania => Get(0xe001); + public static IconUsage RulesetCatch => Get(0xe002); + public static IconUsage RulesetTaiko => Get(0xe003); + + // ruleset icons without circles + public static IconUsage FilledCircle => Get(0xe004); + public static IconUsage CrossCircle => Get(0xe005); + public static IconUsage Logo => Get(0xe006); + public static IconUsage ChevronDownCircle => Get(0xe007); + public static IconUsage EditCircle => Get(0xe033); + public static IconUsage LeftCircle => Get(0xe034); + public static IconUsage RightCircle => Get(0xe035); + public static IconUsage Charts => Get(0xe036); + public static IconUsage Solo => Get(0xe037); + public static IconUsage Multi => Get(0xe038); + public static IconUsage Gear => Get(0xe039); + + // misc icons + public static IconUsage Bat => Get(0xe008); + public static IconUsage Bubble => Get(0xe009); + public static IconUsage BubblePop => Get(0xe02e); + public static IconUsage Dice => Get(0xe011); + public static IconUsage Heart => Get(0xe02f); + public static IconUsage HeartBreak => Get(0xe030); + public static IconUsage Hot => Get(0xe031); + public static IconUsage ListSearch => Get(0xe032); + + //osu! playstyles + public static IconUsage PlaystyleTablet => Get(0xe02a); + public static IconUsage PlaystyleMouse => Get(0xe029); + public static IconUsage PlaystyleKeyboard => Get(0xe02b); + public static IconUsage PlaystyleTouch => Get(0xe02c); + + // osu! difficulties + public static IconUsage EasyOsu => Get(0xe015); + public static IconUsage NormalOsu => Get(0xe016); + public static IconUsage HardOsu => Get(0xe017); + public static IconUsage InsaneOsu => Get(0xe018); + public static IconUsage ExpertOsu => Get(0xe019); + + // taiko difficulties + public static IconUsage EasyTaiko => Get(0xe01a); + public static IconUsage NormalTaiko => Get(0xe01b); + public static IconUsage HardTaiko => Get(0xe01c); + public static IconUsage InsaneTaiko => Get(0xe01d); + public static IconUsage ExpertTaiko => Get(0xe01e); + + // fruits difficulties + public static IconUsage EasyFruits => Get(0xe01f); + public static IconUsage NormalFruits => Get(0xe020); + public static IconUsage HardFruits => Get(0xe021); + public static IconUsage InsaneFruits => Get(0xe022); + public static IconUsage ExpertFruits => Get(0xe023); + + // mania difficulties + public static IconUsage EasyMania => Get(0xe024); + public static IconUsage NormalMania => Get(0xe025); + public static IconUsage HardMania => Get(0xe026); + public static IconUsage InsaneMania => Get(0xe027); + public static IconUsage ExpertMania => Get(0xe028); + + // mod icons + public static IconUsage ModPerfect => Get(0xe049); + public static IconUsage ModAutopilot => Get(0xe03a); + public static IconUsage ModAuto => Get(0xe03b); + public static IconUsage ModCinema => Get(0xe03c); + public static IconUsage ModDoubletime => Get(0xe03d); + public static IconUsage ModEasy => Get(0xe03e); + public static IconUsage ModFlashlight => Get(0xe03f); + public static IconUsage ModHalftime => Get(0xe040); + public static IconUsage ModHardrock => Get(0xe041); + public static IconUsage ModHidden => Get(0xe042); + public static IconUsage ModNightcore => Get(0xe043); + public static IconUsage ModNofail => Get(0xe044); + public static IconUsage ModRelax => Get(0xe045); + public static IconUsage ModSpunout => Get(0xe046); + public static IconUsage ModSuddendeath => Get(0xe047); + public static IconUsage ModTarget => Get(0xe048); + public static IconUsage ModBg => Get(0xe04a); + } +} diff --git a/osu.Game/Graphics/ScreenshotManager.cs b/osu.Game/Graphics/ScreenshotManager.cs index be253f65c1..5ad5e5569a 100644 --- a/osu.Game/Graphics/ScreenshotManager.cs +++ b/osu.Game/Graphics/ScreenshotManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.IO; @@ -8,7 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -51,7 +51,7 @@ namespace osu.Game.Graphics screenshotFormat = config.GetBindable(OsuSetting.ScreenshotFormat); captureMenuCursor = config.GetBindable(OsuSetting.ScreenshotCaptureMenuCursor); - shutter = audio.Sample.Get("UI/shutter"); + shutter = audio.Samples.Get("UI/shutter"); } public bool OnPressed(GlobalAction action) @@ -104,9 +104,11 @@ namespace osu.Game.Graphics case ScreenshotFormat.Png: image.SaveAsPng(stream); break; + case ScreenshotFormat.Jpg: image.SaveAsJpeg(stream); break; + default: throw new ArgumentOutOfRangeException(nameof(screenshotFormat)); } @@ -127,7 +129,7 @@ namespace osu.Game.Graphics { base.Update(); - if (cursorVisibility == false && Interlocked.CompareExchange(ref screenShotTasks, 0, 0) == 0) + if (cursorVisibility.Value == false && Interlocked.CompareExchange(ref screenShotTasks, 0, 0) == 0) cursorVisibility.Value = true; } diff --git a/osu.Game/Graphics/SpriteIcon.cs b/osu.Game/Graphics/SpriteIcon.cs deleted file mode 100644 index 2d63d0a52c..0000000000 --- a/osu.Game/Graphics/SpriteIcon.cs +++ /dev/null @@ -1,1009 +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 osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics; -using osu.Framework.IO.Stores; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Caching; - -namespace osu.Game.Graphics -{ - public class SpriteIcon : CompositeDrawable - { - private Sprite spriteShadow; - private Sprite spriteMain; - - private Cached layout = new Cached(); - private Container shadowVisibility; - - private FontStore store; - - [BackgroundDependencyLoader] - private void load(FontStore store) - { - this.store = store; - - InternalChildren = new Drawable[] - { - shadowVisibility = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = spriteShadow = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Y = 2, - Colour = new Color4(0f, 0f, 0f, 0.2f), - }, - Alpha = shadow ? 1 : 0, - }, - spriteMain = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit - }, - }; - - updateTexture(); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateTexture(); - } - - private FontAwesome loadedIcon; - private void updateTexture() - { - var loadableIcon = icon; - - if (loadableIcon == loadedIcon) return; - - var texture = store.Get(((char)loadableIcon).ToString()); - - spriteMain.Texture = texture; - spriteShadow.Texture = texture; - - if (Size == Vector2.Zero) - Size = new Vector2(texture?.DisplayWidth ?? 0, texture?.DisplayHeight ?? 0); - - loadedIcon = loadableIcon; - } - - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) - { - if ((invalidation & Invalidation.Colour) > 0 && Shadow) - layout.Invalidate(); - return base.Invalidate(invalidation, source, shallPropagate); - } - - protected override void Update() - { - if (!layout.IsValid) - { - //adjust shadow alpha based on highest component intensity to avoid muddy display of darker text. - //squared result for quadratic fall-off seems to give the best result. - var avgColour = (Color4)DrawColourInfo.Colour.AverageColour; - - spriteShadow.Alpha = (float)Math.Pow(Math.Max(Math.Max(avgColour.R, avgColour.G), avgColour.B), 2); - - layout.Validate(); - } - } - - private bool shadow; - public bool Shadow - { - get { return shadow; } - set - { - shadow = value; - if (shadowVisibility != null) - shadowVisibility.Alpha = value ? 1 : 0; - } - } - - private FontAwesome icon; - - public FontAwesome Icon - { - get - { - return icon; - } - - set - { - if (icon == value) return; - - icon = value; - if (LoadState == LoadState.Loaded) - updateTexture(); - } - } - } - - public enum FontAwesome - { - fa_500px = 0xf26e, - fa_address_book = 0xf2b9, - fa_address_book_o = 0xf2ba, - fa_address_card = 0xf2bb, - fa_address_card_o = 0xf2bc, - fa_adjust = 0xf042, - fa_adn = 0xf170, - fa_align_center = 0xf037, - fa_align_justify = 0xf039, - fa_align_left = 0xf036, - fa_align_right = 0xf038, - fa_amazon = 0xf270, - fa_ambulance = 0xf0f9, - fa_american_sign_language_interpreting = 0xf2a3, - fa_anchor = 0xf13d, - fa_android = 0xf17b, - fa_angellist = 0xf209, - fa_angle_double_down = 0xf103, - fa_angle_double_left = 0xf100, - fa_angle_double_right = 0xf101, - fa_angle_double_up = 0xf102, - fa_angle_down = 0xf107, - fa_angle_left = 0xf104, - fa_angle_right = 0xf105, - fa_angle_up = 0xf106, - fa_apple = 0xf179, - fa_archive = 0xf187, - fa_area_chart = 0xf1fe, - fa_arrow_circle_down = 0xf0ab, - fa_arrow_circle_left = 0xf0a8, - fa_arrow_circle_o_down = 0xf01a, - fa_arrow_circle_o_left = 0xf190, - fa_arrow_circle_o_right = 0xf18e, - fa_arrow_circle_o_up = 0xf01b, - fa_arrow_circle_right = 0xf0a9, - fa_arrow_circle_up = 0xf0aa, - fa_arrow_down = 0xf063, - fa_arrow_left = 0xf060, - fa_arrow_right = 0xf061, - fa_arrow_up = 0xf062, - fa_arrows = 0xf047, - fa_arrows_alt = 0xf0b2, - fa_arrows_h = 0xf07e, - fa_arrows_v = 0xf07d, - fa_asl_interpreting = 0xf2a3, - fa_assistive_listening_systems = 0xf2a2, - fa_asterisk = 0xf069, - fa_at = 0xf1fa, - fa_audio_description = 0xf29e, - fa_automobile = 0xf1b9, - fa_backward = 0xf04a, - fa_balance_scale = 0xf24e, - fa_ban = 0xf05e, - fa_bandcamp = 0xf2d5, - fa_bank = 0xf19c, - fa_bar_chart = 0xf080, - fa_bar_chart_o = 0xf080, - fa_barcode = 0xf02a, - fa_bars = 0xf0c9, - fa_bath = 0xf2cd, - fa_bathtub = 0xf2cd, - fa_battery = 0xf240, - fa_battery_0 = 0xf244, - fa_battery_1 = 0xf243, - fa_battery_2 = 0xf242, - fa_battery_3 = 0xf241, - fa_battery_4 = 0xf240, - fa_battery_empty = 0xf244, - fa_battery_full = 0xf240, - fa_battery_half = 0xf242, - fa_battery_quarter = 0xf243, - fa_battery_three_quarters = 0xf241, - fa_bed = 0xf236, - fa_beer = 0xf0fc, - fa_behance = 0xf1b4, - fa_behance_square = 0xf1b5, - fa_bell = 0xf0f3, - fa_bell_o = 0xf0a2, - fa_bell_slash = 0xf1f6, - fa_bell_slash_o = 0xf1f7, - fa_bicycle = 0xf206, - fa_binoculars = 0xf1e5, - fa_birthday_cake = 0xf1fd, - fa_bitbucket = 0xf171, - fa_bitbucket_square = 0xf172, - fa_bitcoin = 0xf15a, - fa_black_tie = 0xf27e, - fa_blind = 0xf29d, - fa_bluetooth = 0xf293, - fa_bluetooth_b = 0xf294, - fa_bold = 0xf032, - fa_bolt = 0xf0e7, - fa_bomb = 0xf1e2, - fa_book = 0xf02d, - fa_bookmark = 0xf02e, - fa_bookmark_o = 0xf097, - fa_braille = 0xf2a1, - fa_briefcase = 0xf0b1, - fa_btc = 0xf15a, - fa_bug = 0xf188, - fa_building = 0xf1ad, - fa_building_o = 0xf0f7, - fa_bullhorn = 0xf0a1, - fa_bullseye = 0xf140, - fa_bus = 0xf207, - fa_buysellads = 0xf20d, - fa_cab = 0xf1ba, - fa_calculator = 0xf1ec, - fa_calendar = 0xf073, - fa_calendar_check_o = 0xf274, - fa_calendar_minus_o = 0xf272, - fa_calendar_o = 0xf133, - fa_calendar_plus_o = 0xf271, - fa_calendar_times_o = 0xf273, - fa_camera = 0xf030, - fa_camera_retro = 0xf083, - fa_car = 0xf1b9, - fa_caret_down = 0xf0d7, - fa_caret_left = 0xf0d9, - fa_caret_right = 0xf0da, - fa_caret_square_o_down = 0xf150, - fa_caret_square_o_left = 0xf191, - fa_caret_square_o_right = 0xf152, - fa_caret_square_o_up = 0xf151, - fa_caret_up = 0xf0d8, - fa_cart_arrow_down = 0xf218, - fa_cart_plus = 0xf217, - fa_cc = 0xf20a, - fa_cc_amex = 0xf1f3, - fa_cc_diners_club = 0xf24c, - fa_cc_discover = 0xf1f2, - fa_cc_jcb = 0xf24b, - fa_cc_mastercard = 0xf1f1, - fa_cc_paypal = 0xf1f4, - fa_cc_stripe = 0xf1f5, - fa_cc_visa = 0xf1f0, - fa_certificate = 0xf0a3, - fa_chain = 0xf0c1, - fa_chain_broken = 0xf127, - fa_check = 0xf00c, - fa_check_circle = 0xf058, - fa_check_circle_o = 0xf05d, - fa_check_square = 0xf14a, - fa_check_square_o = 0xf046, - fa_chevron_circle_down = 0xf13a, - fa_chevron_circle_left = 0xf137, - fa_chevron_circle_right = 0xf138, - fa_chevron_circle_up = 0xf139, - fa_chevron_down = 0xf078, - fa_chevron_left = 0xf053, - fa_chevron_right = 0xf054, - fa_chevron_up = 0xf077, - fa_child = 0xf1ae, - fa_chrome = 0xf268, - fa_circle = 0xf111, - fa_circle_o = 0xf10c, - fa_circle_o_notch = 0xf1ce, - fa_circle_thin = 0xf1db, - fa_clipboard = 0xf0ea, - fa_clock_o = 0xf017, - fa_clone = 0xf24d, - fa_close = 0xf00d, - fa_cloud = 0xf0c2, - fa_cloud_download = 0xf0ed, - fa_cloud_upload = 0xf0ee, - fa_cny = 0xf157, - fa_code = 0xf121, - fa_code_fork = 0xf126, - fa_codepen = 0xf1cb, - fa_codiepie = 0xf284, - fa_coffee = 0xf0f4, - fa_cog = 0xf013, - fa_cogs = 0xf085, - fa_columns = 0xf0db, - fa_comment = 0xf075, - fa_comment_o = 0xf0e5, - fa_commenting = 0xf27a, - fa_commenting_o = 0xf27b, - fa_comments = 0xf086, - fa_comments_o = 0xf0e6, - fa_compass = 0xf14e, - fa_compress = 0xf066, - fa_connectdevelop = 0xf20e, - fa_contao = 0xf26d, - fa_copy = 0xf0c5, - fa_copyright = 0xf1f9, - fa_creative_commons = 0xf25e, - fa_credit_card = 0xf09d, - fa_credit_card_alt = 0xf283, - fa_crop = 0xf125, - fa_crosshairs = 0xf05b, - fa_css3 = 0xf13c, - fa_cube = 0xf1b2, - fa_cubes = 0xf1b3, - fa_cut = 0xf0c4, - fa_cutlery = 0xf0f5, - fa_dashboard = 0xf0e4, - fa_dashcube = 0xf210, - fa_database = 0xf1c0, - fa_deaf = 0xf2a4, - fa_deafness = 0xf2a4, - fa_dedent = 0xf03b, - fa_delicious = 0xf1a5, - fa_desktop = 0xf108, - fa_deviantart = 0xf1bd, - fa_diamond = 0xf219, - fa_digg = 0xf1a6, - fa_dollar = 0xf155, - fa_dot_circle_o = 0xf192, - fa_download = 0xf019, - fa_dribbble = 0xf17d, - fa_drivers_license = 0xf2c2, - fa_drivers_license_o = 0xf2c3, - fa_dropbox = 0xf16b, - fa_drupal = 0xf1a9, - fa_edge = 0xf282, - fa_edit = 0xf044, - fa_eercast = 0xf2da, - fa_eject = 0xf052, - fa_ellipsis_h = 0xf141, - fa_ellipsis_v = 0xf142, - fa_empire = 0xf1d1, - fa_envelope = 0xf0e0, - fa_envelope_o = 0xf003, - fa_envelope_open = 0xf2b6, - fa_envelope_open_o = 0xf2b7, - fa_envelope_square = 0xf199, - fa_envira = 0xf299, - fa_eraser = 0xf12d, - fa_etsy = 0xf2d7, - fa_eur = 0xf153, - fa_euro = 0xf153, - fa_exchange = 0xf0ec, - fa_exclamation = 0xf12a, - fa_exclamation_circle = 0xf06a, - fa_exclamation_triangle = 0xf071, - fa_expand = 0xf065, - fa_expeditedssl = 0xf23e, - fa_external_link = 0xf08e, - fa_external_link_square = 0xf14c, - fa_eye = 0xf06e, - fa_eye_slash = 0xf070, - fa_eyedropper = 0xf1fb, - fa_fa = 0xf2b4, - fa_facebook = 0xf09a, - fa_facebook_f = 0xf09a, - fa_facebook_official = 0xf230, - fa_facebook_square = 0xf082, - fa_fast_backward = 0xf049, - fa_fast_forward = 0xf050, - fa_fax = 0xf1ac, - fa_feed = 0xf09e, - fa_female = 0xf182, - fa_fighter_jet = 0xf0fb, - fa_file = 0xf15b, - fa_file_archive_o = 0xf1c6, - fa_file_audio_o = 0xf1c7, - fa_file_code_o = 0xf1c9, - fa_file_excel_o = 0xf1c3, - fa_file_image_o = 0xf1c5, - fa_file_movie_o = 0xf1c8, - fa_file_o = 0xf016, - fa_file_pdf_o = 0xf1c1, - fa_file_photo_o = 0xf1c5, - fa_file_picture_o = 0xf1c5, - fa_file_powerpoint_o = 0xf1c4, - fa_file_sound_o = 0xf1c7, - fa_file_text = 0xf15c, - fa_file_text_o = 0xf0f6, - fa_file_video_o = 0xf1c8, - fa_file_word_o = 0xf1c2, - fa_file_zip_o = 0xf1c6, - fa_files_o = 0xf0c5, - fa_film = 0xf008, - fa_filter = 0xf0b0, - fa_fire = 0xf06d, - fa_fire_extinguisher = 0xf134, - fa_firefox = 0xf269, - fa_first_order = 0xf2b0, - fa_flag = 0xf024, - fa_flag_checkered = 0xf11e, - fa_flag_o = 0xf11d, - fa_flash = 0xf0e7, - fa_flask = 0xf0c3, - fa_flickr = 0xf16e, - fa_floppy_o = 0xf0c7, - fa_folder = 0xf07b, - fa_folder_o = 0xf114, - fa_folder_open = 0xf07c, - fa_folder_open_o = 0xf115, - fa_font = 0xf031, - fa_font_awesome = 0xf2b4, - fa_fonticons = 0xf280, - fa_fort_awesome = 0xf286, - fa_forumbee = 0xf211, - fa_forward = 0xf04e, - fa_foursquare = 0xf180, - fa_free_code_camp = 0xf2c5, - fa_frown_o = 0xf119, - fa_futbol_o = 0xf1e3, - fa_gamepad = 0xf11b, - fa_gavel = 0xf0e3, - fa_gbp = 0xf154, - fa_ge = 0xf1d1, - fa_gear = 0xf013, - fa_gears = 0xf085, - fa_genderless = 0xf22d, - fa_get_pocket = 0xf265, - fa_gg = 0xf260, - fa_gg_circle = 0xf261, - fa_gift = 0xf06b, - fa_git = 0xf1d3, - fa_git_square = 0xf1d2, - fa_github = 0xf09b, - fa_github_alt = 0xf113, - fa_github_square = 0xf092, - fa_gitlab = 0xf296, - fa_gittip = 0xf184, - fa_glass = 0xf000, - fa_glide = 0xf2a5, - fa_glide_g = 0xf2a6, - fa_globe = 0xf0ac, - fa_google = 0xf1a0, - fa_google_plus = 0xf0d5, - fa_google_plus_circle = 0xf2b3, - fa_google_plus_official = 0xf2b3, - fa_google_plus_square = 0xf0d4, - fa_google_wallet = 0xf1ee, - fa_graduation_cap = 0xf19d, - fa_gratipay = 0xf184, - fa_grav = 0xf2d6, - fa_group = 0xf0c0, - fa_h_square = 0xf0fd, - fa_hacker_news = 0xf1d4, - fa_hand_grab_o = 0xf255, - fa_hand_lizard_o = 0xf258, - fa_hand_o_down = 0xf0a7, - fa_hand_o_left = 0xf0a5, - fa_hand_o_right = 0xf0a4, - fa_hand_o_up = 0xf0a6, - fa_hand_paper_o = 0xf256, - fa_hand_peace_o = 0xf25b, - fa_hand_pointer_o = 0xf25a, - fa_hand_rock_o = 0xf255, - fa_hand_scissors_o = 0xf257, - fa_hand_spock_o = 0xf259, - fa_hand_stop_o = 0xf256, - fa_handshake_o = 0xf2b5, - fa_hard_of_hearing = 0xf2a4, - fa_hashtag = 0xf292, - fa_hdd_o = 0xf0a0, - fa_header = 0xf1dc, - fa_headphones = 0xf025, - fa_heart = 0xf004, - fa_heart_o = 0xf08a, - fa_heartbeat = 0xf21e, - fa_history = 0xf1da, - fa_home = 0xf015, - fa_hospital_o = 0xf0f8, - fa_hotel = 0xf236, - fa_hourglass = 0xf254, - fa_hourglass_1 = 0xf251, - fa_hourglass_2 = 0xf252, - fa_hourglass_3 = 0xf253, - fa_hourglass_end = 0xf253, - fa_hourglass_half = 0xf252, - fa_hourglass_o = 0xf250, - fa_hourglass_start = 0xf251, - fa_houzz = 0xf27c, - fa_html5 = 0xf13b, - fa_i_cursor = 0xf246, - fa_id_badge = 0xf2c1, - fa_id_card = 0xf2c2, - fa_id_card_o = 0xf2c3, - fa_ils = 0xf20b, - fa_image = 0xf03e, - fa_imdb = 0xf2d8, - fa_inbox = 0xf01c, - fa_indent = 0xf03c, - fa_industry = 0xf275, - fa_info = 0xf129, - fa_info_circle = 0xf05a, - fa_inr = 0xf156, - fa_instagram = 0xf16d, - fa_institution = 0xf19c, - fa_internet_explorer = 0xf26b, - fa_intersex = 0xf224, - fa_ioxhost = 0xf208, - fa_italic = 0xf033, - fa_joomla = 0xf1aa, - fa_jpy = 0xf157, - fa_jsfiddle = 0xf1cc, - fa_key = 0xf084, - fa_keyboard_o = 0xf11c, - fa_krw = 0xf159, - fa_language = 0xf1ab, - fa_laptop = 0xf109, - fa_lastfm = 0xf202, - fa_lastfm_square = 0xf203, - fa_leaf = 0xf06c, - fa_leanpub = 0xf212, - fa_legal = 0xf0e3, - fa_lemon_o = 0xf094, - fa_level_down = 0xf149, - fa_level_up = 0xf148, - fa_life_bouy = 0xf1cd, - fa_life_buoy = 0xf1cd, - fa_life_ring = 0xf1cd, - fa_life_saver = 0xf1cd, - fa_lightbulb_o = 0xf0eb, - fa_line_chart = 0xf201, - fa_link = 0xf0c1, - fa_linkedin = 0xf0e1, - fa_linkedin_square = 0xf08c, - fa_linode = 0xf2b8, - fa_linux = 0xf17c, - fa_list = 0xf03a, - fa_list_alt = 0xf022, - fa_list_ol = 0xf0cb, - fa_list_ul = 0xf0ca, - fa_location_arrow = 0xf124, - fa_lock = 0xf023, - fa_long_arrow_down = 0xf175, - fa_long_arrow_left = 0xf177, - fa_long_arrow_right = 0xf178, - fa_long_arrow_up = 0xf176, - fa_low_vision = 0xf2a8, - fa_magic = 0xf0d0, - fa_magnet = 0xf076, - fa_mail_forward = 0xf064, - fa_mail_reply = 0xf112, - fa_mail_reply_all = 0xf122, - fa_male = 0xf183, - fa_map = 0xf279, - fa_map_marker = 0xf041, - fa_map_o = 0xf278, - fa_map_pin = 0xf276, - fa_map_signs = 0xf277, - fa_mars = 0xf222, - fa_mars_double = 0xf227, - fa_mars_stroke = 0xf229, - fa_mars_stroke_h = 0xf22b, - fa_mars_stroke_v = 0xf22a, - fa_maxcdn = 0xf136, - fa_meanpath = 0xf20c, - fa_medium = 0xf23a, - fa_medkit = 0xf0fa, - fa_meetup = 0xf2e0, - fa_meh_o = 0xf11a, - fa_mercury = 0xf223, - fa_microchip = 0xf2db, - fa_microphone = 0xf130, - fa_microphone_slash = 0xf131, - fa_minus = 0xf068, - fa_minus_circle = 0xf056, - fa_minus_square = 0xf146, - fa_minus_square_o = 0xf147, - fa_mixcloud = 0xf289, - fa_mobile = 0xf10b, - fa_mobile_phone = 0xf10b, - fa_modx = 0xf285, - fa_money = 0xf0d6, - fa_moon_o = 0xf186, - fa_mortar_board = 0xf19d, - fa_motorcycle = 0xf21c, - fa_mouse_pointer = 0xf245, - fa_music = 0xf001, - fa_navicon = 0xf0c9, - fa_neuter = 0xf22c, - fa_newspaper_o = 0xf1ea, - fa_object_group = 0xf247, - fa_object_ungroup = 0xf248, - fa_odnoklassniki = 0xf263, - fa_odnoklassniki_square = 0xf264, - fa_opencart = 0xf23d, - fa_openid = 0xf19b, - fa_opera = 0xf26a, - fa_optin_monster = 0xf23c, - fa_outdent = 0xf03b, - fa_pagelines = 0xf18c, - fa_paint_brush = 0xf1fc, - fa_paper_plane = 0xf1d8, - fa_paper_plane_o = 0xf1d9, - fa_paperclip = 0xf0c6, - fa_paragraph = 0xf1dd, - fa_paste = 0xf0ea, - fa_pause = 0xf04c, - fa_pause_circle = 0xf28b, - fa_pause_circle_o = 0xf28c, - fa_paw = 0xf1b0, - fa_paypal = 0xf1ed, - fa_pencil = 0xf040, - fa_pencil_square = 0xf14b, - fa_pencil_square_o = 0xf044, - fa_percent = 0xf295, - fa_phone = 0xf095, - fa_phone_square = 0xf098, - fa_photo = 0xf03e, - fa_picture_o = 0xf03e, - fa_pie_chart = 0xf200, - fa_pied_piper = 0xf2ae, - fa_pied_piper_alt = 0xf1a8, - fa_pied_piper_pp = 0xf1a7, - fa_pinterest = 0xf0d2, - fa_pinterest_p = 0xf231, - fa_pinterest_square = 0xf0d3, - fa_plane = 0xf072, - fa_play = 0xf04b, - fa_play_circle = 0xf144, - fa_play_circle_o = 0xf01d, - fa_plug = 0xf1e6, - fa_plus = 0xf067, - fa_plus_circle = 0xf055, - fa_plus_square = 0xf0fe, - fa_plus_square_o = 0xf196, - fa_podcast = 0xf2ce, - fa_power_off = 0xf011, - fa_print = 0xf02f, - fa_product_hunt = 0xf288, - fa_puzzle_piece = 0xf12e, - fa_qq = 0xf1d6, - fa_qrcode = 0xf029, - fa_question = 0xf128, - fa_question_circle = 0xf059, - fa_question_circle_o = 0xf29c, - fa_quora = 0xf2c4, - fa_quote_left = 0xf10d, - fa_quote_right = 0xf10e, - fa_ra = 0xf1d0, - fa_random = 0xf074, - fa_ravelry = 0xf2d9, - fa_rebel = 0xf1d0, - fa_recycle = 0xf1b8, - fa_reddit = 0xf1a1, - fa_reddit_alien = 0xf281, - fa_reddit_square = 0xf1a2, - fa_refresh = 0xf021, - fa_registered = 0xf25d, - fa_remove = 0xf00d, - fa_renren = 0xf18b, - fa_reorder = 0xf0c9, - fa_repeat = 0xf01e, - fa_reply = 0xf112, - fa_reply_all = 0xf122, - fa_resistance = 0xf1d0, - fa_retweet = 0xf079, - fa_rmb = 0xf157, - fa_road = 0xf018, - fa_rocket = 0xf135, - fa_rotate_left = 0xf0e2, - fa_rotate_right = 0xf01e, - fa_rouble = 0xf158, - fa_rss = 0xf09e, - fa_rss_square = 0xf143, - fa_rub = 0xf158, - fa_ruble = 0xf158, - fa_rupee = 0xf156, - fa_s15 = 0xf2cd, - fa_safari = 0xf267, - fa_save = 0xf0c7, - fa_scissors = 0xf0c4, - fa_scribd = 0xf28a, - fa_search = 0xf002, - fa_search_minus = 0xf010, - fa_search_plus = 0xf00e, - fa_sellsy = 0xf213, - fa_send = 0xf1d8, - fa_send_o = 0xf1d9, - fa_server = 0xf233, - fa_share = 0xf064, - fa_share_alt = 0xf1e0, - fa_share_alt_square = 0xf1e1, - fa_share_square = 0xf14d, - fa_share_square_o = 0xf045, - fa_shekel = 0xf20b, - fa_sheqel = 0xf20b, - fa_shield = 0xf132, - fa_ship = 0xf21a, - fa_shirtsinbulk = 0xf214, - fa_shopping_bag = 0xf290, - fa_shopping_basket = 0xf291, - fa_shopping_cart = 0xf07a, - fa_shower = 0xf2cc, - fa_sign_in = 0xf090, - fa_sign_language = 0xf2a7, - fa_sign_out = 0xf08b, - fa_signal = 0xf012, - fa_signing = 0xf2a7, - fa_simplybuilt = 0xf215, - fa_sitemap = 0xf0e8, - fa_skyatlas = 0xf216, - fa_skype = 0xf17e, - fa_slack = 0xf198, - fa_sliders = 0xf1de, - fa_slideshare = 0xf1e7, - fa_smile_o = 0xf118, - fa_snapchat = 0xf2ab, - fa_snapchat_ghost = 0xf2ac, - fa_snapchat_square = 0xf2ad, - fa_snowflake_o = 0xf2dc, - fa_soccer_ball_o = 0xf1e3, - fa_sort = 0xf0dc, - fa_sort_alpha_asc = 0xf15d, - fa_sort_alpha_desc = 0xf15e, - fa_sort_amount_asc = 0xf160, - fa_sort_amount_desc = 0xf161, - fa_sort_asc = 0xf0de, - fa_sort_desc = 0xf0dd, - fa_sort_down = 0xf0dd, - fa_sort_numeric_asc = 0xf162, - fa_sort_numeric_desc = 0xf163, - fa_sort_up = 0xf0de, - fa_soundcloud = 0xf1be, - fa_space_shuttle = 0xf197, - fa_spinner = 0xf110, - fa_spoon = 0xf1b1, - fa_spotify = 0xf1bc, - fa_square = 0xf0c8, - fa_square_o = 0xf096, - fa_stack_exchange = 0xf18d, - fa_stack_overflow = 0xf16c, - fa_star = 0xf005, - fa_star_half = 0xf089, - fa_star_half_empty = 0xf123, - fa_star_half_full = 0xf123, - fa_star_half_o = 0xf123, - fa_star_o = 0xf006, - fa_steam = 0xf1b6, - fa_steam_square = 0xf1b7, - fa_step_backward = 0xf048, - fa_step_forward = 0xf051, - fa_stethoscope = 0xf0f1, - fa_sticky_note = 0xf249, - fa_sticky_note_o = 0xf24a, - fa_stop = 0xf04d, - fa_stop_circle = 0xf28d, - fa_stop_circle_o = 0xf28e, - fa_street_view = 0xf21d, - fa_strikethrough = 0xf0cc, - fa_stumbleupon = 0xf1a4, - fa_stumbleupon_circle = 0xf1a3, - fa_subscript = 0xf12c, - fa_subway = 0xf239, - fa_suitcase = 0xf0f2, - fa_sun_o = 0xf185, - fa_superpowers = 0xf2dd, - fa_superscript = 0xf12b, - fa_support = 0xf1cd, - fa_table = 0xf0ce, - fa_tablet = 0xf10a, - fa_tachometer = 0xf0e4, - fa_tag = 0xf02b, - fa_tags = 0xf02c, - fa_tasks = 0xf0ae, - fa_taxi = 0xf1ba, - fa_telegram = 0xf2c6, - fa_television = 0xf26c, - fa_tencent_weibo = 0xf1d5, - fa_terminal = 0xf120, - fa_text_height = 0xf034, - fa_text_width = 0xf035, - fa_th = 0xf00a, - fa_th_large = 0xf009, - fa_th_list = 0xf00b, - fa_themeisle = 0xf2b2, - fa_thermometer = 0xf2c7, - fa_thermometer_0 = 0xf2cb, - fa_thermometer_1 = 0xf2ca, - fa_thermometer_2 = 0xf2c9, - fa_thermometer_3 = 0xf2c8, - fa_thermometer_4 = 0xf2c7, - fa_thermometer_empty = 0xf2cb, - fa_thermometer_full = 0xf2c7, - fa_thermometer_half = 0xf2c9, - fa_thermometer_quarter = 0xf2ca, - fa_thermometer_three_quarters = 0xf2c8, - fa_thumb_tack = 0xf08d, - fa_thumbs_down = 0xf165, - fa_thumbs_o_down = 0xf088, - fa_thumbs_o_up = 0xf087, - fa_thumbs_up = 0xf164, - fa_ticket = 0xf145, - fa_times = 0xf00d, - fa_times_circle = 0xf057, - fa_times_circle_o = 0xf05c, - fa_times_rectangle = 0xf2d3, - fa_times_rectangle_o = 0xf2d4, - fa_tint = 0xf043, - fa_toggle_down = 0xf150, - fa_toggle_left = 0xf191, - fa_toggle_off = 0xf204, - fa_toggle_on = 0xf205, - fa_toggle_right = 0xf152, - fa_toggle_up = 0xf151, - fa_trademark = 0xf25c, - fa_train = 0xf238, - fa_transgender = 0xf224, - fa_transgender_alt = 0xf225, - fa_trash = 0xf1f8, - fa_trash_o = 0xf014, - fa_tree = 0xf1bb, - fa_trello = 0xf181, - fa_tripadvisor = 0xf262, - fa_trophy = 0xf091, - fa_truck = 0xf0d1, - fa_try = 0xf195, - fa_tty = 0xf1e4, - fa_tumblr = 0xf173, - fa_tumblr_square = 0xf174, - fa_turkish_lira = 0xf195, - fa_tv = 0xf26c, - fa_twitch = 0xf1e8, - fa_twitter = 0xf099, - fa_twitter_square = 0xf081, - fa_umbrella = 0xf0e9, - fa_underline = 0xf0cd, - fa_undo = 0xf0e2, - fa_universal_access = 0xf29a, - fa_university = 0xf19c, - fa_unlink = 0xf127, - fa_unlock = 0xf09c, - fa_unlock_alt = 0xf13e, - fa_unsorted = 0xf0dc, - fa_upload = 0xf093, - fa_usb = 0xf287, - fa_usd = 0xf155, - fa_user = 0xf007, - fa_user_circle = 0xf2bd, - fa_user_circle_o = 0xf2be, - fa_user_md = 0xf0f0, - fa_user_o = 0xf2c0, - fa_user_plus = 0xf234, - fa_user_secret = 0xf21b, - fa_user_times = 0xf235, - fa_users = 0xf0c0, - fa_vcard = 0xf2bb, - fa_vcard_o = 0xf2bc, - fa_venus = 0xf221, - fa_venus_double = 0xf226, - fa_venus_mars = 0xf228, - fa_viacoin = 0xf237, - fa_viadeo = 0xf2a9, - fa_viadeo_square = 0xf2aa, - fa_video_camera = 0xf03d, - fa_vimeo = 0xf27d, - fa_vimeo_square = 0xf194, - fa_vine = 0xf1ca, - fa_vk = 0xf189, - fa_volume_control_phone = 0xf2a0, - fa_volume_down = 0xf027, - fa_volume_off = 0xf026, - fa_volume_up = 0xf028, - fa_warning = 0xf071, - fa_wechat = 0xf1d7, - fa_weibo = 0xf18a, - fa_weixin = 0xf1d7, - fa_whatsapp = 0xf232, - fa_wheelchair = 0xf193, - fa_wheelchair_alt = 0xf29b, - fa_wifi = 0xf1eb, - fa_wikipedia_w = 0xf266, - fa_window_close = 0xf2d3, - fa_window_close_o = 0xf2d4, - fa_window_maximize = 0xf2d0, - fa_window_minimize = 0xf2d1, - fa_window_restore = 0xf2d2, - fa_windows = 0xf17a, - fa_won = 0xf159, - fa_wordpress = 0xf19a, - fa_wpbeginner = 0xf297, - fa_wpexplorer = 0xf2de, - fa_wpforms = 0xf298, - fa_wrench = 0xf0ad, - fa_xing = 0xf168, - fa_xing_square = 0xf169, - fa_y_combinator = 0xf23b, - fa_y_combinator_square = 0xf1d4, - fa_yahoo = 0xf19e, - fa_yc = 0xf23b, - fa_yc_square = 0xf1d4, - fa_yelp = 0xf1e9, - fa_yen = 0xf157, - fa_yoast = 0xf2b1, - fa_youtube = 0xf167, - fa_youtube_play = 0xf16a, - fa_youtube_square = 0xf166, - - // ruleset icons in circles - fa_osu_osu_o = 0xe000, - fa_osu_mania_o = 0xe001, - fa_osu_fruits_o = 0xe002, - fa_osu_taiko_o = 0xe003, - - // ruleset icons without circles - fa_osu_filled_circle = 0xe004, - fa_osu_cross_o = 0xe005, - fa_osu_logo = 0xe006, - fa_osu_chevron_down_o = 0xe007, - fa_osu_edit_o = 0xe033, - fa_osu_left_o = 0xe034, - fa_osu_right_o = 0xe035, - fa_osu_charts = 0xe036, - fa_osu_solo = 0xe037, - fa_osu_multi = 0xe038, - fa_osu_gear = 0xe039, - - // misc icons - fa_osu_bat = 0xe008, - fa_osu_bubble = 0xe009, - fa_osu_bubble_pop = 0xe02e, - fa_osu_dice = 0xe011, - fa_osu_heart1 = 0xe02f, - fa_osu_heart1_break = 0xe030, - fa_osu_hot = 0xe031, - fa_osu_list_search = 0xe032, - - //osu! playstyles - fa_osu_playstyle_tablet = 0xe02a, - fa_osu_playstyle_mouse = 0xe029, - fa_osu_playstyle_keyboard = 0xe02b, - fa_osu_playstyle_touch = 0xe02c, - - // osu! difficulties - fa_osu_easy_osu = 0xe015, - fa_osu_normal_osu = 0xe016, - fa_osu_hard_osu = 0xe017, - fa_osu_insane_osu = 0xe018, - fa_osu_expert_osu = 0xe019, - - // taiko difficulties - fa_osu_easy_taiko = 0xe01a, - fa_osu_normal_taiko = 0xe01b, - fa_osu_hard_taiko = 0xe01c, - fa_osu_insane_taiko = 0xe01d, - fa_osu_expert_taiko = 0xe01e, - - // fruits difficulties - fa_osu_easy_fruits = 0xe01f, - fa_osu_normal_fruits = 0xe020, - fa_osu_hard_fruits = 0xe021, - fa_osu_insane_fruits = 0xe022, - fa_osu_expert_fruits = 0xe023, - - // mania difficulties - fa_osu_easy_mania = 0xe024, - fa_osu_normal_mania = 0xe025, - fa_osu_hard_mania = 0xe026, - fa_osu_insane_mania = 0xe027, - fa_osu_expert_mania = 0xe028, - - // mod icons - fa_osu_mod_perfect = 0xe049, - fa_osu_mod_autopilot = 0xe03a, - fa_osu_mod_auto = 0xe03b, - fa_osu_mod_cinema = 0xe03c, - fa_osu_mod_doubletime = 0xe03d, - fa_osu_mod_easy = 0xe03e, - fa_osu_mod_flashlight = 0xe03f, - fa_osu_mod_halftime = 0xe040, - fa_osu_mod_hardrock = 0xe041, - fa_osu_mod_hidden = 0xe042, - fa_osu_mod_nightcore = 0xe043, - fa_osu_mod_nofail = 0xe044, - fa_osu_mod_relax = 0xe045, - fa_osu_mod_spunout = 0xe046, - fa_osu_mod_suddendeath = 0xe047, - fa_osu_mod_target = 0xe048, - fa_osu_mod_bg = 0xe04a, - } -} diff --git a/osu.Game/Graphics/Sprites/OsuSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteText.cs index 8607d51e12..ed771bb03f 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteText.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -9,12 +9,10 @@ namespace osu.Game.Graphics.Sprites { public class OsuSpriteText : SpriteText { - public const float FONT_SIZE = 16; - public OsuSpriteText() { Shadow = true; - TextSize = FONT_SIZE; + Font = OsuFont.Default; } } diff --git a/osu.Game/Graphics/UserInterface/BackButton.cs b/osu.Game/Graphics/UserInterface/BackButton.cs index 19135b8550..48bf0848ae 100644 --- a/osu.Game/Graphics/UserInterface/BackButton.cs +++ b/osu.Game/Graphics/UserInterface/BackButton.cs @@ -1,26 +1,65 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface { - public class BackButton : TwoLayerButton + public class BackButton : VisibilityContainer, IKeyBindingHandler { + public Action Action; + + private readonly TwoLayerButton button; + public BackButton() { - Text = @"back"; - Icon = FontAwesome.fa_osu_left_o; - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; + Size = TwoLayerButton.SIZE_EXTENDED; + + Child = button = new TwoLayerButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = @"back", + Icon = OsuIcon.LeftCircle, + Action = () => Action?.Invoke() + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - BackgroundColour = colours.Pink; - HoverColour = colours.PinkDark; + button.BackgroundColour = colours.Pink; + button.HoverColour = colours.PinkDark; + } + + public bool OnPressed(GlobalAction action) + { + if (action == GlobalAction.Back) + { + Action?.Invoke(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + + protected override void PopIn() + { + button.MoveToX(0, 400, Easing.OutQuint); + button.FadeIn(150, Easing.OutQuint); + } + + protected override void PopOut() + { + button.MoveToX(-TwoLayerButton.SIZE_EXTENDED.X / 2, 400, Easing.OutQuint); + button.FadeOut(400, Easing.OutQuint); } } } diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index 89bd6f6118..2a858ccbcf 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -26,10 +26,7 @@ namespace osu.Game.Graphics.UserInterface /// public float Length { - get - { - return length; - } + get => length; set { length = MathHelper.Clamp(value, 0, 1); @@ -39,35 +36,21 @@ namespace osu.Game.Graphics.UserInterface public Color4 BackgroundColour { - get - { - return background.Colour; - } - set - { - background.Colour = value; - } + get => background.Colour; + set => background.Colour = value; } public Color4 AccentColour { - get - { - return bar.Colour; - } - set - { - bar.Colour = value; - } + get => bar.Colour; + set => bar.Colour = value; } private BarDirection direction = BarDirection.LeftToRight; + public BarDirection Direction { - get - { - return direction; - } + get => direction; set { direction = value; diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index f461a0f296..953f3985f9 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -17,16 +17,15 @@ namespace osu.Game.Graphics.UserInterface public float? MaxValue { get; set; } private BarDirection direction = BarDirection.BottomToTop; + public new BarDirection Direction { - get - { - return direction; - } + get => direction; set { direction = value; base.Direction = direction.HasFlag(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; + foreach (var bar in Children) { bar.Size = direction.HasFlag(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); @@ -43,6 +42,7 @@ namespace osu.Game.Graphics.UserInterface set { List bars = Children.ToList(); + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) { float length = MaxValue ?? value.Max(); @@ -69,6 +69,7 @@ namespace osu.Game.Graphics.UserInterface }); } } + //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList()); } diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index d4ef335c3e..d1e55fee24 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -27,12 +28,12 @@ namespace osu.Game.Graphics.UserInterface { Height = 32; TabContainer.Spacing = new Vector2(padding, 0f); - Current.ValueChanged += tab => + Current.ValueChanged += index => { foreach (var t in TabContainer.Children.OfType()) { var tIndex = TabContainer.IndexOf(t); - var tabIndex = TabContainer.IndexOf(TabMap[tab]); + var tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]); t.State = tIndex < tabIndex ? Visibility.Hidden : Visibility.Visible; t.Chevron.FadeTo(tIndex <= tabIndex ? 0f : 1f, 500, Easing.OutQuint); @@ -57,10 +58,11 @@ namespace osu.Game.Graphics.UserInterface public Visibility State { - get { return state; } + get => state; set { if (value == state) return; + state = value; const float transition_duration = 500; @@ -80,9 +82,14 @@ namespace osu.Game.Graphics.UserInterface } } - public BreadcrumbTabItem(T value) : base(value) + public override void Hide() => State = Visibility.Hidden; + + public override void Show() => State = Visibility.Visible; + + public BreadcrumbTabItem(T value) + : base(value) { - Text.TextSize = 18; + Text.Font = Text.Font.With(size: 18); Text.Margin = new MarginPadding { Vertical = 8 }; Padding = new MarginPadding { Right = padding + item_chevron_size }; Add(Chevron = new SpriteIcon @@ -90,7 +97,7 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.CentreRight, Origin = Anchor.CentreLeft, Size = new Vector2(item_chevron_size), - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, Margin = new MarginPadding { Left = padding }, Alpha = 0f, }); diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 24bac05384..b50bf14bab 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -11,8 +12,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics.Containers; -using osu.Framework.Configuration; using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface @@ -141,8 +142,7 @@ namespace osu.Game.Graphics.UserInterface Text = Text, Anchor = Anchor.Centre, Origin = Anchor.Centre, - TextSize = 28, - Font = "Exo2.0-Bold", + Font = OsuFont.GetFont(size: 28, weight: FontWeight.Bold), Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.1f), Colour = Color4.White, @@ -155,12 +155,10 @@ namespace osu.Game.Graphics.UserInterface } private Color4 buttonColour; + public Color4 ButtonColour { - get - { - return buttonColour; - } + get => buttonColour; set { buttonColour = value; @@ -170,12 +168,10 @@ namespace osu.Game.Graphics.UserInterface } private Color4 backgroundColour = OsuColour.Gray(34); + public Color4 BackgroundColour { - get - { - return backgroundColour; - } + get => backgroundColour; set { backgroundColour = value; @@ -184,12 +180,10 @@ namespace osu.Game.Graphics.UserInterface } private string text; + public string Text { - get - { - return text; - } + get => text; set { text = value; @@ -197,18 +191,10 @@ namespace osu.Game.Graphics.UserInterface } } - private float textSize = 28; public float TextSize { - get - { - return textSize; - } - set - { - textSize = value; - spriteText.TextSize = value; - } + get => spriteText.Font.Size; + set => spriteText.Font = spriteText.Font.With(size: value); } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => backgroundContainer.ReceivePositionalInputAt(screenSpacePos); @@ -242,9 +228,9 @@ namespace osu.Game.Graphics.UserInterface Selected.Value = false; } - private void selectionChanged(bool isSelected) + private void selectionChanged(ValueChangedEvent args) { - if (isSelected) + if (args.NewValue) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); colourContainer.ResizeTo(new Vector2(hover_width, 1f), hover_duration, Easing.OutElastic); diff --git a/osu.Game/Graphics/UserInterface/ExpandingBar.cs b/osu.Game/Graphics/UserInterface/ExpandingBar.cs new file mode 100644 index 0000000000..439a6002d8 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ExpandingBar.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// A rounded bar which can be expanded or collapsed. + /// Generally used for tabs or breadcrumbs. + /// + public class ExpandingBar : Circle + { + private bool isCollapsed; + + public bool IsCollapsed + { + get => isCollapsed; + set + { + if (value == isCollapsed) + return; + + isCollapsed = value; + updateState(); + } + } + + private float expandedSize = 4; + + public float ExpandedSize + { + get => expandedSize; + set + { + if (value == expandedSize) + return; + + expandedSize = value; + updateState(); + } + } + + private float collapsedSize = 2; + + public float CollapsedSize + { + get => collapsedSize; + set + { + if (value == collapsedSize) + return; + + collapsedSize = value; + updateState(); + } + } + + public override Axes RelativeSizeAxes + { + get => base.RelativeSizeAxes; + set + { + base.RelativeSizeAxes = Axes.None; + Size = Vector2.Zero; + + base.RelativeSizeAxes = value; + updateState(); + } + } + + public ExpandingBar() + { + RelativeSizeAxes = Axes.X; + Origin = Anchor.Centre; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + + public void Collapse() => IsCollapsed = true; + + public void Expand() => IsCollapsed = false; + + private void updateState() + { + float newSize = IsCollapsed ? CollapsedSize : ExpandedSize; + Easing easingType = IsCollapsed ? Easing.Out : Easing.OutElastic; + + if (RelativeSizeAxes == Axes.X) + this.ResizeHeightTo(newSize, 400, easingType); + else + this.ResizeWidthTo(newSize, 400, easingType); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 8f8ba1ede8..8c00cae08a 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Platform; using osuTK; @@ -25,7 +26,7 @@ namespace osu.Game.Graphics.UserInterface Size = new Vector2(12); InternalChild = new SpriteIcon { - Icon = FontAwesome.fa_external_link, + Icon = FontAwesome.Solid.ExternalLinkAlt, RelativeSizeAxes = Axes.Both }; } @@ -51,7 +52,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnClick(ClickEvent e) { - if(Link != null) + if (Link != null) host.OpenUrlExternally(Link); return true; } diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index 0564d364ed..f873db0dcb 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -1,9 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using System; +using osu.Framework.Allocation; using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Game.Input.Bindings; using osuTK.Input; @@ -14,16 +16,20 @@ namespace osu.Game.Graphics.UserInterface /// public class FocusedTextBox : OsuTextBox { - protected override Color4 BackgroundUnfocused => new Color4(10, 10, 10, 255); - protected override Color4 BackgroundFocused => new Color4(10, 10, 10, 255); - public Action Exit; private bool focus; + private bool allowImmediateFocus => host?.OnScreenKeyboardOverlapsGameWindow != true; + + public void TakeFocus() + { + if (allowImmediateFocus) GetContainingInputManager().ChangeFocus(this); + } + public bool HoldFocus { - get { return focus; } + get => allowImmediateFocus && focus; set { focus = value; @@ -32,6 +38,17 @@ namespace osu.Game.Graphics.UserInterface } } + private GameHost host; + + [BackgroundDependencyLoader] + private void load(GameHost host) + { + this.host = host; + + BackgroundUnfocused = new Color4(10, 10, 10, 255); + BackgroundFocused = new Color4(10, 10, 10, 255); + } + // We may not be focused yet, but we need to handle keyboard input to be able to request focus public override bool HandleNonPositionalInput => HoldFocus || base.HandleNonPositionalInput; diff --git a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs index 3641e251bc..70d988f60e 100644 --- a/osu.Game/Graphics/UserInterface/HoverClickSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverClickSounds.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; @@ -17,7 +17,8 @@ namespace osu.Game.Graphics.UserInterface { private SampleChannel sampleClick; - public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) : base(sampleSet) + public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) + : base(sampleSet) { } @@ -30,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleClick = audio.Sample.Get($@"UI/generic-select{SampleSet.GetDescription()}"); + sampleClick = audio.Samples.Get($@"UI/generic-select{SampleSet.GetDescription()}"); } } } diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 710948121e..f1ac8ced6e 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; using osu.Framework.Allocation; @@ -37,7 +37,7 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleHover = audio.Sample.Get($@"UI/generic-hover{SampleSet.GetDescription()}"); + sampleHover = audio.Samples.Get($@"UI/generic-hover{SampleSet.GetDescription()}"); } } @@ -45,8 +45,10 @@ namespace osu.Game.Graphics.UserInterface { [Description("")] Loud, + [Description("-soft")] Normal, + [Description("-softer")] Soft } diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index c59326a256..27427581fd 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -1,16 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; namespace osu.Game.Graphics.UserInterface { public class IconButton : OsuAnimatedButton { - public const float BUTTON_SIZE = 30; + public const float DEFAULT_BUTTON_SIZE = 30; private Color4? iconColour; @@ -19,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface /// public Color4 IconColour { - get { return iconColour ?? Color4.White; } + get => iconColour ?? Color4.White; set { iconColour = value; @@ -34,17 +35,17 @@ namespace osu.Game.Graphics.UserInterface /// public Color4 IconHoverColour { - get { return iconHoverColour ?? IconColour; } - set { iconHoverColour = value; } + get => iconHoverColour ?? IconColour; + set => iconHoverColour = value; } /// /// The icon. /// - public FontAwesome Icon + public IconUsage Icon { - get { return icon.Icon; } - set { icon.Icon = value; } + get => icon.Icon; + set => icon.Icon = value; } /// @@ -52,29 +53,15 @@ namespace osu.Game.Graphics.UserInterface /// public Vector2 IconScale { - get { return icon.Scale; } - set { icon.Scale = value; } - } - - /// - /// The size of the while it is not being pressed. - /// - public Vector2 ButtonSize - { - get => Content.Size; - set - { - Content.RelativeSizeAxes = Axes.None; - Content.Size = value; - } + get => icon.Scale; + set => icon.Scale = value; } private readonly SpriteIcon icon; public IconButton() { - AutoSizeAxes = Axes.Both; - ButtonSize = new Vector2(BUTTON_SIZE); + Size = new Vector2(DEFAULT_BUTTON_SIZE); Add(icon = new SpriteIcon { diff --git a/osu.Game/Graphics/UserInterface/LineGraph.cs b/osu.Game/Graphics/UserInterface/LineGraph.cs index c84c500201..757a9a349c 100644 --- a/osu.Game/Graphics/UserInterface/LineGraph.cs +++ b/osu.Game/Graphics/UserInterface/LineGraph.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -9,6 +9,7 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; +using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { @@ -44,7 +45,7 @@ namespace osu.Game.Graphics.UserInterface /// public IEnumerable Values { - get { return values; } + get => values; set { values = value.ToArray(); @@ -63,13 +64,19 @@ namespace osu.Game.Graphics.UserInterface } } + public Color4 LineColour + { + get => maskingContainer.Colour; + set => maskingContainer.Colour = value; + } + public LineGraph() { Add(maskingContainer = new Container { Masking = true, RelativeSizeAxes = Axes.Both, - Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathWidth = 1 } + Child = path = new SmoothPath { RelativeSizeAxes = Axes.Both, PathRadius = 1 } }); } @@ -86,6 +93,7 @@ namespace osu.Game.Graphics.UserInterface protected override void Update() { base.Update(); + if (!pathCached.IsValid) { applyPath(); @@ -102,9 +110,10 @@ namespace osu.Game.Graphics.UserInterface for (int i = 0; i < values.Length; i++) { - float x = (i + count - values.Length) / (float)(count - 1) * DrawWidth - 1; - float y = GetYPosition(values[i]) * DrawHeight - 1; - // the -1 is for inner offset in path (actually -PathWidth) + // Make sure that we are accounting for path width when calculating vertex positions + // We need to apply 2x the path radius to account for it because the full diameter of the line accounts into height + float x = (i + count - values.Length) / (float)(count - 1) * (DrawWidth - 2 * path.PathRadius); + float y = GetYPosition(values[i]) * (DrawHeight - 2 * path.PathRadius); path.AddVertex(new Vector2(x, y)); } } @@ -112,6 +121,7 @@ namespace osu.Game.Graphics.UserInterface protected float GetYPosition(float value) { if (ActualMaxValue == ActualMinValue) return 0; + return (ActualMaxValue - value) / (ActualMaxValue - ActualMinValue); } } diff --git a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs index e503436d47..5a8a0da135 100644 --- a/osu.Game/Graphics/UserInterface/LoadingAnimation.cs +++ b/osu.Game/Graphics/UserInterface/LoadingAnimation.cs @@ -1,13 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { + /// + /// A loading spinner. + /// public class LoadingAnimation : VisibilityContainer { private readonly SpriteIcon spinner; @@ -33,14 +37,14 @@ namespace osu.Game.Graphics.UserInterface Position = new Vector2(1, 1), Colour = Color4.Black, Alpha = 0.4f, - Icon = FontAwesome.fa_circle_o_notch + Icon = FontAwesome.Solid.CircleNotch }, spinner = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_circle_o_notch + Icon = FontAwesome.Solid.CircleNotch } }; } diff --git a/osu.Game/Graphics/UserInterface/MenuItemType.cs b/osu.Game/Graphics/UserInterface/MenuItemType.cs index 42bade1439..0269f2cb57 100644 --- a/osu.Game/Graphics/UserInterface/MenuItemType.cs +++ b/osu.Game/Graphics/UserInterface/MenuItemType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 974708af97..82b09e0821 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -1,12 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; @@ -40,9 +42,9 @@ namespace osu.Game.Graphics.UserInterface }, }; - Current.ValueChanged += newValue => + Current.ValueChanged += filled => { - if (newValue) + if (filled.NewValue) fill.FadeIn(200, Easing.OutQuint); else fill.FadeTo(0.01f, 200, Easing.OutQuint); //todo: remove once we figure why containers aren't drawing at all times @@ -71,9 +73,10 @@ namespace osu.Game.Graphics.UserInterface } private bool glowing; + public bool Glowing { - get { return glowing; } + get => glowing; set { glowing = value; @@ -93,18 +96,29 @@ namespace osu.Game.Graphics.UserInterface public bool Expanded { + set => this.ResizeTo(new Vector2(value ? EXPANDED_SIZE : COLLAPSED_SIZE, 12), 500, Easing.OutQuint); + } + + private readonly Bindable current = new Bindable(); + + public Bindable Current + { + get => current; set { - this.ResizeTo(new Vector2(value ? EXPANDED_SIZE : COLLAPSED_SIZE, 12), 500, Easing.OutQuint); + if (value == null) + throw new ArgumentNullException(nameof(value)); + + current.UnbindBindings(); + current.BindTo(value); } } - public Bindable Current { get; } = new Bindable(); - private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; @@ -114,9 +128,10 @@ namespace osu.Game.Graphics.UserInterface } private Color4 glowingAccentColour; + public Color4 GlowingAccentColour { - get { return glowingAccentColour; } + get => glowingAccentColour; set { glowingAccentColour = value; @@ -126,9 +141,10 @@ namespace osu.Game.Graphics.UserInterface } private Color4 glowColour; + public Color4 GlowColour { - get { return glowColour; } + get => glowColour; set { glowColour = value; diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 89760a9873..1a8fea4ff9 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -18,14 +19,14 @@ namespace osu.Game.Graphics.UserInterface public class OsuAnimatedButton : OsuClickableContainer { /// - /// The colour that should be flashed when the is clicked. + /// The colour that should be flashed when the is clicked. /// protected Color4 FlashColour = Color4.White.Opacity(0.3f); private Color4 hoverColour = Color4.White.Opacity(0.1f); /// - /// The background colour of the while it is hovered. + /// The background colour of the while it is hovered. /// protected Color4 HoverColour { @@ -73,7 +74,13 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colours) { - Enabled.BindValueChanged(enabled => this.FadeColour(enabled ? Color4.White : colours.Gray9, 200, Easing.OutQuint), true); + if (AutoSizeAxes != Axes.None) + { + content.RelativeSizeAxes = (Axes.Both & ~AutoSizeAxes); + content.AutoSizeAxes = AutoSizeAxes; + } + + Enabled.BindValueChanged(enabled => this.FadeColour(enabled.NewValue ? Color4.White : colours.Gray9, 200, Easing.OutQuint), true); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 0ff4542678..7a27f825f6 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -46,13 +47,13 @@ namespace osu.Game.Graphics.UserInterface new HoverClickSounds(HoverSampleSet.Loud), }); - Enabled.ValueChanged += enabled_ValueChanged; + Enabled.ValueChanged += enabledChanged; Enabled.TriggerChange(); } - private void enabled_ValueChanged(bool enabled) + private void enabledChanged(ValueChangedEvent e) { - this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) @@ -84,7 +85,7 @@ namespace osu.Game.Graphics.UserInterface Depth = -1, Origin = Anchor.Centre, Anchor = Anchor.Centre, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold) }; } } diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index 194e81dae1..47324ee646 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -1,59 +1,45 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { public class OsuCheckbox : Checkbox { - private Bindable bindable; - - public Bindable Bindable - { - set - { - bindable = value; - Current.BindTo(bindable); - } - } - public Color4 CheckedColor { get; set; } = Color4.Cyan; public Color4 UncheckedColor { get; set; } = Color4.White; public int FadeDuration { get; set; } public string LabelText { - get { return labelSpriteText?.Text; } set { - if (labelSpriteText != null) - labelSpriteText.Text = value; + if (labelText != null) + labelText.Text = value; } } public MarginPadding LabelPadding { - get { return labelSpriteText?.Padding ?? new MarginPadding(); } + get => labelText?.Padding ?? new MarginPadding(); set { - if (labelSpriteText != null) - labelSpriteText.Padding = value; + if (labelText != null) + labelText.Padding = value; } } protected readonly Nub Nub; - private readonly SpriteText labelSpriteText; + private readonly OsuTextFlowContainer labelText; private SampleChannel sampleChecked; private SampleChannel sampleUnchecked; @@ -62,33 +48,37 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; + const float nub_padding = 5; + Children = new Drawable[] { - labelSpriteText = new OsuSpriteText(), + labelText = new OsuTextFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding } + }, Nub = new Nub { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 5 }, + Margin = new MarginPadding { Right = nub_padding }, }, new HoverClickSounds() }; Nub.Current.BindTo(Current); - Current.DisabledChanged += disabled => - { - Alpha = disabled ? 0.3f : 1; - }; + Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1; } protected override void LoadComplete() { base.LoadComplete(); - Current.ValueChanged += newValue => + Current.ValueChanged += enabled => { - if (newValue) + if (enabled.NewValue) sampleChecked?.Play(); else sampleUnchecked?.Play(); @@ -112,8 +102,8 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleChecked = audio.Sample.Get(@"UI/check-on"); - sampleUnchecked = audio.Sample.Get(@"UI/check-off"); + sampleChecked = audio.Samples.Get(@"UI/check-on"); + sampleUnchecked = audio.Samples.Get(@"UI/check-off"); } } } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 2dd6d4fe78..cea8427296 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 3f15616d6c..fc3a7229fa 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osuTK.Graphics; @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; @@ -17,9 +18,10 @@ namespace osu.Game.Graphics.UserInterface public class OsuDropdown : Dropdown, IHasAccentColour { private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; @@ -37,11 +39,9 @@ namespace osu.Game.Graphics.UserInterface private void updateAccentColour() { - var header = Header as IHasAccentColour; - if (header != null) header.AccentColour = accentColour; + if (Header is IHasAccentColour header) header.AccentColour = accentColour; - var menu = Menu as IHasAccentColour; - if (menu != null) menu.AccentColour = accentColour; + if (Menu is IHasAccentColour menu) menu.AccentColour = accentColour; } protected override DropdownHeader CreateHeader() => new OsuDropdownHeader(); @@ -49,6 +49,7 @@ namespace osu.Game.Graphics.UserInterface protected override DropdownMenu CreateMenu() => new OsuDropdownMenu(); #region OsuDropdownMenu + protected class OsuDropdownMenu : DropdownMenu, IHasAccentColour { public override bool HandleNonPositionalInput => State == MenuState.Open; @@ -83,9 +84,10 @@ namespace osu.Game.Graphics.UserInterface } private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; @@ -94,18 +96,24 @@ namespace osu.Game.Graphics.UserInterface } } - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour }; + protected override Menu CreateSubMenu() => new OsuMenu(Direction.Vertical); + + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour }; + + protected override ScrollContainer CreateScrollContainer(Direction direction) => new OsuScrollContainer(direction); #region DrawableOsuDropdownMenuItem + public class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour { // IsHovered is used public override bool HandlePositionalInput => true; private Color4? accentColour; + public Color4 AccentColour { - get { return accentColour ?? nonAccentSelectedColour; } + get => accentColour ?? nonAccentSelectedColour; set { accentColour = value; @@ -149,8 +157,7 @@ namespace osu.Game.Graphics.UserInterface { base.UpdateForegroundColour(); - var content = Foreground.Children.FirstOrDefault() as Content; - if (content != null) content.Chevron.Alpha = IsHovered ? 1 : 0; + if (Foreground.Children.FirstOrDefault() is Content content) content.Chevron.Alpha = IsHovered ? 1 : 0; } protected override Drawable CreateContent() => new Content(); @@ -159,8 +166,8 @@ namespace osu.Game.Graphics.UserInterface { public string Text { - get { return Label.Text; } - set { Label.Text = value; } + get => Label.Text; + set => Label.Text = value; } public readonly OsuSpriteText Label; @@ -177,7 +184,7 @@ namespace osu.Game.Graphics.UserInterface Chevron = new SpriteIcon { AlwaysPresent = true, - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, Colour = Color4.Black, Alpha = 0.5f, Size = new Vector2(8), @@ -194,25 +201,29 @@ namespace osu.Game.Graphics.UserInterface } } } + #endregion } + #endregion public class OsuDropdownHeader : DropdownHeader, IHasAccentColour { protected readonly SpriteText Text; + protected override string Label { - get { return Text.Text; } - set { Text.Text = value; } + get => Text.Text; + set => Text.Text = value; } protected readonly SpriteIcon Icon; private Color4 accentColour; + public virtual Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; @@ -238,11 +249,11 @@ namespace osu.Game.Graphics.UserInterface }, Icon = new SpriteIcon { - Icon = FontAwesome.fa_chevron_down, + Icon = FontAwesome.Solid.ChevronDown, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 4 }, - Size = new Vector2(20), + Margin = new MarginPadding { Right = 5 }, + Size = new Vector2(12), }, }; diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs index 5c6a2a0569..e132027787 100644 --- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 570ed368d8..c4c6950eb1 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; @@ -46,6 +47,8 @@ namespace osu.Game.Graphics.UserInterface protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item); + protected override ScrollContainer CreateScrollContainer(Direction direction) => new OsuScrollContainer(direction); + protected override Menu CreateSubMenu() => new OsuMenu(Direction.Vertical) { Anchor = Direction == Direction.Horizontal ? Anchor.BottomLeft : Anchor.TopRight @@ -71,8 +74,8 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleHover = audio.Sample.Get(@"UI/generic-hover"); - sampleClick = audio.Sample.Get(@"UI/generic-select"); + sampleHover = audio.Samples.Get(@"UI/generic-hover"); + sampleClick = audio.Samples.Get(@"UI/generic-select"); BackgroundColour = Color4.Transparent; BackgroundColourHover = OsuColour.FromHex(@"172023"); @@ -88,9 +91,11 @@ namespace osu.Game.Graphics.UserInterface case MenuItemType.Standard: text.Colour = Color4.White; break; + case MenuItemType.Destructive: text.Colour = Color4.Red; break; + case MenuItemType.Highlighted: text.Colour = OsuColour.FromHex(@"ffcc22"); break; @@ -125,7 +130,7 @@ namespace osu.Game.Graphics.UserInterface { public string Text { - get { return NormalText.Text; } + get => NormalText.Text; set { NormalText.Text = value; @@ -149,7 +154,7 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextSize = text_size, + Font = OsuFont.GetFont(size: text_size), Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, }, BoldText = new OsuSpriteText @@ -158,8 +163,7 @@ namespace osu.Game.Graphics.UserInterface Alpha = 0, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextSize = text_size, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL }, } }; diff --git a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs index 02452523a8..b7aa666302 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenuItem.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics.UserInterface; diff --git a/osu.Game/Graphics/UserInterface/OsuNumberBox.cs b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs new file mode 100644 index 0000000000..36288c745a --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuNumberBox.cs @@ -0,0 +1,10 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuNumberBox : OsuTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index c8a1bf9178..418ad038f7 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -9,12 +9,14 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Platform; namespace osu.Game.Graphics.UserInterface { - public class OsuPasswordTextBox : OsuTextBox + public class OsuPasswordTextBox : OsuTextBox, ISuppressKeyEventLogging { protected override Drawable GetDrawableCharacter(char c) => new PasswordMaskChar(CalculatedTextSize); @@ -106,7 +108,7 @@ namespace osu.Game.Graphics.UserInterface public CapsWarning() { - Icon = FontAwesome.fa_warning; + Icon = FontAwesome.Solid.ExclamationTriangle; } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index a59abcbcee..5c706781e6 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Globalization; @@ -8,7 +8,6 @@ using osuTK.Graphics; 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.UserInterface; using osu.Framework.Graphics.Cursor; @@ -33,43 +32,13 @@ namespace osu.Game.Graphics.UserInterface private readonly Box leftBox; private readonly Box rightBox; - public virtual string TooltipText - { - get - { - var bindableDouble = CurrentNumber as BindableNumber; - var bindableFloat = CurrentNumber as BindableNumber; - var floatValue = bindableDouble?.Value ?? bindableFloat?.Value; - var floatPrecision = bindableDouble?.Precision ?? bindableFloat?.Precision; - - if (floatValue != null) - { - var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue; - var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue; - - if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1)) - return floatValue.Value.ToString("P0"); - - var decimalPrecision = normalise((decimal)floatPrecision, max_decimal_digits); - - // Find the number of significant digits (we could have less than 5 after normalize()) - var significantDigits = findPrecision(decimalPrecision); - - return floatValue.Value.ToString($"N{significantDigits}"); - } - - var bindableInt = CurrentNumber as BindableNumber; - if (bindableInt != null) - return bindableInt.Value.ToString("N0"); - - return Current.Value.ToString(CultureInfo.InvariantCulture); - } - } + public virtual string TooltipText { get; private set; } private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; @@ -111,19 +80,22 @@ namespace osu.Game.Graphics.UserInterface new HoverClickSounds() }; - Current.DisabledChanged += disabled => - { - Alpha = disabled ? 0.3f : 1; - }; + Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; }; } [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { - sample = audio.Sample.Get(@"UI/sliderbar-notch"); + sample = audio.Samples.Get(@"UI/sliderbar-notch"); AccentColour = colours.Pink; } + protected override void LoadComplete() + { + base.LoadComplete(); + CurrentNumber.BindValueChanged(current => updateTooltipText(current.NewValue), true); + } + protected override bool OnHover(HoverEvent e) { Nub.Glowing = true; @@ -136,21 +108,34 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } - protected override void OnUserChange() + protected override bool OnMouseDown(MouseDownEvent e) { - base.OnUserChange(); - playSample(); + Nub.Current.Value = true; + return base.OnMouseDown(e); } - private void playSample() + protected override bool OnMouseUp(MouseUpEvent e) + { + Nub.Current.Value = false; + return base.OnMouseUp(e); + } + + protected override void OnUserChange(T value) + { + base.OnUserChange(value); + playSample(value); + updateTooltipText(value); + } + + private void playSample(T value) { if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) return; - if (Current.Value.Equals(lastSampleValue)) + if (value.Equals(lastSampleValue)) return; - lastSampleValue = Current.Value; + lastSampleValue = value; lastSampleTime = Clock.CurrentTime; sample.Frequency.Value = 1 + NormalizedValue * 0.2f; @@ -163,16 +148,28 @@ namespace osu.Game.Graphics.UserInterface sample.Play(); } - protected override bool OnMouseDown(MouseDownEvent e) + private void updateTooltipText(T value) { - Nub.Current.Value = true; - return base.OnMouseDown(e); - } + if (CurrentNumber.IsInteger) + TooltipText = ((int)Convert.ChangeType(value, typeof(int))).ToString("N0"); + else + { + double floatValue = (double)Convert.ChangeType(value, typeof(double)); + double floatMinValue = (double)Convert.ChangeType(CurrentNumber.MinValue, typeof(double)); + double floatMaxValue = (double)Convert.ChangeType(CurrentNumber.MaxValue, typeof(double)); - protected override bool OnMouseUp(MouseUpEvent e) - { - Nub.Current.Value = false; - return base.OnMouseUp(e); + if (floatMaxValue == 1 && floatMinValue >= -1) + TooltipText = floatValue.ToString("P0"); + else + { + var decimalPrecision = normalise((decimal)Convert.ChangeType(CurrentNumber.Precision, typeof(decimal)), max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + var significantDigits = findPrecision(decimalPrecision); + + TooltipText = floatValue.ToString($"N{significantDigits}"); + } + } } protected override void UpdateAfterChildren() @@ -206,6 +203,7 @@ namespace osu.Game.Graphics.UserInterface private int findPrecision(decimal d) { int precision = 0; + while (d != Math.Round(d)) { d *= 10; diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index 488e16b6fb..11f41b1a48 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -58,14 +58,14 @@ namespace osu.Game.Graphics.UserInterface } private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; - var dropdown = Dropdown as IHasAccentColour; - if (dropdown != null) + if (Dropdown is IHasAccentColour dropdown) dropdown.AccentColour = value; foreach (var i in TabContainer.Children.OfType()) i.AccentColour = value; @@ -101,13 +101,14 @@ namespace osu.Game.Graphics.UserInterface protected readonly Box Bar; private Color4 accentColour; + public Color4 AccentColour { - get { return accentColour; } + get => accentColour; set { accentColour = value; - if (!Active) + if (!Active.Value) Text.Colour = value; } } @@ -128,14 +129,14 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { - if (!Active) + if (!Active.Value) fadeActive(); return true; } protected override void OnHoverLost(HoverLostEvent e) { - if (!Active) + if (!Active.Value) fadeInactive(); } @@ -146,7 +147,8 @@ namespace osu.Game.Graphics.UserInterface AccentColour = colours.Blue; } - public OsuTabItem(T value) : base(value) + public OsuTabItem(T value) + : base(value) { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; @@ -159,8 +161,7 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Text = (value as IHasDescription)?.Description ?? (value as Enum)?.GetDescription() ?? value.ToString(), - TextSize = 14, - Font = @"Exo2.0-Bold", // Font should only turn bold when active? + Font = OsuFont.GetFont(size: 14) }, Bar = new Box { @@ -173,6 +174,8 @@ namespace osu.Game.Graphics.UserInterface }, new HoverClickSounds() }; + + Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } protected override void OnActivated() => fadeActive(); @@ -207,7 +210,7 @@ namespace osu.Game.Graphics.UserInterface MaxHeight = 400; } - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item) { AccentColour = AccentColour }; + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item) { AccentColour = AccentColour }; private class DrawableOsuTabDropdownMenuItem : DrawableOsuDropdownMenuItem { @@ -223,11 +226,7 @@ namespace osu.Game.Graphics.UserInterface { public override Color4 AccentColour { - get - { - return base.AccentColour; - } - + get => base.AccentColour; set { base.AccentColour = value; @@ -255,7 +254,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.fa_ellipsis_h, + Icon = FontAwesome.Solid.EllipsisH, Size = new Vector2(14), Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index cb951898da..869005d05c 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -24,14 +24,15 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteIcon icon; private Color4? accentColour; + public Color4 AccentColour { - get { return accentColour.GetValueOrDefault(); } + get => accentColour.GetValueOrDefault(); set { accentColour = value; - if (Current) + if (Current.Value) { text.Colour = AccentColour; icon.Colour = AccentColour; @@ -41,8 +42,8 @@ namespace osu.Game.Graphics.UserInterface public string Text { - get { return text.Text; } - set { text.Text = value; } + get => text.Text; + set => text.Text = value; } private const float transition_length = 500; @@ -67,7 +68,7 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(HoverLostEvent e) { - if (!Current) + if (!Current.Value) fadeOut(); base.OnHoverLost(e); @@ -94,15 +95,11 @@ namespace osu.Game.Graphics.UserInterface Direction = FillDirection.Horizontal, Children = new Drawable[] { - text = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, + text = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold) }, icon = new SpriteIcon { Size = new Vector2(14), - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.Regular.Circle, Shadow = true, }, }, @@ -118,17 +115,17 @@ namespace osu.Game.Graphics.UserInterface } }; - Current.ValueChanged += v => + Current.ValueChanged += selected => { - if (v) + if (selected.NewValue) { fadeIn(); - icon.Icon = FontAwesome.fa_check_circle_o; + icon.Icon = FontAwesome.Regular.CheckCircle; } else { fadeOut(); - icon.Icon = FontAwesome.fa_circle_o; + icon.Icon = FontAwesome.Regular.Circle; } }; } diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 523b60fdd2..89de91bc9b 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -16,15 +16,11 @@ namespace osu.Game.Graphics.UserInterface { public class OsuTextBox : TextBox, IKeyBindingHandler { - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.5f); - protected override Color4 BackgroundFocused => OsuColour.Gray(0.3f).Opacity(0.8f); - protected override Color4 BackgroundCommit => BorderColour; - protected override float LeftRightPadding => 10; protected override SpriteText CreatePlaceholder() => new OsuSpriteText { - Font = @"Exo2.0-MediumItalic", + Font = OsuFont.GetFont(italics: true), Colour = new Color4(180, 180, 180, 255), Margin = new MarginPadding { Left = 2 }, }; @@ -34,6 +30,7 @@ namespace osu.Game.Graphics.UserInterface Height = 40; TextContainer.Height = 0.5f; CornerRadius = 5; + LengthLimit = 1000; Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; }; } @@ -41,7 +38,9 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load(OsuColour colour) { - BorderColour = colour.Yellow; + BackgroundUnfocused = Color4.Black.Opacity(0.5f); + BackgroundFocused = OsuColour.Gray(0.3f).Opacity(0.8f); + BackgroundCommit = BorderColour = colour.Yellow; } protected override void OnFocus(FocusEvent e) @@ -57,7 +56,7 @@ namespace osu.Game.Graphics.UserInterface base.OnFocusLost(e); } - protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), TextSize = CalculatedTextSize }; + protected override Drawable GetDrawableCharacter(char c) => new OsuSpriteText { Text = c.ToString(), Font = OsuFont.GetFont(size: CalculatedTextSize) }; public virtual bool OnPressed(GlobalAction action) { diff --git a/osu.Game/Graphics/UserInterface/PageTabControl.cs b/osu.Game/Graphics/UserInterface/PageTabControl.cs index 50e4743028..156a556b5e 100644 --- a/osu.Game/Graphics/UserInterface/PageTabControl.cs +++ b/osu.Game/Graphics/UserInterface/PageTabControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -32,7 +32,8 @@ namespace osu.Game.Graphics.UserInterface protected readonly SpriteText Text; - public PageTabItem(T value) : base(value) + public PageTabItem(T value) + : base(value) { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; @@ -45,8 +46,7 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Text = (value as Enum)?.GetDescription() ?? value.ToString(), - TextSize = 14, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 14) }, box = new Box { @@ -59,6 +59,8 @@ namespace osu.Game.Graphics.UserInterface }, new HoverClickSounds() }; + + Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true); } [BackgroundDependencyLoader] @@ -69,14 +71,14 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { - if (!Active) + if (!Active.Value) slideActive(); return true; } protected override void OnHoverLost(HoverLostEvent e) { - if (!Active) + if (!Active.Value) slideInactive(); } diff --git a/osu.Game/Graphics/UserInterface/PercentageCounter.cs b/osu.Game/Graphics/UserInterface/PercentageCounter.cs index b1514cb8c8..8254bdda7c 100644 --- a/osu.Game/Graphics/UserInterface/PercentageCounter.cs +++ b/osu.Game/Graphics/UserInterface/PercentageCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface public PercentageCounter() { - DisplayedCountSpriteText.FixedWidth = true; + DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true); Current.Value = DisplayedCount = 1.0f; } @@ -41,7 +41,7 @@ namespace osu.Game.Graphics.UserInterface public override void Increment(double amount) { - Current.Value = Current + amount; + Current.Value = Current.Value + amount; } } } diff --git a/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs b/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs new file mode 100644 index 0000000000..d75e78e2d9 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ProcessingOverlay.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + /// + /// An overlay that will consume all available space and block input when required. + /// Useful for disabling all elements in a form and showing we are waiting on a response, for instance. + /// + public class ProcessingOverlay : VisibilityContainer + { + private const float transition_duration = 200; + + public ProcessingOverlay() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.9f, + }, + new LoadingAnimation { State = { Value = Visibility.Visible } } + }; + } + + protected override bool Handle(UIEvent e) + { + return true; + } + + protected override void PopIn() + { + this.FadeIn(transition_duration * 2, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(transition_duration, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index ee64c7c25c..d271cd121c 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.UserInterface public Color4 FillColour { - set { fill.FadeColour(value, 150, Easing.OutQuint); } + set => fill.FadeColour(value, 150, Easing.OutQuint); } public Color4 BackgroundColour @@ -32,12 +32,12 @@ namespace osu.Game.Graphics.UserInterface public double EndTime { - set { CurrentNumber.MaxValue = value; } + set => CurrentNumber.MaxValue = value; } public double CurrentTime { - set { CurrentNumber.Value = value; } + set => CurrentNumber.Value = value; } public ProgressBar() @@ -62,6 +62,6 @@ namespace osu.Game.Graphics.UserInterface fill.Width = value * UsableWidth; } - protected override void OnUserChange() => OnSeek?.Invoke(Current); + protected override void OnUserChange(double value) => OnSeek?.Invoke(value); } } diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 5a9dd4e05e..cd244ed7e6 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -1,13 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites; using System; using System.Collections.Generic; +using osu.Framework.Bindables; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -45,15 +45,13 @@ namespace osu.Game.Graphics.UserInterface /// public virtual T DisplayedCount { - get - { - return displayedCount; - } + get => displayedCount; set { if (EqualityComparer.Default.Equals(displayedCount, value)) return; + displayedCount = value; DisplayedCountSpriteText.Text = FormatCount(value); } @@ -61,22 +59,16 @@ namespace osu.Game.Graphics.UserInterface public abstract void Increment(T amount); - private float textSize; - public float TextSize { - get { return textSize; } - set - { - textSize = value; - DisplayedCountSpriteText.TextSize = value; - } + get => DisplayedCountSpriteText.Font.Size; + set => DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: value); } public Color4 AccentColour { - get { return DisplayedCountSpriteText.Colour; } - set { DisplayedCountSpriteText.Colour = value; } + get => DisplayedCountSpriteText.Colour; + set => DisplayedCountSpriteText.Colour = value; } /// @@ -86,20 +78,17 @@ namespace osu.Game.Graphics.UserInterface { Children = new Drawable[] { - DisplayedCountSpriteText = new OsuSpriteText - { - Font = @"Venera" - }, + DisplayedCountSpriteText = new OsuSpriteText { Font = OsuFont.Numeric } }; TextSize = 40; AutoSizeAxes = Axes.Both; - DisplayedCount = Current; + DisplayedCount = Current.Value; - Current.ValueChanged += newValue => + Current.ValueChanged += val => { - if (IsLoaded) TransformCount(displayedCount, newValue); + if (IsLoaded) TransformCount(displayedCount, val.NewValue); }; } @@ -107,7 +96,7 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - DisplayedCountSpriteText.Text = FormatCount(Current); + DisplayedCountSpriteText.Text = FormatCount(Current.Value); } /// @@ -126,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface public virtual void StopRolling() { FinishTransforms(false, nameof(DisplayedCount)); - DisplayedCount = Current; + DisplayedCount = Current.Value; } /// diff --git a/osu.Game/Graphics/UserInterface/ScoreCounter.cs b/osu.Game/Graphics/UserInterface/ScoreCounter.cs index 8bf30c32c6..63062cdc9d 100644 --- a/osu.Game/Graphics/UserInterface/ScoreCounter.cs +++ b/osu.Game/Graphics/UserInterface/ScoreCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -28,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface /// How many leading zeroes the counter will have. public ScoreCounter(uint leading = 0) { - DisplayedCountSpriteText.FixedWidth = true; + DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(fixedWidth: true); LeadingZeroes = leading; } @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.UserInterface public override void Increment(double amount) { - Current.Value = Current + amount; + Current.Value = Current.Value + amount; } } } diff --git a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs index adcf401546..3e0a6c3265 100644 --- a/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/ScreenBreadcrumbControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; @@ -8,47 +8,32 @@ using osu.Framework.Screens; namespace osu.Game.Graphics.UserInterface { /// - /// A which follows the active screen (and allows navigation) in a stack. + /// A which follows the active screen (and allows navigation) in a stack. /// - public class ScreenBreadcrumbControl : BreadcrumbControl + public class ScreenBreadcrumbControl : BreadcrumbControl { - private Screen last; - - public ScreenBreadcrumbControl(Screen initialScreen) + public ScreenBreadcrumbControl(ScreenStack stack) { - Current.ValueChanged += newScreen => - { - if (last != newScreen && !newScreen.IsCurrentScreen) - newScreen.MakeCurrent(); - }; + stack.ScreenPushed += onPushed; + stack.ScreenExited += onExited; - onPushed(initialScreen); + onPushed(null, stack.CurrentScreen); + + Current.ValueChanged += current => current.NewValue.MakeCurrent(); } - private void screenChanged(Screen newScreen) + private void onPushed(IScreen lastScreen, IScreen newScreen) { - if (newScreen == null) return; - - if (last != null) - { - last.Exited -= screenChanged; - last.ModePushed -= onPushed; - } - - last = newScreen; - - newScreen.Exited += screenChanged; - newScreen.ModePushed += onPushed; - + AddItem(newScreen); Current.Value = newScreen; } - private void onPushed(Screen screen) + private void onExited(IScreen lastScreen, IScreen newScreen) { - Items.ToList().SkipWhile(i => i != Current.Value).Skip(1).ForEach(RemoveItem); - AddItem(screen); + if (newScreen != null) + Current.Value = newScreen; - screenChanged(screen); + Items.ToList().SkipWhile(s => s != Current.Value).Skip(1).ForEach(RemoveItem); } } } diff --git a/osu.Game/Graphics/UserInterface/ScreenTitle.cs b/osu.Game/Graphics/UserInterface/ScreenTitle.cs new file mode 100644 index 0000000000..7b39238e5e --- /dev/null +++ b/osu.Game/Graphics/UserInterface/ScreenTitle.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour + { + public const float ICON_WIDTH = ICON_SIZE + icon_spacing; + + protected const float ICON_SIZE = 25; + + private SpriteIcon iconSprite; + private readonly OsuSpriteText titleText, pageText; + + private const float icon_spacing = 10; + + protected IconUsage Icon + { + set + { + if (iconSprite == null) + throw new InvalidOperationException($"Cannot use {nameof(Icon)} with a custom {nameof(CreateIcon)} function."); + + iconSprite.Icon = value; + } + } + + protected string Title + { + set => titleText.Text = value; + } + + protected string Section + { + set => pageText.Text = value; + } + + public Color4 AccentColour + { + get => pageText.Colour; + set => pageText.Colour = value; + } + + protected virtual Drawable CreateIcon() => iconSprite = new SpriteIcon + { + Size = new Vector2(ICON_SIZE), + }; + + protected ScreenTitle() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(icon_spacing, 0), + Children = new[] + { + CreateIcon(), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(6, 0), + Children = new[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), + }, + pageText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Light), + } + } + } + } + }, + }; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/SearchTextBox.cs b/osu.Game/Graphics/UserInterface/SearchTextBox.cs index 40db6677f5..7023711aaa 100644 --- a/osu.Game/Graphics/UserInterface/SearchTextBox.cs +++ b/osu.Game/Graphics/UserInterface/SearchTextBox.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osuTK; using osuTK.Input; @@ -21,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface { new SpriteIcon { - Icon = FontAwesome.fa_search, + Icon = FontAwesome.Solid.Search, Origin = Anchor.CentreRight, Anchor = Anchor.CentreRight, Margin = new MarginPadding { Right = 10 }, diff --git a/osu.Game/Graphics/UserInterface/SelectionState.cs b/osu.Game/Graphics/UserInterface/SelectionState.cs index 079ae343eb..c85b2ad3ab 100644 --- a/osu.Game/Graphics/UserInterface/SelectionState.cs +++ b/osu.Game/Graphics/UserInterface/SelectionState.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Graphics.UserInterface { diff --git a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs index ddc48c9be7..4717401c75 100644 --- a/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs +++ b/osu.Game/Graphics/UserInterface/SimpleComboCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -33,7 +33,7 @@ namespace osu.Game.Graphics.UserInterface public override void Increment(int amount) { - Current.Value = Current + amount; + Current.Value = Current.Value + amount; } } } diff --git a/osu.Game/Graphics/UserInterface/StarCounter.cs b/osu.Game/Graphics/UserInterface/StarCounter.cs index 009a2a2e1b..3ee572602b 100644 --- a/osu.Game/Graphics/UserInterface/StarCounter.cs +++ b/osu.Game/Graphics/UserInterface/StarCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.MathUtils; using System; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Graphics.UserInterface { @@ -41,10 +42,7 @@ namespace osu.Game.Graphics.UserInterface /// public float CountStars { - get - { - return countStars; - } + get => countStars; set { @@ -102,6 +100,7 @@ namespace osu.Game.Graphics.UserInterface public void StopAnimation() { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); @@ -122,6 +121,7 @@ namespace osu.Game.Graphics.UserInterface private void transformCount(float newValue) { int i = 0; + foreach (var star in stars.Children) { star.ClearTransforms(true); @@ -137,6 +137,7 @@ namespace osu.Game.Graphics.UserInterface private class Star : Container { public readonly SpriteIcon Icon; + public Star() { Size = new Vector2(star_size); @@ -144,7 +145,7 @@ namespace osu.Game.Graphics.UserInterface Child = Icon = new SpriteIcon { Size = new Vector2(star_size), - Icon = FontAwesome.fa_star, + Icon = FontAwesome.Solid.Star, Anchor = Anchor.Centre, Origin = Anchor.Centre, }; diff --git a/osu.Game/Graphics/UserInterface/TriangleButton.cs b/osu.Game/Graphics/UserInterface/TriangleButton.cs index 683b442d93..5baf794227 100644 --- a/osu.Game/Graphics/UserInterface/TriangleButton.cs +++ b/osu.Game/Graphics/UserInterface/TriangleButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Allocation; @@ -27,14 +27,13 @@ namespace osu.Game.Graphics.UserInterface }); } - public IEnumerable FilterTerms => new[] { Text }; + public virtual IEnumerable FilterTerms => new[] { Text }; public bool MatchingFilter { - set - { - this.FadeTo(value ? 1 : 0); - } + set => this.FadeTo(value ? 1 : 0); } + + public bool FilteringActive { get; set; } } } diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 16f727b783..c74d00cd1e 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Beatmaps.ControlPoints; using osu.Framework.Audio.Track; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; @@ -48,11 +49,7 @@ namespace osu.Game.Graphics.UserInterface public override Anchor Origin { - get - { - return base.Origin; - } - + get => base.Origin; set { base.Origin = value; @@ -153,20 +150,14 @@ namespace osu.Game.Graphics.UserInterface }; } - public FontAwesome Icon + public IconUsage Icon { - set - { - bouncingIcon.Icon = value; - } + set => bouncingIcon.Icon = value; } public string Text { - set - { - text.Text = value; - } + set => text.Text = value; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => IconLayer.ReceivePositionalInputAt(screenSpacePos) || TextLayer.ReceivePositionalInputAt(screenSpacePos); @@ -174,7 +165,8 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { this.ResizeTo(SIZE_EXTENDED, transform_time, Easing.OutElastic); - IconLayer.FadeColour(HoverColour, transform_time, Easing.OutElastic); + + IconLayer.FadeColour(HoverColour, transform_time / 2f, Easing.OutQuint); bouncingIcon.ScaleTo(1.1f, transform_time, Easing.OutElastic); @@ -183,16 +175,13 @@ namespace osu.Game.Graphics.UserInterface protected override void OnHoverLost(HoverLostEvent e) { - this.ResizeTo(SIZE_RETRACTED, transform_time, Easing.OutElastic); - IconLayer.FadeColour(TextLayer.Colour, transform_time, Easing.OutElastic); + this.ResizeTo(SIZE_RETRACTED, transform_time, Easing.Out); + IconLayer.FadeColour(TextLayer.Colour, transform_time, Easing.Out); - bouncingIcon.ScaleTo(1, transform_time, Easing.OutElastic); + bouncingIcon.ScaleTo(1, transform_time, Easing.Out); } - protected override bool OnMouseDown(MouseDownEvent e) - { - return true; - } + protected override bool OnMouseDown(MouseDownEvent e) => true; protected override bool OnClick(ClickEvent e) { @@ -217,7 +206,10 @@ namespace osu.Game.Graphics.UserInterface private readonly SpriteIcon icon; - public FontAwesome Icon { set { icon.Icon = value; } } + public IconUsage Icon + { + set => icon.Icon = value; + } public BouncingIcon() { diff --git a/osu.Game/IO/Archives/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs index 24a5094586..4ee7a19ebc 100644 --- a/osu.Game/IO/Archives/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.IO; @@ -15,6 +15,8 @@ namespace osu.Game.IO.Archives /// public abstract Stream GetStream(string name); + public IEnumerable GetAvailableResources() => Filenames; + public abstract void Dispose(); /// diff --git a/osu.Game/IO/Archives/LegacyFilesystemReader.cs b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs similarity index 71% rename from osu.Game/IO/Archives/LegacyFilesystemReader.cs rename to osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs index dcaaf41c77..eff02ae7a5 100644 --- a/osu.Game/IO/Archives/LegacyFilesystemReader.cs +++ b/osu.Game/IO/Archives/LegacyDirectoryArchiveReader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.IO; @@ -8,13 +8,13 @@ using System.Linq; namespace osu.Game.IO.Archives { /// - /// Reads an extracted legacy beatmap from disk. + /// Reads an archive from a directory on disk. /// - public class LegacyFilesystemReader : ArchiveReader + public class LegacyDirectoryArchiveReader : ArchiveReader { private readonly string path; - public LegacyFilesystemReader(string path) + public LegacyDirectoryArchiveReader(string path) : base(Path.GetFileName(path)) { // re-get full path to standardise with Directory.GetFiles return values below. diff --git a/osu.Game/IO/Archives/LegacyFileArchiveReader.cs b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs new file mode 100644 index 0000000000..bd5f9cbd07 --- /dev/null +++ b/osu.Game/IO/Archives/LegacyFileArchiveReader.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.IO; + +namespace osu.Game.IO.Archives +{ + /// + /// Reads a file on disk as an archive. + /// Note: In this case, the file is not an extractable archive, use instead. + /// + public class LegacyFileArchiveReader : ArchiveReader + { + private readonly string path; + + public LegacyFileArchiveReader(string path) + : base(Path.GetFileName(path)) + { + // re-get full path to standardise + this.path = Path.GetFullPath(path); + } + + public override Stream GetStream(string name) => File.OpenRead(path); + + public override void Dispose() + { + } + + public override IEnumerable Filenames => new[] { Name }; + + public override Stream GetUnderlyingStream() => null; + } +} diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 2ea35835fa..d934ac54c4 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.IO; diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index 1804be90a0..e04bfb46cc 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -1,14 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.ComponentModel.DataAnnotations.Schema; using System.IO; +using osu.Game.Database; namespace osu.Game.IO { - public class FileInfo + public class FileInfo : IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Hash { get; set; } diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 050fccb98a..370d6786f5 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -1,9 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -21,11 +24,19 @@ namespace osu.Game.IO public new Storage Storage => base.Storage; - public FileStore(IDatabaseContextFactory contextFactory, Storage storage) : base(contextFactory, storage.GetStorageForDirectory(@"files")) + public FileStore(IDatabaseContextFactory contextFactory, Storage storage) + : base(contextFactory, storage.GetStorageForDirectory(@"files")) { Store = new StorageBackedResourceStore(Storage); } + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// Results from the provided query. + public IEnumerable QueryFiles(Expression> query) => ContextFactory.Get().Set().AsNoTracking().Where(f => f.ReferenceCount > 0).Where(query); + public FileInfo Add(Stream data, bool reference = true) { using (var usage = ContextFactory.GetForWrite()) diff --git a/osu.Game/IO/Legacy/ILegacySerializable.cs b/osu.Game/IO/Legacy/ILegacySerializable.cs index 1bf33d8bd7..0124ea756a 100644 --- a/osu.Game/IO/Legacy/ILegacySerializable.cs +++ b/osu.Game/IO/Legacy/ILegacySerializable.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.IO.Legacy { diff --git a/osu.Game/IO/Legacy/SerializationReader.cs b/osu.Game/IO/Legacy/SerializationReader.cs index 8fd18b9640..7a84c11930 100644 --- a/osu.Game/IO/Legacy/SerializationReader.cs +++ b/osu.Game/IO/Legacy/SerializationReader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -38,7 +38,8 @@ namespace osu.Game.IO.Legacy /// Reads a string from the buffer. Overrides the base implementation so it can cope with nulls. public override string ReadString() { - if (0 == ReadByte()) return null; + if (ReadByte() == 0) return null; + return base.ReadString(); } @@ -48,6 +49,7 @@ namespace osu.Game.IO.Legacy int len = ReadInt32(); if (len > 0) return ReadBytes(len); if (len < 0) return null; + return Array.Empty(); } @@ -57,6 +59,7 @@ namespace osu.Game.IO.Legacy int len = ReadInt32(); if (len > 0) return ReadChars(len); if (len < 0) return null; + return Array.Empty(); } @@ -65,6 +68,7 @@ namespace osu.Game.IO.Legacy { long ticks = ReadInt64(); if (ticks < 0) throw new IOException("Bad ticks count read!"); + return new DateTime(ticks, DateTimeKind.Utc); } @@ -73,6 +77,7 @@ namespace osu.Game.IO.Legacy { int count = ReadInt32(); if (count < 0) return null; + IList d = new List(count); SerializationReader sr = new SerializationReader(BaseStream); @@ -80,6 +85,7 @@ namespace osu.Game.IO.Legacy for (int i = 0; i < count; i++) { T obj = new T(); + try { obj.ReadFromStream(sr); @@ -88,6 +94,7 @@ namespace osu.Game.IO.Legacy { if (skipErrors) continue; + throw; } @@ -102,6 +109,7 @@ namespace osu.Game.IO.Legacy { int count = ReadInt32(); if (count < 0) return null; + IList d = new List(count); for (int i = 0; i < count; i++) d.Add((T)ReadObject()); return d; @@ -112,6 +120,7 @@ namespace osu.Game.IO.Legacy { int count = ReadInt32(); if (count < 0) return null; + IDictionary d = new Dictionary(); for (int i = 0; i < count; i++) d[(T)ReadObject()] = (U)ReadObject(); return d; @@ -121,44 +130,63 @@ namespace osu.Game.IO.Legacy public object ReadObject() { ObjType t = (ObjType)ReadByte(); + switch (t) { case ObjType.boolType: return ReadBoolean(); + case ObjType.byteType: return ReadByte(); + case ObjType.uint16Type: return ReadUInt16(); + case ObjType.uint32Type: return ReadUInt32(); + case ObjType.uint64Type: return ReadUInt64(); + case ObjType.sbyteType: return ReadSByte(); + case ObjType.int16Type: return ReadInt16(); + case ObjType.int32Type: return ReadInt32(); + case ObjType.int64Type: return ReadInt64(); + case ObjType.charType: return ReadChar(); + case ObjType.stringType: return base.ReadString(); + case ObjType.singleType: return ReadSingle(); + case ObjType.doubleType: return ReadDouble(); + case ObjType.decimalType: return ReadDecimal(); + case ObjType.dateTimeType: return ReadDateTime(); + case ObjType.byteArrayType: return ReadByteArray(); + case ObjType.charArrayType: return ReadCharArray(); + case ObjType.otherType: return DynamicDeserializer.Deserialize(BaseStream); + default: return null; } @@ -174,7 +202,7 @@ namespace osu.Game.IO.Legacy versionBinder = new VersionConfigToNamespaceAssemblyObjectBinder(); formatter = new BinaryFormatter { -// AssemblyFormat = FormatterAssemblyStyle.Simple, + // AssemblyFormat = FormatterAssemblyStyle.Simple, Binder = versionBinder }; } @@ -224,6 +252,7 @@ namespace osu.Game.IO.Legacy genType = BindToType(assemblyName, typ); } } + if (genType != null && tmpTypes.Count > 0) { return genType.MakeGenericType(tmpTypes.ToArray()); @@ -232,6 +261,7 @@ namespace osu.Game.IO.Legacy string toAssemblyName = assemblyName.Split(',')[0]; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (Assembly a in assemblies) { if (a.FullName.Split(',')[0] == toAssemblyName) diff --git a/osu.Game/IO/Legacy/SerializationWriter.cs b/osu.Game/IO/Legacy/SerializationWriter.cs index 85728794af..f30e4492af 100644 --- a/osu.Game/IO/Legacy/SerializationWriter.cs +++ b/osu.Game/IO/Legacy/SerializationWriter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,6 +8,7 @@ using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters; using System.Runtime.Serialization.Formatters.Binary; using System.Text; + // ReSharper disable ConditionIsAlwaysTrueOrFalse (we're allowing nulls to be passed to the writer where the underlying class doesn't). // ReSharper disable HeuristicUnreachableCode @@ -110,6 +111,7 @@ namespace osu.Game.IO.Legacy else { Write(d.Count); + foreach (KeyValuePair kvp in d) { WriteObject(kvp.Key); @@ -219,7 +221,7 @@ namespace osu.Game.IO.Legacy Write((byte)ObjType.otherType); BinaryFormatter b = new BinaryFormatter { -// AssemblyFormat = FormatterAssemblyStyle.Simple, + // AssemblyFormat = FormatterAssemblyStyle.Simple, TypeFormat = FormatterTypeStyle.TypesWhenNeeded }; b.Serialize(BaseStream, obj); diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index 42d47cc1fb..6d244bff60 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -65,6 +65,7 @@ namespace osu.Game.IO.Serialization.Converters var lookupTable = new List(); var objects = new List(); + foreach (var item in list) { var type = item.GetType(); @@ -75,6 +76,7 @@ namespace osu.Game.IO.Serialization.Converters typeString += $", {assemblyName.Version}"; int typeId = lookupTable.IndexOf(typeString); + if (typeId == -1) { lookupTable.Add(typeString); diff --git a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs index 4b057c0f2f..bf5edeef94 100644 --- a/osu.Game/IO/Serialization/Converters/Vector2Converter.cs +++ b/osu.Game/IO/Serialization/Converters/Vector2Converter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using Newtonsoft.Json; diff --git a/osu.Game/IO/Serialization/IJsonSerializable.cs b/osu.Game/IO/Serialization/IJsonSerializable.cs index ce6ff7c82d..ac95d47c4b 100644 --- a/osu.Game/IO/Serialization/IJsonSerializable.cs +++ b/osu.Game/IO/Serialization/IJsonSerializable.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; diff --git a/osu.Game/IO/Serialization/KeyContractResolver.cs b/osu.Game/IO/Serialization/KeyContractResolver.cs index e2b470ab4a..7b6905643c 100644 --- a/osu.Game/IO/Serialization/KeyContractResolver.cs +++ b/osu.Game/IO/Serialization/KeyContractResolver.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using Humanizer; using Newtonsoft.Json.Serialization; diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index fa8168c1de..484db932f8 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Diagnostics; using System.IO; @@ -38,7 +38,7 @@ namespace osu.Game.IPC } if (importer.HandledExtensions.Contains(Path.GetExtension(path)?.ToLowerInvariant())) - importer.Import(path); + await importer.Import(path); } } diff --git a/osu.Game/IPC/ScoreIPCChannel.cs b/osu.Game/IPC/ScoreIPCChannel.cs deleted file mode 100644 index a66b8ce1f3..0000000000 --- a/osu.Game/IPC/ScoreIPCChannel.cs +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Diagnostics; -using System.Threading.Tasks; -using osu.Framework.Platform; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.IPC -{ - public class ScoreIPCChannel : IpcChannel - { - private readonly ScoreStore scores; - - public ScoreIPCChannel(IIpcHost host, ScoreStore scores = null) - : base(host) - { - this.scores = scores; - MessageReceived += msg => - { - Debug.Assert(scores != null); - ImportAsync(msg.Path).ContinueWith(t => - { - if (t.Exception != null) throw t.Exception; - }, TaskContinuationOptions.OnlyOnFaulted); - }; - } - - public async Task ImportAsync(string path) - { - if (scores == null) - { - //we want to contact a remote osu! to handle the import. - await SendMessageAsync(new ScoreImportMessage { Path = path }); - return; - } - - scores.ReadReplayFile(path); - } - } - - public class ScoreImportMessage - { - public string Path; - } -} diff --git a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs index 7a230f32a0..8c0072c3da 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBinding.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBinding.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel.DataAnnotations.Schema; using osu.Framework.Input.Bindings; @@ -10,7 +10,6 @@ namespace osu.Game.Input.Bindings [Table("KeyBinding")] public class DatabasedKeyBinding : KeyBinding, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int? RulesetID { get; set; } @@ -20,15 +19,15 @@ namespace osu.Game.Input.Bindings [Column("Keys")] public string KeysString { - get { return KeyCombination.ToString(); } - private set { KeyCombination = value; } + get => KeyCombination.ToString(); + private set => KeyCombination = value; } [Column("Action")] public int IntAction { - get { return (int)Action; } - set { Action = value; } + get => (int)Action; + set => Action = value; } } } diff --git a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs index 85a8ac7aa3..f34b8f14b0 100644 --- a/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs +++ b/osu.Game/Input/Bindings/DatabasedKeyBindingContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 2f5f1aea3f..14d356f889 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.ComponentModel; @@ -43,12 +43,14 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select), + new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), }; public IEnumerable InGameKeyBindings => new[] { new KeyBinding(InputKey.Space, GlobalAction.SkipCutscene), new KeyBinding(InputKey.Tilde, GlobalAction.QuickRetry), + new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), }; @@ -61,31 +63,41 @@ namespace osu.Game.Input.Bindings { [Description("Toggle chat overlay")] ToggleChat, + [Description("Toggle social overlay")] ToggleSocial, + [Description("Reset input settings")] ResetInputSettings, + [Description("Toggle toolbar")] ToggleToolbar, + [Description("Toggle settings")] ToggleSettings, + [Description("Toggle osu!direct")] ToggleDirect, + [Description("Increase volume")] IncreaseVolume, + [Description("Decrease volume")] DecreaseVolume, + [Description("Toggle mute")] ToggleMute, // In-Game Keybindings [Description("Skip cutscene")] SkipCutscene, + [Description("Quick retry (hold)")] QuickRetry, [Description("Take screenshot")] TakeScreenshot, + [Description("Toggle gameplay mouse buttons")] ToggleGameplayMouseButtons, @@ -100,5 +112,8 @@ namespace osu.Game.Input.Bindings [Description("Select")] Select, + + [Description("Quick exit (Hold)")] + QuickExit, } } diff --git a/osu.Game/Input/Handlers/ReplayInputHandler.cs b/osu.Game/Input/Handlers/ReplayInputHandler.cs index c8c5657e41..93ed3ca884 100644 --- a/osu.Game/Input/Handlers/ReplayInputHandler.cs +++ b/osu.Game/Input/Handlers/ReplayInputHandler.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs new file mode 100644 index 0000000000..cbc446a126 --- /dev/null +++ b/osu.Game/Input/IdleTracker.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; + +namespace osu.Game.Input +{ + /// + /// Track whether the end-user is in an idle state, based on their last interaction with the game. + /// + public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalInput + { + private readonly double timeToIdle; + + private double lastInteractionTime; + + protected double TimeSpentIdle => Clock.CurrentTime - lastInteractionTime; + + /// + /// Whether the user is currently in an idle state. + /// + public IBindable IsIdle => isIdle; + + private readonly BindableBool isIdle = new BindableBool(); + + /// + /// Whether the game can currently enter an idle state. + /// + protected virtual bool AllowIdle => true; + + /// + /// Intstantiate a new . + /// + /// The length in milliseconds until an idle state should be assumed. + public IdleTracker(double timeToIdle) + { + this.timeToIdle = timeToIdle; + RelativeSizeAxes = Axes.Both; + } + + protected override void Update() + { + base.Update(); + isIdle.Value = TimeSpentIdle > timeToIdle && AllowIdle; + } + + public bool OnPressed(PlatformAction action) => updateLastInteractionTime(); + + public bool OnReleased(PlatformAction action) => updateLastInteractionTime(); + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case KeyDownEvent _: + case KeyUpEvent _: + case MouseDownEvent _: + case MouseUpEvent _: + case MouseMoveEvent _: + return updateLastInteractionTime(); + + default: + return base.Handle(e); + } + } + + private bool updateLastInteractionTime() + { + lastInteractionTime = Clock.CurrentTime; + return false; + } + } +} diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 29fdabbbc2..caddb1ae0d 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game/Migrations/20180125143340_Settings.cs b/osu.Game/Migrations/20180125143340_Settings.cs index 2e2768dc7c..166d3c086d 100644 --- a/osu.Game/Migrations/20180125143340_Settings.cs +++ b/osu.Game/Migrations/20180125143340_Settings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Migrations { ID = table.Column(type: "INTEGER", nullable: false) .Annotation("Sqlite:Autoincrement", true), - Key = table.Column(type: "INTEGER", nullable: false), + Key = table.Column(type: "TEXT", nullable: false), RulesetID = table.Column(type: "INTEGER", nullable: true), Value = table.Column(type: "TEXT", nullable: true), Variant = table.Column(type: "INTEGER", nullable: true) diff --git a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs index c38cd19b42..026442f328 100644 --- a/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs +++ b/osu.Game/Migrations/20180628011956_RemoveNegativeSetIDs.cs @@ -13,7 +13,6 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { - } } } diff --git a/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs new file mode 100644 index 0000000000..120674671a --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.Designer.cs @@ -0,0 +1,387 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181128100659_AddSkinInfoHash")] + partial class AddSkinInfoHash + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + 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("Status"); + + 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.Property("Status"); + + 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("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + 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/20181128100659_AddSkinInfoHash.cs b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs new file mode 100644 index 0000000000..860264a7dd --- /dev/null +++ b/osu.Game/Migrations/20181128100659_AddSkinInfoHash.cs @@ -0,0 +1,41 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddSkinInfoHash : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Hash", + table: "SkinInfo", + nullable: true); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo", + column: "DeletePending"); + + migrationBuilder.CreateIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo", + column: "Hash", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_SkinInfo_DeletePending", + table: "SkinInfo"); + + migrationBuilder.DropIndex( + name: "IX_SkinInfo_Hash", + table: "SkinInfo"); + + migrationBuilder.DropColumn( + name: "Hash", + table: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs new file mode 100644 index 0000000000..eee53182ce --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.Designer.cs @@ -0,0 +1,484 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20181130113755_AddScoreInfoTables")] + partial class AddScoreInfoTables + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + + 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("Status"); + + 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.Property("Status"); + + 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.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + 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("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + 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.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany() + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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/20181130113755_AddScoreInfoTables.cs b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs new file mode 100644 index 0000000000..2b6f94c5a4 --- /dev/null +++ b/osu.Game/Migrations/20181130113755_AddScoreInfoTables.cs @@ -0,0 +1,112 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddScoreInfoTables : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ScoreInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Rank = table.Column(nullable: false), + TotalScore = table.Column(nullable: false), + Accuracy = table.Column(type: "DECIMAL(1,4)", nullable: false), + PP = table.Column(nullable: true), + MaxCombo = table.Column(nullable: false), + Combo = table.Column(nullable: false), + RulesetID = table.Column(nullable: false), + Mods = table.Column(nullable: true), + User = table.Column(nullable: true), + BeatmapInfoID = table.Column(nullable: false), + OnlineScoreID = table.Column(nullable: true), + Date = table.Column(nullable: false), + Statistics = table.Column(nullable: true), + Hash = table.Column(nullable: true), + DeletePending = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreInfo_BeatmapInfo_BeatmapInfoID", + column: x => x.BeatmapInfoID, + principalTable: "BeatmapInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreInfo_RulesetInfo_RulesetID", + column: x => x.RulesetID, + principalTable: "RulesetInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ScoreFileInfo", + columns: table => new + { + ID = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(nullable: false), + Filename = table.Column(nullable: false), + ScoreInfoID = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ScoreFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_ScoreFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_ScoreFileInfo_ScoreInfo_ScoreInfoID", + column: x => x.ScoreInfoID, + principalTable: "ScoreInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_FileInfoID", + table: "ScoreFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreFileInfo_ScoreInfoID", + table: "ScoreFileInfo", + column: "ScoreInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_BeatmapInfoID", + table: "ScoreInfo", + column: "BeatmapInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_OnlineScoreID", + table: "ScoreInfo", + column: "OnlineScoreID", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ScoreInfo_RulesetID", + table: "ScoreInfo", + column: "RulesetID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ScoreFileInfo"); + + migrationBuilder.DropTable( + name: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs new file mode 100644 index 0000000000..8e1e3a59f3 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.Designer.cs @@ -0,0 +1,487 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190225062029_AddUserIDColumn")] + partial class AddUserIDColumn + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.1-servicing-10028"); + + 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("Status"); + + 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.Property("Status"); + + 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.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.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.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.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + 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("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + 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.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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/20190225062029_AddUserIDColumn.cs b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs new file mode 100644 index 0000000000..0720e0eac7 --- /dev/null +++ b/osu.Game/Migrations/20190225062029_AddUserIDColumn.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddUserIDColumn : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "UserID", + table: "ScoreInfo", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "UserID", + table: "ScoreInfo"); + } + } +} diff --git a/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs new file mode 100644 index 0000000000..348c42adb9 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.Designer.cs @@ -0,0 +1,498 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190525060824_SkinSettings")] + partial class SkinSettings + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + 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("Status"); + + 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.Property("Status"); + + 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("Key") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("SkinInfoID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("SkinInfoID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + 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.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.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.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + 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("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + 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.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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/20190525060824_SkinSettings.cs b/osu.Game/Migrations/20190525060824_SkinSettings.cs new file mode 100644 index 0000000000..99237419b7 --- /dev/null +++ b/osu.Game/Migrations/20190525060824_SkinSettings.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class SkinSettings : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql(@"create table Settings_dg_tmp + ( + ID INTEGER not null + constraint PK_Settings + primary key autoincrement, + Key TEXT not null, + RulesetID INTEGER, + Value TEXT, + Variant INTEGER, + SkinInfoID int + constraint Settings_SkinInfo_ID_fk + references SkinInfo + on delete restrict + ); + + insert into Settings_dg_tmp(ID, Key, RulesetID, Value, Variant) select ID, Key, RulesetID, Value, Variant from Settings; + + drop table Settings; + + alter table Settings_dg_tmp rename to Settings; + + create index IX_Settings_RulesetID_Variant + on Settings (RulesetID, Variant); + + create index Settings_SkinInfoID_index + on Settings (SkinInfoID); + + "); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Settings_SkinInfo_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropIndex( + name: "IX_Settings_SkinInfoID", + table: "Settings"); + + migrationBuilder.DropColumn( + name: "SkinInfoID", + table: "Settings"); + } + } +} diff --git a/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs new file mode 100644 index 0000000000..9477369aa0 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.Designer.cs @@ -0,0 +1,489 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using osu.Game.Database; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20190605091246_AddDateAddedColumnToBeatmapSet")] + partial class AddDateAddedColumnToBeatmapSet + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); + + 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("Status"); + + 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("DateAdded"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.Property("Status"); + + 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("Key") + .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.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.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.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.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + + 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("Hash"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + 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.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + 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/20190605091246_AddDateAddedColumnToBeatmapSet.cs b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs new file mode 100644 index 0000000000..55dc18b6a3 --- /dev/null +++ b/osu.Game/Migrations/20190605091246_AddDateAddedColumnToBeatmapSet.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace osu.Game.Migrations +{ + public partial class AddDateAddedColumnToBeatmapSet : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DateAdded", + table: "BeatmapSetInfo", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DateAdded", + table: "BeatmapSetInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 663676a6d3..11b032a941 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.3-rtm-32065"); + .HasAnnotation("ProductVersion", "2.2.4-servicing-10062"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -166,6 +166,8 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); + b.Property("DateAdded"); + b.Property("DeletePending"); b.Property("Hash"); @@ -198,11 +200,13 @@ namespace osu.Game.Migrations b.Property("ID") .ValueGeneratedOnAdd(); - b.Property("IntKey") + b.Property("Key") .HasColumnName("Key"); b.Property("RulesetID"); + b.Property("SkinInfoID"); + b.Property("StringValue") .HasColumnName("Value"); @@ -210,11 +214,32 @@ namespace osu.Game.Migrations b.HasKey("ID"); + b.HasIndex("SkinInfoID"); + b.HasIndex("RulesetID", "Variant"); b.ToTable("Settings"); }); + 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.Input.Bindings.DatabasedKeyBinding", b => { b.Property("ID") @@ -239,25 +264,6 @@ namespace osu.Game.Migrations 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") @@ -281,6 +287,81 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("ScoreInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("ScoreInfoID"); + + b.ToTable("ScoreFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Accuracy") + .HasColumnType("DECIMAL(1,4)"); + + b.Property("BeatmapInfoID"); + + b.Property("Combo"); + + b.Property("Date"); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MaxCombo"); + + b.Property("ModsJson") + .HasColumnName("Mods"); + + b.Property("OnlineScoreID"); + + b.Property("PP"); + + b.Property("Rank"); + + b.Property("RulesetID"); + + b.Property("StatisticsJson") + .HasColumnName("Statistics"); + + b.Property("TotalScore"); + + b.Property("UserID") + .HasColumnName("UserID"); + + b.Property("UserString") + .HasColumnName("User"); + + b.HasKey("ID"); + + b.HasIndex("BeatmapInfoID"); + + b.HasIndex("OnlineScoreID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("ScoreInfo"); + }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => { b.Property("ID") @@ -311,10 +392,17 @@ namespace osu.Game.Migrations b.Property("DeletePending"); + b.Property("Hash"); + b.Property("Name"); b.HasKey("ID"); + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + b.ToTable("SkinInfo"); }); @@ -360,6 +448,38 @@ namespace osu.Game.Migrations .HasForeignKey("MetadataID"); }); + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Settings") + .HasForeignKey("SkinInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Scoring.ScoreInfo") + .WithMany("Files") + .HasForeignKey("ScoreInfoID"); + }); + + modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") + .WithMany("Scores") + .HasForeignKey("BeatmapInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => { b.HasOne("osu.Game.IO.FileInfo", "FileInfo") diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1dda257c95..d722c7a98a 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -1,12 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; +using System.Net.Http; using System.Threading; -using osu.Framework.Configuration; +using Newtonsoft.Json.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Configuration; @@ -20,7 +22,7 @@ namespace osu.Game.Online.API private readonly OsuConfigManager config; private readonly OAuth authentication; - public string Endpoint = @"https://osu.ppy.sh"; + public string Endpoint => @"https://osu.ppy.sh"; private const string client_id = @"5"; private const string client_secret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; @@ -35,7 +37,9 @@ namespace osu.Game.Online.API public Bindable LocalUser { get; } = new Bindable(createGuestUser()); - protected bool HasLogin => authentication.Token.Value != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password); + public Bindable Activity { get; } = new Bindable(); + + protected bool HasLogin => authentication.Token.Value != null || (!string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password)); private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource(); @@ -53,6 +57,12 @@ namespace osu.Game.Online.API authentication.TokenString = config.Get(OsuSetting.Token); authentication.Token.ValueChanged += onTokenChanged; + LocalUser.BindValueChanged(u => + { + u.OldValue?.Activity.UnbindFrom(Activity); + u.NewValue.Activity.BindTo(Activity); + }, true); + var thread = new Thread(run) { Name = "APIAccess", @@ -62,24 +72,26 @@ namespace osu.Game.Online.API thread.Start(); } - private void onTokenChanged(OAuthToken token) => config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); + private void onTokenChanged(ValueChangedEvent e) => config.Set(OsuSetting.Token, config.Get(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty); private readonly List components = new List(); internal new void Schedule(Action action) => base.Schedule(action); + /// + /// Register a component to receive API events. + /// Fires once immediately to ensure a correct state. + /// + /// public void Register(IOnlineComponent component) { - Scheduler.Add(delegate - { - components.Add(component); - component.APIStateChanged(this, state); - }); + Schedule(() => components.Add(component)); + component.APIStateChanged(this, state); } public void Unregister(IOnlineComponent component) { - Scheduler.Add(delegate { components.Remove(component); }); + Schedule(() => components.Remove(component)); } public string AccessToken => authentication.RequestAccessToken(); @@ -99,13 +111,17 @@ namespace osu.Game.Online.API //todo: replace this with a ping request. log.Add(@"In a failing state, waiting a bit before we try again..."); Thread.Sleep(5000); + + if (!IsLoggedIn) goto case APIState.Connecting; + if (queue.Count == 0) { log.Add(@"Queueing a ping request"); - Queue(new ListChannelsRequest { Timeout = 5000 }); + Queue(new GetUserRequest()); } break; + case APIState.Offline: case APIState.Connecting: //work to restore a connection... @@ -143,7 +159,8 @@ namespace osu.Game.Online.API if (!handleRequest(userReq)) { - Thread.Sleep(500); + if (State == APIState.Connecting) + State = APIState.Failing; continue; } @@ -159,7 +176,7 @@ namespace osu.Game.Online.API //hard bail if we can't get a valid access token. if (authentication.RequestAccessToken() == null) { - Logout(false); + Logout(); continue; } @@ -170,10 +187,10 @@ namespace osu.Game.Online.API lock (queue) { if (queue.Count == 0) break; + req = queue.Dequeue(); } - // TODO: handle failures better handleRequest(req); } @@ -189,66 +206,66 @@ namespace osu.Game.Online.API this.password = password; } + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) + { + Debug.Assert(State == APIState.Offline); + + var req = new RegistrationRequest + { + Url = $@"{Endpoint}/users", + Method = HttpMethod.Post, + Username = username, + Email = email, + Password = password + }; + + try + { + req.Perform(); + } + catch (Exception e) + { + try + { + return JObject.Parse(req.ResponseString).SelectToken("form_error", true).ToObject(); + } + catch + { + // if we couldn't deserialize the error message let's throw the original exception outwards. + throw e; + } + } + + return null; + } + /// /// Handle a single API request. + /// Ensures all exceptions are caught and dealt with correctly. /// /// The request. - /// true if we should remove this request from the queue. + /// true if the request succeeded. private bool handleRequest(APIRequest req) { try { - Logger.Log($@"Performing request {req}", LoggingTarget.Network); req.Perform(this); //we could still be in initialisation, at which point we don't want to say we're Online yet. - if (IsLoggedIn) - State = APIState.Online; + if (IsLoggedIn) State = APIState.Online; failureCount = 0; return true; } catch (WebException we) { - HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode - ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); - - // special cases for un-typed but useful message responses. - switch (we.Message) - { - case "Unauthorized": - statusCode = HttpStatusCode.Unauthorized; - break; - } - - switch (statusCode) - { - case HttpStatusCode.Unauthorized: - Logout(false); - return true; - case HttpStatusCode.RequestTimeout: - failureCount++; - log.Add($@"API failure count is now {failureCount}"); - - if (failureCount < 3) - //we might try again at an api level. - return false; - - State = APIState.Failing; - flushQueue(); - return true; - } - - req.Fail(we); - return true; + handleWebException(we); + return false; } - catch (Exception e) + catch (Exception ex) { - if (e is TimeoutException) - log.Add(@"API level timeout exception was hit"); - - req.Fail(e); - return true; + Logger.Error(ex, "Error occurred while handling an API request."); + return false; } } @@ -256,26 +273,64 @@ namespace osu.Game.Online.API public APIState State { - get { return state; } + get => state; private set { - APIState oldState = state; - APIState newState = value; + if (state == value) + return; + APIState oldState = state; state = value; - if (oldState != newState) + log.Add($@"We just went {state}!"); + Schedule(() => { - log.Add($@"We just went {newState}!"); - Scheduler.Add(delegate - { - components.ForEach(c => c.APIStateChanged(this, newState)); - OnStateChange?.Invoke(oldState, newState); - }); - } + components.ForEach(c => c.APIStateChanged(this, state)); + OnStateChange?.Invoke(oldState, state); + }); } } + private bool handleWebException(WebException we) + { + HttpStatusCode statusCode = (we.Response as HttpWebResponse)?.StatusCode + ?? (we.Status == WebExceptionStatus.UnknownError ? HttpStatusCode.NotAcceptable : HttpStatusCode.RequestTimeout); + + // special cases for un-typed but useful message responses. + switch (we.Message) + { + case "Unauthorized": + case "Forbidden": + statusCode = HttpStatusCode.Unauthorized; + break; + } + + switch (statusCode) + { + case HttpStatusCode.Unauthorized: + Logout(); + return true; + + case HttpStatusCode.RequestTimeout: + failureCount++; + log.Add($@"API failure count is now {failureCount}"); + + if (failureCount < 3) + //we might try again at an api level. + return false; + + if (State == APIState.Online) + { + State = APIState.Failing; + flushQueue(); + } + + return true; + } + + return true; + } + public bool IsLoggedIn => LocalUser.Value.Id > 1; public void Queue(APIRequest request) @@ -303,21 +358,20 @@ namespace osu.Game.Online.API } } - public void Logout(bool clearUsername = true) + public void Logout() { flushQueue(); - if (clearUsername) ProvidedUsername = null; + password = null; authentication.Clear(); - LocalUser.Value = createGuestUser(); + + // Scheduled prior to state change such that the state changed event is invoked with the correct user present + Schedule(() => LocalUser.Value = createGuestUser()); + State = APIState.Offline; } - private static User createGuestUser() => new User - { - Username = @"Guest", - Id = 1, - }; + private static User createGuestUser() => new GuestUser(); protected override void Dispose(bool isDisposing) { @@ -328,6 +382,15 @@ namespace osu.Game.Online.API } } + internal class GuestUser : User + { + public GuestUser() + { + Username = @"Guest"; + Id = 1; + } + } + public enum APIState { /// diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index 86558faf3b..940b9b4803 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -1,20 +1,32 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.IO; using osu.Framework.IO.Network; namespace osu.Game.Online.API { public abstract class APIDownloadRequest : APIRequest { + private string filename; + + /// + /// Used to set the extension of the file returned by this request. + /// + protected virtual string FileExtension { get; } = @".tmp"; + protected override WebRequest CreateWebRequest() { - var request = new WebRequest(Uri); + var file = Path.GetTempFileName(); + + File.Move(file, filename = Path.ChangeExtension(file, FileExtension)); + + var request = new FileWebRequest(filename, Uri); request.DownloadProgress += request_Progress; return request; } - private void request_Progress(long current, long total) => API.Schedule(() => Progress?.Invoke(current, total)); + private void request_Progress(long current, long total) => API.Schedule(() => Progressed?.Invoke(current, total)); protected APIDownloadRequest() { @@ -23,11 +35,11 @@ namespace osu.Game.Online.API private void onSuccess() { - Success?.Invoke(WebRequest.ResponseData); + Success?.Invoke(filename); } - public event APIProgressHandler Progress; + public event APIProgressHandler Progressed; - public new event APISuccessHandler Success; + public new event APISuccessHandler Success; } } diff --git a/osu.Game/Online/API/APIMessagesRequest.cs b/osu.Game/Online/API/APIMessagesRequest.cs index 991096c0af..3ad6b1d7c8 100644 --- a/osu.Game/Online/API/APIMessagesRequest.cs +++ b/osu.Game/Online/API/APIMessagesRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.IO.Network; diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index adbedb2aac..e8eff5a3a9 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.IO.Network; +using osu.Framework.Logging; namespace osu.Game.Online.API { @@ -35,23 +36,12 @@ namespace osu.Game.Online.API /// public abstract class APIRequest { - /// - /// The maximum amount of time before this request will fail. - /// - public int Timeout = WebRequest.DEFAULT_TIMEOUT; - - protected virtual string Target => string.Empty; + protected abstract string Target { get; } protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri); protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}"; - private double remainingTime => Math.Max(0, Timeout - (DateTimeOffset.UtcNow - (startTime ?? DateTimeOffset.MinValue)).TotalMilliseconds); - - public bool ExceededTimeout => remainingTime == 0; - - private DateTimeOffset? startTime; - protected APIAccess API; protected WebRequest WebRequest; @@ -71,53 +61,64 @@ namespace osu.Game.Online.API private Action pendingFailure; - public void Perform(APIAccess api) + public void Perform(IAPIProvider api) { - API = api; + if (!(api is APIAccess apiAccess)) + throw new NotSupportedException($"A {nameof(APIAccess)} is required to perform requests."); - if (checkAndProcessFailure()) + API = apiAccess; + + if (checkAndScheduleFailure()) return; - if (startTime == null) - startTime = DateTimeOffset.UtcNow; - - if (remainingTime <= 0) - throw new TimeoutException(@"API request timeout hit"); - WebRequest = CreateWebRequest(); WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; - WebRequest.AddHeader("Authorization", $"Bearer {api.AccessToken}"); + WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); - if (checkAndProcessFailure()) + if (checkAndScheduleFailure()) return; if (!WebRequest.Aborted) //could have been aborted by a Cancel() call + { + Logger.Log($@"Performing request {this}", LoggingTarget.Network); WebRequest.Perform(); + } - if (checkAndProcessFailure()) + if (checkAndScheduleFailure()) return; - api.Schedule(delegate { Success?.Invoke(); }); + API.Schedule(delegate + { + if (cancelled) return; + + Success?.Invoke(); + }); } public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled")); public void Fail(Exception e) { - cancelled = true; + if (WebRequest?.Completed == true) + return; + if (cancelled) + return; + + cancelled = true; WebRequest?.Abort(); + Logger.Log($@"Failing request {this} ({e})", LoggingTarget.Network); pendingFailure = () => Failure?.Invoke(e); - checkAndProcessFailure(); + checkAndScheduleFailure(); } /// /// Checked for cancellation or error. Also queues up the Failed event if we can. /// /// Whether we are in a failed or cancelled state. - private bool checkAndProcessFailure() + private bool checkAndScheduleFailure() { if (API == null || pendingFailure == null) return cancelled; @@ -128,7 +129,10 @@ namespace osu.Game.Online.API } public delegate void APIFailureHandler(Exception e); + public delegate void APISuccessHandler(); + public delegate void APIProgressHandler(long current, long total); + public delegate void APISuccessHandler(T content); } diff --git a/osu.Game/Online/API/ArchiveDownloadRequest.cs b/osu.Game/Online/API/ArchiveDownloadRequest.cs new file mode 100644 index 0000000000..f1966aeb2b --- /dev/null +++ b/osu.Game/Online/API/ArchiveDownloadRequest.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Online.API +{ + public abstract class ArchiveDownloadRequest : APIDownloadRequest + where TModel : class + { + public readonly TModel Model; + + public float Progress; + + public event Action DownloadProgressed; + + protected ArchiveDownloadRequest(TModel model) + { + Model = model; + + Progressed += (current, total) => DownloadProgressed?.Invoke(Progress = (float)current / total); + } + } +} diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index f99f184d5c..6c04c77dc0 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -1,23 +1,55 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using System.Collections.Generic; +using System.Threading; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Users; namespace osu.Game.Online.API { - public class DummyAPIAccess : IAPIProvider + public class DummyAPIAccess : Component, IAPIProvider { public Bindable LocalUser { get; } = new Bindable(new User { Username = @"Dummy", - Id = 1, + Id = 1001, }); + public Bindable Activity { get; } = new Bindable(); + public bool IsLoggedIn => true; - public void Update() + public string ProvidedUsername => LocalUser.Value.Username; + + public string Endpoint => "http://localhost"; + + private APIState state = APIState.Online; + + private readonly List components = new List(); + + public APIState State { + get => state; + private set + { + if (state == value) + return; + + state = value; + + Scheduler.Add(() => components.ForEach(c => c.APIStateChanged(this, value))); + } + } + + public DummyAPIAccess() + { + LocalUser.BindValueChanged(u => + { + u.OldValue?.Activity.UnbindFrom(Activity); + u.NewValue.Activity.BindTo(Activity); + }, true); } public virtual void Queue(APIRequest request) @@ -26,6 +58,36 @@ namespace osu.Game.Online.API public void Register(IOnlineComponent component) { + Scheduler.Add(delegate { components.Add(component); }); + component.APIStateChanged(this, state); + } + + public void Unregister(IOnlineComponent component) + { + Scheduler.Add(delegate { components.Remove(component); }); + } + + public void Login(string username, string password) + { + LocalUser.Value = new User + { + Username = username, + Id = 1001, + }; + + State = APIState.Online; + } + + public void Logout() + { + LocalUser.Value = new GuestUser(); + State = APIState.Offline; + } + + public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password) + { + Thread.Sleep(200); + return null; } } } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index a3fc9a699c..0cd41aee26 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Users; namespace osu.Game.Online.API @@ -13,11 +13,29 @@ namespace osu.Game.Online.API /// Bindable LocalUser { get; } + /// + /// The current user's activity. + /// + Bindable Activity { get; } + /// /// Returns whether the local user is logged in. /// bool IsLoggedIn { get; } + /// + /// The last username provided by the end-user. + /// May not be authenticated. + /// + string ProvidedUsername { get; } + + /// + /// The URL endpoint for this API. Does not include a trailing slash. + /// + string Endpoint { get; } + + APIState State { get; } + /// /// Queue a new request. /// @@ -29,5 +47,32 @@ namespace osu.Game.Online.API /// /// The component to register. void Register(IOnlineComponent component); + + /// + /// Unregisters a component to receive state changes. + /// + /// The component to unregister. + void Unregister(IOnlineComponent component); + + /// + /// Attempt to login using the provided credentials. This is a non-blocking operation. + /// + /// The user's username. + /// The user's password. + void Login(string username, string password); + + /// + /// Log out the current user. + /// + void Logout(); + + /// + /// Create a new user account. This is a blocking operation. + /// + /// The email to create the account with. + /// The username to create the account with. + /// The password to create the account with. + /// Any errors encoutnered during account creation. + RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password); } } diff --git a/osu.Game/Online/API/IOnlineComponent.cs b/osu.Game/Online/API/IOnlineComponent.cs index 5ca6143428..da6b784759 100644 --- a/osu.Game/Online/API/IOnlineComponent.cs +++ b/osu.Game/Online/API/IOnlineComponent.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Online.API { public interface IOnlineComponent { - void APIStateChanged(APIAccess api, APIState state); + void APIStateChanged(IAPIProvider api, APIState state); } } diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 7892df9aab..baf494ebf9 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Diagnostics; using System.Net.Http; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.IO.Network; namespace osu.Game.Online.API @@ -178,6 +178,7 @@ namespace osu.Game.Online.API AddParameter("grant_type", GrantType); AddParameter("client_id", ClientId); AddParameter("client_secret", ClientSecret); + AddParameter("scope", "*"); base.PrePerform(); } diff --git a/osu.Game/Online/API/OAuthToken.cs b/osu.Game/Online/API/OAuthToken.cs index b9458583f1..f103d0917d 100644 --- a/osu.Game/Online/API/OAuthToken.cs +++ b/osu.Game/Online/API/OAuthToken.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Globalization; @@ -19,15 +19,8 @@ namespace osu.Game.Online.API [JsonProperty(@"expires_in")] public long ExpiresIn { - get - { - return AccessTokenExpiry - DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - } - - set - { - AccessTokenExpiry = DateTimeOffset.Now.AddSeconds(value).ToUnixTimeSeconds(); - } + get => AccessTokenExpiry - DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + set => AccessTokenExpiry = DateTimeOffset.Now.AddSeconds(value).ToUnixTimeSeconds(); } public bool IsValid => !string.IsNullOrEmpty(AccessToken) && ExpiresIn > 30; @@ -57,6 +50,7 @@ namespace osu.Game.Online.API catch { } + return null; } } diff --git a/osu.Game/Online/API/RegistrationRequest.cs b/osu.Game/Online/API/RegistrationRequest.cs new file mode 100644 index 0000000000..349cd4de0c --- /dev/null +++ b/osu.Game/Online/API/RegistrationRequest.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API +{ + public class RegistrationRequest : WebRequest + { + internal string Username; + internal string Email; + internal string Password; + + protected override void PrePerform() + { + AddParameter("user[username]", Username); + AddParameter("user[user_email]", Email); + AddParameter("user[password]", Password); + + base.PrePerform(); + } + + public class RegistrationRequestErrors + { + public UserErrors User; + + public class UserErrors + { + [JsonProperty("username")] + public string[] Username; + + [JsonProperty("user_email")] + public string[] Email; + + [JsonProperty("password")] + public string[] Password; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/CreateNewPrivateMessageRequest.cs b/osu.Game/Online/API/Requests/CreateNewPrivateMessageRequest.cs index 00dab10c75..37ffc04e04 100644 --- a/osu.Game/Online/API/Requests/CreateNewPrivateMessageRequest.cs +++ b/osu.Game/Online/API/Requests/CreateNewPrivateMessageRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Net.Http; using osu.Framework.IO.Network; diff --git a/osu.Game/Online/API/Requests/CreateNewPrivateMessageResponse.cs b/osu.Game/Online/API/Requests/CreateNewPrivateMessageResponse.cs index 84f1593c31..f749b27114 100644 --- a/osu.Game/Online/API/Requests/CreateNewPrivateMessageResponse.cs +++ b/osu.Game/Online/API/Requests/CreateNewPrivateMessageResponse.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; using osu.Game.Online.Chat; diff --git a/osu.Game/Online/API/Requests/CreateRoomRequest.cs b/osu.Game/Online/API/Requests/CreateRoomRequest.cs new file mode 100644 index 0000000000..c848c55cc6 --- /dev/null +++ b/osu.Game/Online/API/Requests/CreateRoomRequest.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Online.API.Requests +{ + public class CreateRoomRequest : APIRequest + { + private readonly Room room; + + public CreateRoomRequest(Room room) + { + this.room = room; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.ContentType = "application/json"; + req.Method = HttpMethod.Post; + + req.AddRaw(JsonConvert.SerializeObject(room)); + + return req; + } + + protected override string Target => "rooms"; + } +} diff --git a/osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs b/osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs new file mode 100644 index 0000000000..e6246b4f1f --- /dev/null +++ b/osu.Game/Online/API/Requests/CreateRoomScoreRequest.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class CreateRoomScoreRequest : APIRequest + { + private readonly int roomId; + private readonly int playlistItemId; + + public CreateRoomScoreRequest(int roomId, int playlistItemId) + { + this.roomId = roomId; + this.playlistItemId = playlistItemId; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Post; + return req; + } + + protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; + } +} diff --git a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs index c72bf8cbed..707c59436d 100644 --- a/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/DownloadBeatmapSetRequest.cs @@ -1,27 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using System; namespace osu.Game.Online.API.Requests { - public class DownloadBeatmapSetRequest : APIDownloadRequest + public class DownloadBeatmapSetRequest : ArchiveDownloadRequest { - public readonly BeatmapSetInfo BeatmapSet; - - public Action DownloadProgressed; - private readonly bool noVideo; public DownloadBeatmapSetRequest(BeatmapSetInfo set, bool noVideo) + : base(set) { this.noVideo = noVideo; - BeatmapSet = set; - - Progress += (current, total) => DownloadProgressed?.Invoke((float) current / total); } - protected override string Target => $@"beatmapsets/{BeatmapSet.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}"; + protected override string Target => $@"beatmapsets/{Model.OnlineBeatmapSetID}/download{(noVideo ? "?noVideo=1" : "")}"; } } diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs deleted file mode 100644 index e3865be5fb..0000000000 --- a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; -using osu.Game.Online.API.Requests.Responses; - -namespace osu.Game.Online.API.Requests -{ - public class GetBeatmapDetailsRequest : APIRequest - { - private readonly BeatmapInfo beatmap; - - public GetBeatmapDetailsRequest(BeatmapInfo beatmap) - { - this.beatmap = beatmap; - } - - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}"; - } -} diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 9d254ce29d..b37a6804fe 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; diff --git a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs index d04e069cd6..8e6deeb3c6 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapSetRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Online.API.Requests.Responses; diff --git a/osu.Game/Online/API/Requests/GetChangelogBuildRequest.cs b/osu.Game/Online/API/Requests/GetChangelogBuildRequest.cs new file mode 100644 index 0000000000..baa15c70c4 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetChangelogBuildRequest.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetChangelogBuildRequest : APIRequest + { + private readonly string name; + private readonly string version; + + public GetChangelogBuildRequest(string streamName, string buildVersion) + { + name = streamName; + version = buildVersion; + } + + protected override string Target => $@"changelog/{name}/{version}"; + } +} diff --git a/osu.Game/Online/API/Requests/GetChangelogRequest.cs b/osu.Game/Online/API/Requests/GetChangelogRequest.cs new file mode 100644 index 0000000000..97799ff66a --- /dev/null +++ b/osu.Game/Online/API/Requests/GetChangelogRequest.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetChangelogRequest : APIRequest + { + protected override string Target => @"changelog"; + } +} diff --git a/osu.Game/Online/API/Requests/GetFriendsRequest.cs b/osu.Game/Online/API/Requests/GetFriendsRequest.cs index 13bdd23170..46890aa889 100644 --- a/osu.Game/Online/API/Requests/GetFriendsRequest.cs +++ b/osu.Game/Online/API/Requests/GetFriendsRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Users; diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs index 94f5a114ad..36e81a9348 100644 --- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs +++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Online.Chat; diff --git a/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs b/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs new file mode 100644 index 0000000000..993e49dab2 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetRoomScoresRequest.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetRoomScoresRequest : APIRequest> + { + private readonly int roomId; + + public GetRoomScoresRequest(int roomId) + { + this.roomId = roomId; + } + + protected override string Target => $@"rooms/{roomId}/leaderboard"; + } +} diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/API/Requests/GetRoomsRequest.cs new file mode 100644 index 0000000000..8f1497ef33 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetRoomsRequest.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Lounge.Components; + +namespace osu.Game.Online.API.Requests +{ + public class GetRoomsRequest : APIRequest> + { + private readonly PrimaryFilter primaryFilter; + + public GetRoomsRequest(PrimaryFilter primaryFilter) + { + this.primaryFilter = primaryFilter; + } + + protected override string Target + { + get + { + string target = "rooms"; + + switch (primaryFilter) + { + case PrimaryFilter.Open: + break; + + case PrimaryFilter.Owned: + target += "/owned"; + break; + + case PrimaryFilter.Participated: + target += "/participated"; + break; + + case PrimaryFilter.RecentlyEnded: + target += "/ended"; + break; + } + + return target; + } + } + } +} diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 3be5b91a0d..0b6f65a0e0 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Beatmaps; @@ -10,18 +10,18 @@ using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest { private readonly BeatmapInfo beatmap; - private readonly LeaderboardScope scope; + private readonly BeatmapLeaderboardScope scope; private readonly RulesetInfo ruleset; - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, LeaderboardScope scope = LeaderboardScope.Global) + public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global) { if (!beatmap.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); - if (scope == LeaderboardScope.Local) + if (scope == BeatmapLeaderboardScope.Local) throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); this.beatmap = beatmap; @@ -31,10 +31,10 @@ namespace osu.Game.Online.API.Requests Success += onSuccess; } - private void onSuccess(APIScores r) + private void onSuccess(APILegacyScores r) { - foreach (APIScore score in r.Scores) - score.ApplyBeatmap(beatmap); + foreach (APILegacyScoreInfo score in r.Scores) + score.Beatmap = beatmap; } protected override WebRequest CreateWebRequest() diff --git a/osu.Game/Online/API/Requests/GetUpdatesRequest.cs b/osu.Game/Online/API/Requests/GetUpdatesRequest.cs index 950ad93396..488bb8bbf4 100644 --- a/osu.Game/Online/API/Requests/GetUpdatesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUpdatesRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; using osu.Framework.IO.Network; diff --git a/osu.Game/Online/API/Requests/GetUpdatesResponse.cs b/osu.Game/Online/API/Requests/GetUpdatesResponse.cs index 474d8e1501..4ac806bedc 100644 --- a/osu.Game/Online/API/Requests/GetUpdatesResponse.cs +++ b/osu.Game/Online/API/Requests/GetUpdatesResponse.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using Newtonsoft.Json; diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index b847a954f6..45d751f00e 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using Humanizer; using System.Collections.Generic; diff --git a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs index 9a54aafa82..40e52bdaf6 100644 --- a/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserMostPlayedBeatmapsRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Online.API.Requests.Responses; diff --git a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs index e1cad1a532..9f80180e70 100644 --- a/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRecentActivitiesRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Online.API.Requests.Responses; diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 607e8e5127..37ed0574f1 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Users; diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 4d4aa4d957..48a43bbbad 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : APIRequest> + public class GetUserScoresRequest : APIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/GetUsersRequest.cs b/osu.Game/Online/API/Requests/GetUsersRequest.cs index 17dd9263c9..55df88b7e5 100644 --- a/osu.Game/Online/API/Requests/GetUsersRequest.cs +++ b/osu.Game/Online/API/Requests/GetUsersRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Online.API.Requests.Responses; diff --git a/osu.Game/Online/API/Requests/JoinChannelRequest.cs b/osu.Game/Online/API/Requests/JoinChannelRequest.cs index a0a4667e98..f6ed5f22c9 100644 --- a/osu.Game/Online/API/Requests/JoinChannelRequest.cs +++ b/osu.Game/Online/API/Requests/JoinChannelRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Net.Http; using osu.Framework.IO.Network; diff --git a/osu.Game/Online/API/Requests/JoinRoomRequest.cs b/osu.Game/Online/API/Requests/JoinRoomRequest.cs new file mode 100644 index 0000000000..36b275236c --- /dev/null +++ b/osu.Game/Online/API/Requests/JoinRoomRequest.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class JoinRoomRequest : APIRequest + { + private readonly Room room; + private readonly User user; + + public JoinRoomRequest(Room room, User user) + { + this.room = room; + this.user = user; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Put; + return req; + } + + protected override string Target => $"rooms/{room.RoomID.Value}/users/{user.Id}"; + } +} diff --git a/osu.Game/Online/API/Requests/LeaveChannelRequest.cs b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs index 89bfa303c6..f2ae3926bd 100644 --- a/osu.Game/Online/API/Requests/LeaveChannelRequest.cs +++ b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Net.Http; using osu.Framework.IO.Network; diff --git a/osu.Game/Online/API/Requests/ListChannelsRequest.cs b/osu.Game/Online/API/Requests/ListChannelsRequest.cs index d1ee4fde01..9660695c14 100644 --- a/osu.Game/Online/API/Requests/ListChannelsRequest.cs +++ b/osu.Game/Online/API/Requests/ListChannelsRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Online.Chat; diff --git a/osu.Game/Online/API/Requests/PartRoomRequest.cs b/osu.Game/Online/API/Requests/PartRoomRequest.cs new file mode 100644 index 0000000000..e1550cb2e0 --- /dev/null +++ b/osu.Game/Online/API/Requests/PartRoomRequest.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Online.API.Requests +{ + public class PartRoomRequest : APIRequest + { + private readonly Room room; + private readonly User user; + + public PartRoomRequest(Room room, User user) + { + this.room = room; + this.user = user; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Delete; + return req; + } + + protected override string Target => $"rooms/{room.RoomID.Value}/users/{user.Id}"; + } +} diff --git a/osu.Game/Online/API/Requests/PostMessageRequest.cs b/osu.Game/Online/API/Requests/PostMessageRequest.cs index 8d9ba5dd5d..84ab873acf 100644 --- a/osu.Game/Online/API/Requests/PostMessageRequest.cs +++ b/osu.Game/Online/API/Requests/PostMessageRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Net.Http; using osu.Framework.IO.Network; diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 193ccf1f6b..bcbe060f82 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; using osu.Game.Beatmaps; @@ -57,21 +57,23 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"version")] private string version { get; set; } + [JsonProperty(@"failtimes")] + private BeatmapMetrics metrics { get; set; } + public BeatmapInfo ToBeatmap(RulesetStore rulesets) { + var set = BeatmapSet?.ToBeatmapSet(rulesets); + return new BeatmapInfo { - Metadata = this, + Metadata = set?.Metadata ?? this, Ruleset = rulesets.GetRuleset(ruleset), StarDifficulty = starDifficulty, OnlineBeatmapID = OnlineBeatmapID, Version = version, Status = Status, - BeatmapSet = new BeatmapSetInfo - { - OnlineBeatmapSetID = OnlineBeatmapSetID, - Status = BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None - }, + BeatmapSet = set, + Metrics = metrics, BaseDifficulty = new BeatmapDifficulty { DrainRate = drainRate, diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapMetrics.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapMetrics.cs deleted file mode 100644 index e67c60cb91..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapMetrics.cs +++ /dev/null @@ -1,29 +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; -using osu.Game.Beatmaps; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APIBeatmapMetrics : BeatmapMetrics - { - //the online API returns some metrics as a nested object. - [JsonProperty(@"failtimes")] - private BeatmapMetrics failTimes - { - set - { - Fails = value.Fails; - Retries = value.Retries; - } - } - - //and other metrics in the beatmap set. - [JsonProperty(@"beatmapset")] - private BeatmapMetrics beatmapSet - { - set => Ratings = value.Ratings; - } - } -} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 8446285070..200a705500 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -54,12 +54,18 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"last_updated")] private DateTimeOffset lastUpdated { get; set; } + [JsonProperty(@"ratings")] + private int[] ratings { get; set; } + [JsonProperty(@"user_id")] private long creatorId { set => Author.Id = value; } + [JsonProperty(@"availability")] + private BeatmapSetOnlineAvailability availability { get; set; } + [JsonProperty(@"beatmaps")] private IEnumerable beatmaps { get; set; } @@ -70,6 +76,7 @@ namespace osu.Game.Online.API.Requests.Responses OnlineBeatmapSetID = OnlineBeatmapSetID, Metadata = this, Status = Status, + Metrics = ratings == null ? null : new BeatmapSetMetrics { Ratings = ratings }, OnlineInfo = new BeatmapSetOnlineInfo { Covers = covers, @@ -83,6 +90,7 @@ namespace osu.Game.Online.API.Requests.Responses Submitted = submitted, Ranked = ranked, LastUpdated = lastUpdated, + Availability = availability, }, Beatmaps = beatmaps?.Select(b => b.ToBeatmap(rulesets)).ToList(), }; diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs new file mode 100644 index 0000000000..36407c7b0e --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogBuild.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System; +using System.Collections.Generic; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogBuild : IEquatable + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("version")] + public string Version { get; set; } + + [JsonProperty("display_version")] + public string DisplayVersion { get; set; } + + [JsonProperty("users")] + public long Users { get; set; } + + [JsonProperty("created_at")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonProperty("update_stream")] + public APIUpdateStream UpdateStream { get; set; } + + [JsonProperty("changelog_entries")] + public List ChangelogEntries { get; set; } + + [JsonProperty("versions")] + public VersionNavigation Versions { get; set; } + + public class VersionNavigation + { + [JsonProperty("next")] + public APIChangelogBuild Next { get; set; } + + [JsonProperty("previous")] + public APIChangelogBuild Previous { get; set; } + } + + public bool Equals(APIChangelogBuild other) => Id == other?.Id; + + public override string ToString() => $"{UpdateStream.DisplayName} {DisplayVersion}"; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs new file mode 100644 index 0000000000..140e228acd --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogEntry.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogEntry + { + [JsonProperty("id")] + public long? Id { get; set; } + + [JsonProperty("repository")] + public string Repository { get; set; } + + [JsonProperty("github_pull_request_id")] + public long? GithubPullRequestId { get; set; } + + [JsonProperty("github_url")] + public string GithubUrl { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("category")] + public string Category { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + + [JsonProperty("message_html")] + public string MessageHtml { get; set; } + + [JsonProperty("major")] + public bool Major { get; set; } + + [JsonProperty("created_at")] + public DateTimeOffset? CreatedAt { get; set; } + + [JsonProperty("github_user")] + public APIChangelogUser GithubUser { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogIndex.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogIndex.cs new file mode 100644 index 0000000000..778e8754fe --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogIndex.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogIndex + { + [JsonProperty] + public List Builds; + + [JsonProperty] + public List Streams; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChangelogUser.cs b/osu.Game/Online/API/Requests/Responses/APIChangelogUser.cs new file mode 100644 index 0000000000..5891391e83 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChangelogUser.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChangelogUser + { + [JsonProperty("id")] + public long? Id { get; set; } + + [JsonProperty("display_name")] + public string DisplayName { get; set; } + + [JsonProperty("github_url")] + public string GithubUrl { get; set; } + + [JsonProperty("osu_username")] + public string OsuUsername { get; set; } + + [JsonProperty("user_id")] + public long? UserId { get; set; } + + [JsonProperty("user_url")] + public string UserUrl { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs b/osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs new file mode 100644 index 0000000000..a554101bc7 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APICreatedRoom.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APICreatedRoom : Room + { + [JsonProperty("error")] + public string Error { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs similarity index 58% rename from osu.Game/Online/API/Requests/Responses/APIScore.cs rename to osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 25eb32a79f..3060300077 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,16 +7,15 @@ using System.Linq; using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { - public class APIScore : Score + public class APILegacyScoreInfo : LegacyScoreInfo { [JsonProperty(@"score")] - private double totalScore + private int totalScore { set => TotalScore = value; } @@ -33,15 +32,6 @@ namespace osu.Game.Online.API.Requests.Responses set => User = value; } - [JsonProperty(@"replay_data")] - private Replay replay - { - set => Replay = value; - } - - [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } - [JsonProperty(@"score_id")] private long onlineScoreID { @@ -74,51 +64,79 @@ namespace osu.Game.Online.API.Requests.Responses } [JsonProperty(@"statistics")] - private Dictionary jsonStats + private Dictionary jsonStats { set { foreach (var kvp in value) { - HitResult newKey; switch (kvp.Key) { + case @"count_geki": + CountGeki = kvp.Value; + break; + case @"count_300": - newKey = HitResult.Great; + Count300 = kvp.Value; break; + + case @"count_katu": + CountKatu = kvp.Value; + break; + case @"count_100": - newKey = HitResult.Good; + Count100 = kvp.Value; break; + case @"count_50": - newKey = HitResult.Meh; + Count50 = kvp.Value; break; + case @"count_miss": - newKey = HitResult.Miss; + CountMiss = kvp.Value; break; + default: continue; } - - Statistics.Add(newKey, kvp.Value); } } } + [JsonProperty(@"mode_int")] + public int OnlineRulesetID + { + get => RulesetID; + set => RulesetID = value; + } + [JsonProperty(@"mods")] private string[] modStrings { get; set; } - public void ApplyBeatmap(BeatmapInfo beatmap) + public override BeatmapInfo Beatmap { - Beatmap = beatmap; - ApplyRuleset(beatmap.Ruleset); + get => base.Beatmap; + set + { + base.Beatmap = value; + if (Beatmap.Ruleset != null) + Ruleset = value.Ruleset; + } } - public void ApplyRuleset(RulesetInfo ruleset) + public override RulesetInfo Ruleset { - Ruleset = ruleset; + get => base.Ruleset; + set + { + base.Ruleset = value; - // Evaluate the mod string - Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.ShortenedName)).ToArray(); + if (modStrings != null) + { + // Evaluate the mod string + Mods = Ruleset.CreateInstance().GetAllMods().Where(mod => modStrings.Contains(mod.Acronym)).ToArray(); + } + } } } } diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs new file mode 100644 index 0000000000..c629caaa6f --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScores.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APILegacyScores + { + [JsonProperty(@"scores")] + public List Scores; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIMod.cs b/osu.Game/Online/API/Requests/Responses/APIMod.cs new file mode 100644 index 0000000000..b9da4f49ee --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIMod.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIMod : IMod + { + public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs index 2c65a37cf8..8695d09570 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using Humanizer; using Newtonsoft.Json; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { diff --git a/osu.Game/Online/API/Requests/Responses/APIRoomScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIRoomScoreInfo.cs new file mode 100644 index 0000000000..33467b59b2 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIRoomScoreInfo.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using osu.Game.Scoring; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIRoomScoreInfo : ScoreInfo + { + [JsonProperty("attempts")] + public int TotalAttempts { get; set; } + + [JsonProperty("completed")] + public int CompletedBeatmaps { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreToken.cs b/osu.Game/Online/API/Requests/Responses/APIScoreToken.cs new file mode 100644 index 0000000000..1d2465bedf --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIScoreToken.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIScoreToken + { + [JsonProperty("id")] + public int ID { get; set; } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIScores.cs b/osu.Game/Online/API/Requests/Responses/APIScores.cs deleted file mode 100644 index b4213db253..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APIScores.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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 Newtonsoft.Json; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APIScores - { - [JsonProperty(@"scores")] - public IEnumerable Scores; - } -} diff --git a/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs new file mode 100644 index 0000000000..d9e48373bb --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIUpdateStream.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; +using osu.Framework.Graphics.Colour; +using osuTK.Graphics; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIUpdateStream : IEquatable + { + [JsonProperty("id")] + public long Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("is_featured")] + public bool IsFeatured { get; set; } + + [JsonProperty("display_name")] + public string DisplayName { get; set; } + + [JsonProperty("latest_build")] + public APIChangelogBuild LatestBuild { get; set; } + + public bool Equals(APIUpdateStream other) => Id == other?.Id; + + public ColourInfo Colour + { + get + { + switch (Name) + { + case "stable40": + return new Color4(102, 204, 255, 255); + + case "stable": + return new Color4(34, 153, 187, 255); + + case "beta40": + return new Color4(255, 221, 85, 255); + + case "cuttingedge": + return new Color4(238, 170, 0, 255); + + case OsuGameBase.CLIENT_STREAM_NAME: + return new Color4(237, 18, 33, 255); + + case "web": + return new Color4(136, 102, 238, 255); + + default: + return new Color4(0, 0, 0, 255); + } + } + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 54c8451456..ff422b8292 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; using osu.Game.Users; diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 8a5aea9e97..4614fe29b7 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using Newtonsoft.Json; using osu.Game.Beatmaps; @@ -10,16 +10,16 @@ namespace osu.Game.Online.API.Requests.Responses public class APIUserMostPlayedBeatmap { [JsonProperty("beatmap_id")] - public int BeatmapID; + public int BeatmapID { get; set; } [JsonProperty("count")] - public int PlayCount; + public int PlayCount { get; set; } [JsonProperty] - private BeatmapInfo beatmap; + private BeatmapInfo beatmap { get; set; } [JsonProperty] - private APIBeatmapSet beatmapSet; + private APIBeatmapSet beatmapSet { get; set; } public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) { diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index ffea7b83e1..d9d05ff285 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; using osu.Game.Overlays; @@ -36,11 +36,12 @@ namespace osu.Game.Online.API.Requests [Description("Ranked & Approved")] RankedApproved = 0, - Approved = 1, + Qualified = 3, Loved = 8, Favourites = 2, - Qualified = 3, - Pending = 4, + + [Description("Pending & WIP")] + PendingWIP = 4, Graveyard = 5, [Description("My Maps")] diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs index cf8b40d068..b0f4fef81a 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsResponse.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using Newtonsoft.Json; diff --git a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs b/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs new file mode 100644 index 0000000000..50b62cd6ed --- /dev/null +++ b/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Net.Http; +using Newtonsoft.Json; +using osu.Framework.IO.Network; +using osu.Game.Scoring; + +namespace osu.Game.Online.API.Requests +{ + public class SubmitRoomScoreRequest : APIRequest + { + private readonly int scoreId; + private readonly int roomId; + private readonly int playlistItemId; + private readonly ScoreInfo scoreInfo; + + public SubmitRoomScoreRequest(int scoreId, int roomId, int playlistItemId, ScoreInfo scoreInfo) + { + this.scoreId = scoreId; + this.roomId = roomId; + this.playlistItemId = playlistItemId; + this.scoreInfo = scoreInfo; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + + req.ContentType = "application/json"; + req.Method = HttpMethod.Put; + + req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore + })); + + return req; + } + + protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{scoreId}"; + } +} diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 9d3b7b5cc9..9ec39c5cb1 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using Newtonsoft.Json; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Lists; using osu.Game.Users; @@ -41,7 +41,6 @@ namespace osu.Game.Online.Chat /// private readonly List pendingMessages = new List(); - /// /// An event that fires when new messages arrived. /// @@ -88,6 +87,17 @@ namespace osu.Game.Online.Chat { } + /// + /// Create a private messaging channel with the specified user. + /// + /// The user to create the private conversation with. + public Channel(User user) + { + Type = ChannelType.PM; + Users.Add(user); + Name = user.Username; + } + /// /// Adds the argument message as a local echo. When this local echo is resolved will get called. /// diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 73ac7c9df4..3af11ff20f 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -1,17 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; +using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; +using osu.Framework.Bindables; using osu.Framework.Logging; -using osu.Framework.Threading; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; namespace osu.Game.Online.Chat @@ -19,7 +18,7 @@ namespace osu.Game.Online.Chat /// /// Manages everything channel related /// - public class ChannelManager : Component, IOnlineComponent + public class ChannelManager : PollingComponent { /// /// The channels the player joins on startup @@ -31,6 +30,9 @@ namespace osu.Game.Online.Chat @"#lobby" }; + private readonly BindableList availableChannels = new BindableList(); + private readonly BindableList joinedChannels = new BindableList(); + /// /// The currently opened channel /// @@ -39,19 +41,22 @@ namespace osu.Game.Online.Chat /// /// The Channels the player has joined /// - public ObservableCollection JoinedChannels { get; } = new ObservableCollection(); //todo: should be publicly readonly + public IBindableList JoinedChannels => joinedChannels; /// /// The channels available for the player to join /// - public ObservableCollection AvailableChannels { get; } = new ObservableCollection(); //todo: should be publicly readonly + public IBindableList AvailableChannels => availableChannels; private IAPIProvider api; - private ScheduledDelegate fetchMessagesScheduleder; + + public readonly BindableBool HighPollRate = new BindableBool(); public ChannelManager() { CurrentChannel.ValueChanged += currentChannelChanged; + + HighPollRate.BindValueChanged(enabled => TimeBetweenPolls = enabled.NewValue ? 1000 : 6000, true); } /// @@ -76,11 +81,18 @@ namespace osu.Game.Online.Chat if (user == null) throw new ArgumentNullException(nameof(user)); + if (user.Id == api.LocalUser.Value.Id) + return; + CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id)) - ?? new Channel { Name = user.Username, Users = { user }, Type = ChannelType.PM }; + ?? new Channel(user); } - private void currentChannelChanged(Channel channel) => JoinChannel(channel); + private void currentChannelChanged(ValueChangedEvent e) + { + if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + JoinChannel(e.NewValue); + } /// /// Ensure we run post actions in sequence, once at a time. @@ -92,12 +104,14 @@ namespace osu.Game.Online.Chat /// /// The message text that is going to be posted /// Is true if the message is an action, e.g.: user is currently eating - public void PostMessage(string text, bool isAction = false) + /// An optional target channel. If null, will be used. + public void PostMessage(string text, bool isAction = false, Channel target = null) { - if (CurrentChannel.Value == null) - return; + if (target == null) + target = CurrentChannel.Value; - var currentChannel = CurrentChannel.Value; + if (target == null) + return; void dequeueAndRun() { @@ -109,7 +123,7 @@ namespace osu.Game.Online.Chat { if (!api.IsLoggedIn) { - currentChannel.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!")); + target.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!")); return; } @@ -117,29 +131,29 @@ namespace osu.Game.Online.Chat { Sender = api.LocalUser.Value, Timestamp = DateTimeOffset.Now, - ChannelId = CurrentChannel.Value.Id, + ChannelId = target.Id, IsAction = isAction, Content = text }; - currentChannel.AddLocalEcho(message); + target.AddLocalEcho(message); // if this is a PM and the first message, we need to do a special request to create the PM channel - if (currentChannel.Type == ChannelType.PM && !currentChannel.Joined) + if (target.Type == ChannelType.PM && !target.Joined.Value) { - var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(currentChannel.Users.First(), message); + var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(target.Users.First(), message); createNewPrivateMessageRequest.Success += createRes => { - currentChannel.Id = createRes.ChannelID; - currentChannel.ReplaceMessage(message, createRes.Message); + target.Id = createRes.ChannelID; + target.ReplaceMessage(message, createRes.Message); dequeueAndRun(); }; createNewPrivateMessageRequest.Failure += exception => { Logger.Error(exception, "Posting message failed."); - currentChannel.ReplaceMessage(message, null); + target.ReplaceMessage(message, null); dequeueAndRun(); }; @@ -151,14 +165,14 @@ namespace osu.Game.Online.Chat req.Success += m => { - currentChannel.ReplaceMessage(message, m); + target.ReplaceMessage(message, m); dequeueAndRun(); }; req.Failure += exception => { Logger.Error(exception, "Posting message failed."); - currentChannel.ReplaceMessage(message, null); + target.ReplaceMessage(message, null); dequeueAndRun(); }; @@ -174,9 +188,13 @@ namespace osu.Game.Online.Chat /// Posts a command locally. Commands like /help will result in a help message written in the current channel. /// /// the text containing the command identifier and command parameters. - public void PostCommand(string text) + /// An optional target channel. If null, will be used. + public void PostCommand(string text, Channel target = null) { - if (CurrentChannel.Value == null) + if (target == null) + target = CurrentChannel.Value; + + if (target == null) return; var parameters = text.Split(new[] { ' ' }, 2); @@ -188,7 +206,7 @@ namespace osu.Game.Online.Chat case "me": if (string.IsNullOrWhiteSpace(content)) { - CurrentChannel.Value.AddNewMessages(new ErrorMessage("Usage: /me [action]")); + target.AddNewMessages(new ErrorMessage("Usage: /me [action]")); break; } @@ -196,11 +214,11 @@ namespace osu.Game.Online.Chat break; case "help": - CurrentChannel.Value.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); + target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); break; default: - CurrentChannel.Value.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help")); + target.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help")); break; } } @@ -293,8 +311,8 @@ namespace osu.Game.Online.Chat found.Users.Remove(foundSelf); } - if (joined == null && addToJoined) JoinedChannels.Add(found); - if (available == null && addToAvailable) AvailableChannels.Add(found); + if (joined == null && addToJoined) joinedChannels.Add(found); + if (available == null && addToAvailable) availableChannels.Add(found); return found; } @@ -321,7 +339,7 @@ namespace osu.Game.Online.Chat switch (channel.Type) { case ChannelType.Public: - var req = new JoinChannelRequest(channel, api.LocalUser); + var req = new JoinChannelRequest(channel, api.LocalUser.Value); req.Success += () => JoinChannel(channel, true); req.Failure += ex => LeaveChannel(channel); api.Queue(req); @@ -346,84 +364,72 @@ namespace osu.Game.Online.Chat { if (channel == null) return; - if (channel == CurrentChannel.Value) CurrentChannel.Value = null; + if (channel == CurrentChannel.Value) + CurrentChannel.Value = null; - JoinedChannels.Remove(channel); + joinedChannels.Remove(channel); if (channel.Joined.Value) { - api.Queue(new LeaveChannelRequest(channel, api.LocalUser)); + api.Queue(new LeaveChannelRequest(channel, api.LocalUser.Value)); channel.Joined.Value = false; } } - public void APIStateChanged(APIAccess api, APIState state) - { - switch (state) - { - case APIState.Online: - fetchUpdates(); - break; - default: - fetchMessagesScheduleder?.Cancel(); - fetchMessagesScheduleder = null; - break; - } - } - private long lastMessageId; - private const int update_poll_interval = 1000; private bool channelsInitialised; - private void fetchUpdates() + protected override Task Poll() { - fetchMessagesScheduleder?.Cancel(); - fetchMessagesScheduleder = Scheduler.AddDelayed(() => + if (!api.IsLoggedIn) + return base.Poll(); + + var fetchReq = new GetUpdatesRequest(lastMessageId); + + var tcs = new TaskCompletionSource(); + + fetchReq.Success += updates => { - var fetchReq = new GetUpdatesRequest(lastMessageId); - - fetchReq.Success += updates => + if (updates?.Presence != null) { - if (updates?.Presence != null) + foreach (var channel in updates.Presence) { - foreach (var channel in updates.Presence) - { - // we received this from the server so should mark the channel already joined. - JoinChannel(channel, true); - } - - //todo: handle left channels - - handleChannelMessages(updates.Messages); - - foreach (var group in updates.Messages.GroupBy(m => m.ChannelId)) - JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); - - lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId; + // we received this from the server so should mark the channel already joined. + JoinChannel(channel, true); } - if (!channelsInitialised) - { - channelsInitialised = true; - // we want this to run after the first presence so we can see if the user is in any channels already. - initializeChannels(); - } + //todo: handle left channels - fetchUpdates(); - }; + handleChannelMessages(updates.Messages); - fetchReq.Failure += delegate { fetchUpdates(); }; + foreach (var group in updates.Messages.GroupBy(m => m.ChannelId)) + JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray()); - api.Queue(fetchReq); - }, update_poll_interval); + lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId; + } + + if (!channelsInitialised) + { + channelsInitialised = true; + // we want this to run after the first presence so we can see if the user is in any channels already. + initializeChannels(); + } + + tcs.SetResult(true); + }; + + fetchReq.Failure += _ => tcs.SetResult(false); + + api.Queue(fetchReq); + + return tcs.Task; } [BackgroundDependencyLoader] private void load(IAPIProvider api) { this.api = api; - api.Register(this); } } diff --git a/osu.Game/Online/Chat/ChannelType.cs b/osu.Game/Online/Chat/ChannelType.cs index c8b9463ded..7d2b661164 100644 --- a/osu.Game/Online/Chat/ChannelType.cs +++ b/osu.Game/Online/Chat/ChannelType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Online.Chat { diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index de017baf35..d27a3fbffe 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -1,12 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Graphics.Cursor; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -15,20 +13,20 @@ using osuTK; namespace osu.Game.Online.Chat { /// - /// An invisible drawable that brings multiple pieces together to form a consumable clickable link. + /// An invisible drawable that brings multiple pieces together to form a consumable clickable link. /// - public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip + public class DrawableLinkCompiler : OsuHoverContainer { /// /// Each word part of a chat link (split for word-wrap support). /// - public List Parts; + public List Parts; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceivePositionalInputAt(screenSpacePos)); protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); - public DrawableLinkCompiler(IEnumerable parts) + public DrawableLinkCompiler(IEnumerable parts) { Parts = parts.ToList(); } @@ -41,13 +39,11 @@ namespace osu.Game.Online.Chat protected override IEnumerable EffectTargets => Parts; - public string TooltipText { get; set; } - private class LinkHoverSounds : HoverClickSounds { - private readonly List parts; + private readonly List parts; - public LinkHoverSounds(HoverSampleSet sampleSet, List parts) + public LinkHoverSounds(HoverSampleSet sampleSet, List parts) : base(sampleSet) { this.parts = parts; diff --git a/osu.Game/Online/Chat/ErrorMessage.cs b/osu.Game/Online/Chat/ErrorMessage.cs index 87652c50c4..a8ff0e9a98 100644 --- a/osu.Game/Online/Chat/ErrorMessage.cs +++ b/osu.Game/Online/Chat/ErrorMessage.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Online.Chat { public class ErrorMessage : InfoMessage { - public ErrorMessage(string message) : base(message) + public ErrorMessage(string message) + : base(message) { Sender.Colour = @"ff0000"; } diff --git a/osu.Game/Online/Chat/ExternalLinkOpener.cs b/osu.Game/Online/Chat/ExternalLinkOpener.cs index d8b8adbbad..495f1ac0b0 100644 --- a/osu.Game/Online/Chat/ExternalLinkOpener.cs +++ b/osu.Game/Online/Chat/ExternalLinkOpener.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Configuration; @@ -27,7 +27,7 @@ namespace osu.Game.Online.Chat public void OpenUrlExternally(string url) { - if (externalLinkWarning) + if (externalLinkWarning.Value) dialogOverlay.Push(new ExternalLinkDialog(url, () => host.OpenUrlExternally(url))); else host.OpenUrlExternally(url); diff --git a/osu.Game/Online/Chat/InfoMessage.cs b/osu.Game/Online/Chat/InfoMessage.cs index 103a999591..8dce188804 100644 --- a/osu.Game/Online/Chat/InfoMessage.cs +++ b/osu.Game/Online/Chat/InfoMessage.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Users; @@ -10,7 +10,8 @@ namespace osu.Game.Online.Chat { private static int infoID = -1; - public InfoMessage(string message) : base(infoID--) + public InfoMessage(string message) + : base(infoID--) { Timestamp = DateTimeOffset.Now; Content = message; diff --git a/osu.Game/Online/Chat/LocalEchoMessage.cs b/osu.Game/Online/Chat/LocalEchoMessage.cs index 7d678029aa..8a39515575 100644 --- a/osu.Game/Online/Chat/LocalEchoMessage.cs +++ b/osu.Game/Online/Chat/LocalEchoMessage.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Online.Chat { public class LocalEchoMessage : LocalMessage { - public LocalEchoMessage() : base(null) + public LocalEchoMessage() + : base(null) { } } diff --git a/osu.Game/Online/Chat/LocalMessage.cs b/osu.Game/Online/Chat/LocalMessage.cs index 93f1e7f9ea..027edbcca5 100644 --- a/osu.Game/Online/Chat/LocalMessage.cs +++ b/osu.Game/Online/Chat/LocalMessage.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Online.Chat { diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 3f0f352ac7..2e41038a59 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -13,10 +13,6 @@ namespace osu.Game.Online.Chat [JsonProperty(@"message_id")] public readonly long? Id; - //todo: this should be inside sender. - [JsonProperty(@"sender_id")] - public long UserId; - [JsonProperty(@"channel_id")] public long ChannelId; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index dd19868954..4aaffdd161 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -25,19 +25,19 @@ namespace osu.Game.Online.Chat // This is in the format (, [optional]): // http[s]://.[:port][/path][?query][#fragment] private static readonly Regex advanced_link_regex = new Regex( - // protocol - @"(?[a-z]*?:\/\/" + - // domain + tld - @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + - // port (optional) - @"(?::\d+)?)" + - // path (optional) - @"(?(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" + - // query (optional) - @"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" + - // fragment (optional) - @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", - RegexOptions.IgnoreCase); + // protocol + @"(?[a-z]*?:\/\/" + + // domain + tld + @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + + // port (optional) + @"(?::\d+)?)" + + // path (optional) + @"(?(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" + + // query (optional) + @"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" + + // fragment (optional) + @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", + RegexOptions.IgnoreCase); // 00:00:000 (1,2,3) - test private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); @@ -51,24 +51,25 @@ namespace osu.Game.Online.Chat private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) { int captureOffset = 0; + foreach (Match m in regex.Matches(result.Text, startIndex)) { var index = m.Index - captureOffset; var displayText = string.Format(display, - m.Groups[0], - m.Groups.Count > 1 ? m.Groups[1].Value : "", - m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + m.Groups[0], + m.Groups.Count > 1 ? m.Groups[1].Value : "", + m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); var linkText = string.Format(link, - m.Groups[0], - m.Groups.Count > 1 ? m.Groups[1].Value : "", - m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + m.Groups[0], + m.Groups.Count > 1 ? m.Groups[1].Value : "", + m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); if (displayText.Length == 0 || linkText.Length == 0) continue; // Check for encapsulated links - if (result.Links.Find(l => l.Index <= index && l.Index + l.Length >= index + m.Length || index <= l.Index && index + m.Length >= l.Index + l.Length) == null) + if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || (index <= l.Index && index + m.Length >= l.Index + l.Length)) == null) { result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText); @@ -114,51 +115,63 @@ namespace osu.Game.Online.Chat case "b": case "beatmaps": return new LinkDetails(LinkAction.OpenBeatmap, args[3]); + case "s": case "beatmapsets": case "d": return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); + case "u": return new LinkDetails(LinkAction.OpenUserProfile, args[3]); } } return new LinkDetails(LinkAction.External, null); + case "osu": // every internal link also needs some kind of argument if (args.Length < 3) return new LinkDetails(LinkAction.External, null); LinkAction linkType; + switch (args[1]) { case "chan": linkType = LinkAction.OpenChannel; break; + case "edit": linkType = LinkAction.OpenEditorTimestamp; break; + case "b": linkType = LinkAction.OpenBeatmap; break; + case "s": case "dl": linkType = LinkAction.OpenBeatmapSet; break; + case "spectate": linkType = LinkAction.Spectate; break; + case "u": linkType = LinkAction.OpenUserProfile; break; + default: linkType = LinkAction.External; break; } return new LinkDetails(linkType, args[2]); + case "osump": return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); + default: return new LinkDetails(LinkAction.External, null); } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs new file mode 100644 index 0000000000..9dab2f2aba --- /dev/null +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -0,0 +1,156 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Chat; +using osuTK.Graphics; + +namespace osu.Game.Online.Chat +{ + /// + /// Display a chat channel in an insolated region. + /// + public class StandAloneChatDisplay : CompositeDrawable + { + public readonly Bindable Channel = new Bindable(); + + public Action Exit; + + private readonly FocusedTextBox textbox; + + protected ChannelManager ChannelManager; + + private DrawableChannel drawableChannel; + + private readonly bool postingTextbox; + + private const float textbox_height = 30; + + /// + /// Construct a new instance. + /// + /// Whether a textbox for posting new messages should be displayed. + public StandAloneChatDisplay(bool postingTextbox = false) + { + this.postingTextbox = postingTextbox; + CornerRadius = 10; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Black, + Alpha = 0.8f, + RelativeSizeAxes = Axes.Both + }, + }; + + if (postingTextbox) + { + AddInternal(textbox = new FocusedTextBox + { + RelativeSizeAxes = Axes.X, + Height = textbox_height, + PlaceholderText = "type your message", + OnCommit = postMessage, + ReleaseFocusOnCommit = false, + HoldFocus = true, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }); + + textbox.Exit += () => Exit?.Invoke(); + } + + Channel.BindValueChanged(channelChanged); + } + + [BackgroundDependencyLoader(true)] + private void load(ChannelManager manager) + { + if (ChannelManager == null) + ChannelManager = manager; + } + + private void postMessage(TextBox sender, bool newtext) + { + var text = textbox.Text.Trim(); + + if (string.IsNullOrWhiteSpace(text)) + return; + + if (text[0] == '/') + ChannelManager?.PostCommand(text.Substring(1), Channel.Value); + else + ChannelManager?.PostMessage(text, target: Channel.Value); + + textbox.Text = string.Empty; + } + + public void Contract() + { + this.FadeIn(300); + this.MoveToY(0, 500, Easing.OutQuint); + } + + public void Expand() + { + this.FadeOut(200); + this.MoveToY(100, 500, Easing.In); + } + + protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message); + + private void channelChanged(ValueChangedEvent e) + { + drawableChannel?.Expire(); + + if (e.NewValue == null) return; + + AddInternal(drawableChannel = new StandAloneDrawableChannel(e.NewValue) + { + CreateChatLineAction = CreateMessage, + Padding = new MarginPadding { Bottom = postingTextbox ? textbox_height : 0 } + }); + } + + protected class StandAloneDrawableChannel : DrawableChannel + { + public Func CreateChatLineAction; + + protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); + + public StandAloneDrawableChannel(Channel channel) + : base(channel) + { + } + + [BackgroundDependencyLoader] + private void load() + { + ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; + } + } + + protected class StandAloneMessage : ChatLine + { + protected override float TextSize => 15; + + protected override float HorizontalPadding => 10; + protected override float MessagePadding => 120; + + public StandAloneMessage(Message message) + : base(message) + { + } + } + } +} diff --git a/osu.Game/Online/DownloadState.cs b/osu.Game/Online/DownloadState.cs new file mode 100644 index 0000000000..72efbc286e --- /dev/null +++ b/osu.Game/Online/DownloadState.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Online +{ + public enum DownloadState + { + NotDownloaded, + Downloading, + Downloaded, + LocallyAvailable + } +} diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs new file mode 100644 index 0000000000..5eb2bb74bb --- /dev/null +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -0,0 +1,134 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Database; +using osu.Game.Online.API; + +namespace osu.Game.Online +{ + /// + /// A component which tracks a beatmap through potential download/import/deletion. + /// + public abstract class DownloadTrackingComposite : CompositeDrawable + where TModel : class, IEquatable + where TModelManager : class, IModelDownloader + { + protected readonly Bindable Model = new Bindable(); + + private TModelManager manager; + + /// + /// Holds the current download state of the beatmap, whether is has already been downloaded, is in progress, or is not downloaded. + /// + protected readonly Bindable State = new Bindable(); + + protected readonly Bindable Progress = new Bindable(); + + protected DownloadTrackingComposite(TModel model = null) + { + Model.Value = model; + } + + [BackgroundDependencyLoader(true)] + private void load(TModelManager manager) + { + this.manager = manager; + + Model.BindValueChanged(modelInfo => + { + if (modelInfo.NewValue == null) + attachDownload(null); + else if (manager.IsAvailableLocally(modelInfo.NewValue)) + State.Value = DownloadState.LocallyAvailable; + else + attachDownload(manager.GetExistingDownload(modelInfo.NewValue)); + }, true); + + manager.DownloadBegan += download => + { + if (download.Model.Equals(Model.Value)) + attachDownload(download); + }; + + manager.ItemAdded += itemAdded; + manager.ItemRemoved += itemRemoved; + } + + private ArchiveDownloadRequest attachedRequest; + + private void attachDownload(ArchiveDownloadRequest request) + { + if (attachedRequest != null) + { + attachedRequest.Failure -= onRequestFailure; + attachedRequest.DownloadProgressed -= onRequestProgress; + attachedRequest.Success -= onRequestSuccess; + } + + attachedRequest = request; + + if (attachedRequest != null) + { + if (attachedRequest.Progress == 1) + { + State.Value = DownloadState.Downloaded; + Progress.Value = 1; + } + else + { + State.Value = DownloadState.Downloading; + Progress.Value = attachedRequest.Progress; + + attachedRequest.Failure += onRequestFailure; + attachedRequest.DownloadProgressed += onRequestProgress; + attachedRequest.Success += onRequestSuccess; + } + } + else + { + State.Value = DownloadState.NotDownloaded; + } + } + + private void onRequestSuccess(string _) => Schedule(() => State.Value = DownloadState.Downloaded); + + private void onRequestProgress(float progress) => Schedule(() => Progress.Value = progress); + + private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); + + private void itemAdded(TModel s) => setDownloadStateFromManager(s, DownloadState.LocallyAvailable); + + private void itemRemoved(TModel s) => setDownloadStateFromManager(s, DownloadState.NotDownloaded); + + private void setDownloadStateFromManager(TModel s, DownloadState state) => Schedule(() => + { + if (!s.Equals(Model.Value)) + return; + + State.Value = state; + }); + + #region Disposal + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (manager != null) + { + manager.DownloadBegan -= attachDownload; + manager.ItemAdded -= itemAdded; + } + + State.UnbindAll(); + + attachDownload(null); + } + + #endregion + } +} diff --git a/osu.Game/Online/Leaderboards/DrawableRank.cs b/osu.Game/Online/Leaderboards/DrawableRank.cs new file mode 100644 index 0000000000..9bbaa28e2a --- /dev/null +++ b/osu.Game/Online/Leaderboards/DrawableRank.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Scoring; +using System; + +namespace osu.Game.Online.Leaderboards +{ + public class DrawableRank : Sprite + { + private readonly ScoreRank rank; + + public DrawableRank(ScoreRank rank) + { + this.rank = rank; + } + + [BackgroundDependencyLoader(true)] + private void load(TextureStore ts) + { + if (ts == null) + throw new ArgumentNullException(nameof(ts)); + + Texture = ts.Get($@"Grades/{getTextureName()}"); + } + + private string getTextureName() + { + switch (rank) + { + default: + return rank.GetDescription(); + + case ScoreRank.SH: + return "SPlus"; + + case ScoreRank.XH: + return "SSPlus"; + } + } + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs similarity index 62% rename from osu.Game/Screens/Select/Leaderboards/Leaderboard.cs rename to osu.Game/Online/Leaderboards/Leaderboard.cs index 0748f68dca..b91de93a4a 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -1,52 +1,45 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; -using osuTK; -using osuTK.Graphics; +using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Scoring; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using System.Linq; -using osu.Framework.Configuration; -using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { - public class Leaderboard : Container + public abstract class Leaderboard : Container, IOnlineComponent { private const double fade_duration = 300; - private readonly ScrollContainer scrollContainer; + private readonly OsuScrollContainer scrollContainer; private readonly Container placeholderContainer; private FillFlowContainer scrollFlow; - private readonly IBindable ruleset = new Bindable(); - - public Action ScoreSelected; - private readonly LoadingAnimation loading; private ScheduledDelegate showScoresDelegate; + private CancellationTokenSource showScoresCancellationSource; private bool scoresLoadedOnce; - private IEnumerable scores; + private IEnumerable scores; - public IEnumerable Scores + public IEnumerable Scores { - get { return scores; } + get => scores; set { scores = value; @@ -58,56 +51,62 @@ namespace osu.Game.Screens.Select.Leaderboards loading.Hide(); + // schedule because we may not be loaded yet (LoadComponentAsync complains). + showScoresDelegate?.Cancel(); + showScoresCancellationSource?.Cancel(); + if (scores == null || !scores.Any()) return; // ensure placeholder is hidden when displaying scores PlaceholderState = PlaceholderState.Successful; - var flow = scrollFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0f, 5f), - Padding = new MarginPadding { Top = 10, Bottom = 5 }, - ChildrenEnumerable = scores.Select((s, index) => new LeaderboardScore(s, index + 1) { Action = () => ScoreSelected?.Invoke(s) }) - }; + scrollFlow = CreateScoreFlow(); + scrollFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); - // schedule because we may not be loaded yet (LoadComponentAsync complains). - showScoresDelegate?.Cancel(); if (!IsLoaded) showScoresDelegate = Schedule(showScores); else showScores(); - void showScores() => LoadComponentAsync(flow, _ => + void showScores() => LoadComponentAsync(scrollFlow, _ => { - scrollContainer.Add(flow); + scrollContainer.Add(scrollFlow); int i = 0; - foreach (var s in flow.Children) + + foreach (var s in scrollFlow.Children) { using (s.BeginDelayedSequence(i++ * 50, true)) s.Show(); } scrollContainer.ScrollTo(0f, false); - }); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); } } - private LeaderboardScope scope; + protected virtual FillFlowContainer CreateScoreFlow() + => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0f, 5f), + Padding = new MarginPadding { Top = 10, Bottom = 5 }, + }; - public LeaderboardScope Scope + private TScope scope; + + public TScope Scope { - get { return scope; } + get => scope; set { - if (value == scope) + if (value.Equals(scope)) return; scope = value; - updateScores(); + UpdateScores(); } } @@ -119,7 +118,7 @@ namespace osu.Game.Screens.Select.Leaderboards /// protected PlaceholderState PlaceholderState { - get { return placeholderState; } + get => placeholderState; set { if (value != PlaceholderState.Successful) @@ -137,21 +136,26 @@ namespace osu.Game.Screens.Select.Leaderboards case PlaceholderState.NetworkFailure: replacePlaceholder(new RetrievalFailurePlaceholder { - OnRetry = updateScores, + OnRetry = UpdateScores, }); break; + case PlaceholderState.Unavailable: replacePlaceholder(new MessagePlaceholder(@"Leaderboards are not available for this beatmap!")); break; + case PlaceholderState.NoScores: replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; + case PlaceholderState.NotLoggedIn: replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!")); break; + case PlaceholderState.NotSupporter: replacePlaceholder(new MessagePlaceholder(@"Please invest in an osu!supporter tag to view this leaderboard!")); break; + default: replacePlaceholder(null); break; @@ -159,7 +163,7 @@ namespace osu.Game.Screens.Select.Leaderboards } } - public Leaderboard() + protected Leaderboard() { Children = new Drawable[] { @@ -176,59 +180,34 @@ namespace osu.Game.Screens.Select.Leaderboards }; } - private APIAccess api; - private BeatmapInfo beatmap; + private IAPIProvider api; private ScheduledDelegate pendingUpdateScores; - public BeatmapInfo Beatmap - { - get { return beatmap; } - set - { - if (beatmap == value) - return; - - beatmap = value; - Scores = null; - - updateScores(); - } - } - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, IBindable parentRuleset) + [BackgroundDependencyLoader(true)] + private void load(IAPIProvider api) { this.api = api; - - ruleset.BindTo(parentRuleset); - ruleset.ValueChanged += _ => updateScores(); - - if (api != null) - api.OnStateChange += handleApiStateChange; + api?.Register(this); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - - if (api != null) - api.OnStateChange -= handleApiStateChange; + api?.Unregister(this); } - private GetScoresRequest getScoresRequest; + public void RefreshScores() => UpdateScores(); - private void handleApiStateChange(APIState oldState, APIState newState) + private APIRequest getScoresRequest; + + public void APIStateChanged(IAPIProvider api, APIState state) { - if (Scope == LeaderboardScope.Local) - // No need to respond to API state change while current scope is local - return; - - if (newState == APIState.Online) - updateScores(); + if (state == APIState.Online) + UpdateScores(); } - private void updateScores() + protected void UpdateScores() { // don't display any scores or placeholder until the first Scores_Set has been called. // this avoids scope changes flickering a "no scores" placeholder before initialisation of song select is finished. @@ -240,18 +219,17 @@ namespace osu.Game.Screens.Select.Leaderboards pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { - if (Scope == LeaderboardScope.Local) - { - // TODO: get local scores from wherever here. - PlaceholderState = PlaceholderState.NoScores; - return; - } + PlaceholderState = PlaceholderState.Retrieving; + loading.Show(); - if (Beatmap?.OnlineBeatmapID == null) + getScoresRequest = FetchScores(scores => Schedule(() => { - PlaceholderState = PlaceholderState.Unavailable; + Scores = scores; + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + })); + + if (getScoresRequest == null) return; - } if (api?.IsLoggedIn != true) { @@ -259,22 +237,6 @@ namespace osu.Game.Screens.Select.Leaderboards return; } - if (Scope != LeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) - { - PlaceholderState = PlaceholderState.NotSupporter; - return; - } - - PlaceholderState = PlaceholderState.Retrieving; - loading.Show(); - - getScoresRequest = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope); - getScoresRequest.Success += r => Schedule(() => - { - Scores = r.Scores; - PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; - }); - getScoresRequest.Failure += e => Schedule(() => { if (e is OperationCanceledException) @@ -287,6 +249,13 @@ namespace osu.Game.Screens.Select.Leaderboards }); } + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. + protected abstract APIRequest FetchScores(Action> scoresCallback); + private Placeholder currentPlaceholder; private void replacePlaceholder(Placeholder placeholder) @@ -310,14 +279,18 @@ namespace osu.Game.Screens.Select.Leaderboards currentPlaceholder = placeholder; } + protected virtual bool FadeBottom => true; + protected virtual bool FadeTop => false; + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - var fadeStart = scrollContainer.Current + scrollContainer.DrawHeight; + var fadeBottom = scrollContainer.Current + scrollContainer.DrawHeight; + var fadeTop = scrollContainer.Current + LeaderboardScore.HEIGHT; if (!scrollContainer.IsScrolledToEnd()) - fadeStart -= LeaderboardScore.HEIGHT; + fadeBottom -= LeaderboardScore.HEIGHT; if (scrollFlow == null) return; @@ -327,17 +300,27 @@ namespace osu.Game.Screens.Select.Leaderboards var topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; var bottomY = topY + LeaderboardScore.HEIGHT; - if (bottomY < fadeStart) + bool requireTopFade = FadeTop && topY <= fadeTop; + bool requireBottomFade = FadeBottom && bottomY >= fadeBottom; + + if (!requireTopFade && !requireBottomFade) c.Colour = Color4.White; - else if (topY > fadeStart + LeaderboardScore.HEIGHT) + else if (topY > fadeBottom + LeaderboardScore.HEIGHT || bottomY < fadeTop - LeaderboardScore.HEIGHT) c.Colour = Color4.Transparent; else { - c.Colour = ColourInfo.GradientVertical( - Color4.White.Opacity(Math.Min(1 - (topY - fadeStart) / LeaderboardScore.HEIGHT, 1)), - Color4.White.Opacity(Math.Min(1 - (bottomY - fadeStart) / LeaderboardScore.HEIGHT, 1))); + if (bottomY - fadeBottom > 0 && FadeBottom) + c.Colour = ColourInfo.GradientVertical( + Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / LeaderboardScore.HEIGHT, 1)), + Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / LeaderboardScore.HEIGHT, 1))); + else if (FadeTop) + c.Colour = ColourInfo.GradientVertical( + Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / LeaderboardScore.HEIGHT, 1)), + Color4.White.Opacity(Math.Min(1 - (fadeTop - bottomY) / LeaderboardScore.HEIGHT, 1))); } } } + + protected abstract LeaderboardScore CreateDrawableScore(ScoreInfo model, int index); } } diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs similarity index 80% rename from osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs rename to osu.Game/Online/Leaderboards/LeaderboardScore.cs index 19363fe0f9..9840b59805 100644 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -1,51 +1,57 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Linq; -using osuTK; -using osuTK.Graphics; 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.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; -using osu.Game.Users; +using osu.Game.Scoring; +using osu.Game.Users.Drawables; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public class LeaderboardScore : OsuClickableContainer { - public static readonly float HEIGHT = 60; - public readonly int RankPosition; - public readonly Score Score; + + public const float HEIGHT = 60; private const float corner_radius = 5; private const float edge_margin = 5; private const float background_alpha = 0.25f; private const float rank_width = 30; + protected Container RankContainer { get; private set; } + + private readonly ScoreInfo score; + private Box background; private Container content; private Drawable avatar; - private DrawableRank scoreRank; + private Drawable scoreRank; private OsuSpriteText nameLabel; private GlowingSpriteText scoreLabel; - private ScoreComponentLabel maxCombo; - private ScoreComponentLabel accuracy; private Container flagBadgeContainer; private FillFlowContainer modsContainer; - public LeaderboardScore(Score score, int rank) + private List statisticsLabels; + + public LeaderboardScore(ScoreInfo score, int rank) { - Score = score; + this.score = score; RankPosition = rank; RelativeSizeAxes = Axes.X; @@ -55,6 +61,12 @@ namespace osu.Game.Screens.Select.Leaderboards [BackgroundDependencyLoader] private void load() { + var user = score.User; + + statisticsLabels = GetStatistics(score).Select(s => new ScoreComponentLabel(s)).ToList(); + + DrawableAvatar innerAvatar; + Children = new Drawable[] { new Container @@ -65,11 +77,9 @@ namespace osu.Game.Screens.Select.Leaderboards { new OsuSpriteText { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = @"Exo2.0-MediumItalic", - TextSize = 22, - // ReSharper disable once ImpureMethodCallOnReadonlyValueField + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 22, italics: true), Text = RankPosition.ToString(), }, }, @@ -102,12 +112,11 @@ namespace osu.Game.Screens.Select.Leaderboards Children = new[] { avatar = new DelayedLoadWrapper( - new Avatar(Score.User) + innerAvatar = new DrawableAvatar(user) { RelativeSizeAxes = Axes.Both, CornerRadius = corner_radius, Masking = true, - OnLoadComplete = d => d.FadeInFromZero(200), EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -128,9 +137,8 @@ namespace osu.Game.Screens.Select.Leaderboards { nameLabel = new OsuSpriteText { - Text = Score.User.Username, - Font = @"Exo2.0-BoldItalic", - TextSize = 23, + Text = user.Username, + Font = OsuFont.GetFont(size: 23, weight: FontWeight.Bold, italics: true) }, new FillFlowContainer { @@ -149,7 +157,7 @@ namespace osu.Game.Screens.Select.Leaderboards Masking = true, Children = new Drawable[] { - new DrawableFlag(Score.User?.Country) + new UpdateableFlag(user.Country) { Width = 30, RelativeSizeAxes = Axes.Y, @@ -164,11 +172,7 @@ namespace osu.Game.Screens.Select.Leaderboards Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), Margin = new MarginPadding { Left = edge_margin }, - Children = new Drawable[] - { - 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"), - }, + Children = statisticsLabels }, }, }, @@ -183,17 +187,17 @@ namespace osu.Game.Screens.Select.Leaderboards Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - scoreLabel = new GlowingSpriteText(Score.TotalScore.ToString(@"N0"), @"Venera", 23, Color4.White, OsuColour.FromHex(@"83ccfa")), - new Container + scoreLabel = new GlowingSpriteText(score.TotalScore.ToString(@"N0"), OsuFont.Numeric.With(size: 23), Color4.White, OsuColour.FromHex(@"83ccfa")), + RankContainer = new Container { Size = new Vector2(40f, 20f), Children = new[] { - scoreRank = new DrawableRank(Score.Rank) + scoreRank = new UpdateableRank(score.Rank) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(40f), + Size = new Vector2(40f) }, }, }, @@ -205,18 +209,20 @@ namespace osu.Game.Screens.Select.Leaderboards Origin = Anchor.BottomRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - ChildrenEnumerable = Score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) + ChildrenEnumerable = score.Mods.Select(mod => new ModIcon(mod) { Scale = new Vector2(0.375f) }) }, }, }, }, }, }; + + innerAvatar.OnLoadComplete += d => d.FadeInFromZero(200); } public override void Show() { - foreach (var d in new[] { avatar, nameLabel, scoreLabel, scoreRank, flagBadgeContainer, maxCombo, accuracy, modsContainer }) + foreach (var d in new[] { avatar, nameLabel, scoreLabel, scoreRank, flagBadgeContainer, modsContainer }.Concat(statisticsLabels)) d.FadeOut(); Alpha = 0; @@ -243,7 +249,7 @@ namespace osu.Game.Screens.Select.Leaderboards using (BeginDelayedSequence(50, true)) { - var drawables = new Drawable[] { flagBadgeContainer, maxCombo, accuracy, modsContainer, }; + var drawables = new Drawable[] { flagBadgeContainer, modsContainer }.Concat(statisticsLabels).ToArray(); for (int i = 0; i < drawables.Length; i++) drawables[i].FadeIn(100 + i * 50); } @@ -251,6 +257,12 @@ namespace osu.Game.Screens.Select.Leaderboards } } + protected virtual IEnumerable GetStatistics(ScoreInfo model) => new[] + { + new LeaderboardScoreStatistic(FontAwesome.Solid.Link, "Max Combo", model.MaxCombo.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)) + }; + protected override bool OnHover(HoverEvent e) { background.FadeTo(0.5f, 300, Easing.OutQuint); @@ -265,7 +277,7 @@ namespace osu.Game.Screens.Select.Leaderboards private class GlowingSpriteText : Container { - public GlowingSpriteText(string text, string font, int textSize, Color4 textColour, Color4 glowColour) + public GlowingSpriteText(string text, FontUsage font, Color4 textColour, Color4 glowColour) { AutoSizeAxes = Axes.Both; @@ -286,9 +298,7 @@ namespace osu.Game.Screens.Select.Leaderboards { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = font, - FixedWidth = true, - TextSize = textSize, + Font = font.With(fixedWidth: true), Text = text, Colour = glowColour, Shadow = false, @@ -299,9 +309,7 @@ namespace osu.Game.Screens.Select.Leaderboards { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = font, - FixedWidth = true, - TextSize = textSize, + Font = font.With(fixedWidth: true), Text = text, Colour = textColour, Shadow = false, @@ -321,11 +329,10 @@ namespace osu.Game.Screens.Select.Leaderboards public string TooltipText => name; - public ScoreComponentLabel(FontAwesome icon, string value, string name) + public ScoreComponentLabel(LeaderboardScoreStatistic statistic) { - this.name = name; - AutoSizeAxes = Axes.Y; - Width = 60; + name = statistic.Name; + AutoSizeAxes = Axes.Both; Child = content = new FillFlowContainer { @@ -347,7 +354,7 @@ namespace osu.Game.Screens.Select.Leaderboards Size = new Vector2(icon_size), Rotation = 45, Colour = OsuColour.FromHex(@"3087ac"), - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Solid.Square, Shadow = true, }, new SpriteIcon @@ -356,11 +363,11 @@ namespace osu.Game.Screens.Select.Leaderboards Origin = Anchor.Centre, Size = new Vector2(icon_size - 6), Colour = OsuColour.FromHex(@"a4edff"), - Icon = icon, + Icon = statistic.Icon, }, }, }, - new GlowingSpriteText(value, @"Exo2.0-Bold", 17, Color4.White, OsuColour.FromHex(@"83ccfa")) + new GlowingSpriteText(statistic.Value, OsuFont.GetFont(size: 17, weight: FontWeight.Bold), Color4.White, OsuColour.FromHex(@"83ccfa")) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -369,5 +376,19 @@ namespace osu.Game.Screens.Select.Leaderboards }; } } + + public class LeaderboardScoreStatistic + { + public IconUsage Icon; + public string Value; + public string Name; + + public LeaderboardScoreStatistic(IconUsage icon, string name, string value) + { + Icon = icon; + Name = name; + Value = value; + } + } } } diff --git a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs similarity index 56% rename from osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs rename to osu.Game/Online/Leaderboards/MessagePlaceholder.cs index f01a55b662..ef425dacd8 100644 --- a/osu.Game/Screens/Select/Leaderboards/MessagePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/MessagePlaceholder.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public class MessagePlaceholder : Placeholder { @@ -12,9 +12,9 @@ namespace osu.Game.Screens.Select.Leaderboards public MessagePlaceholder(string message) { - AddIcon(FontAwesome.fa_exclamation_circle, cp => + AddIcon(FontAwesome.Solid.ExclamationCircle, cp => { - cp.TextSize = TEXT_SIZE; + cp.Font = cp.Font.With(size: TEXT_SIZE); cp.Padding = new MarginPadding { Right = 10 }; }); diff --git a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs b/osu.Game/Online/Leaderboards/Placeholder.cs similarity index 70% rename from osu.Game/Screens/Select/Leaderboards/Placeholder.cs rename to osu.Game/Online/Leaderboards/Placeholder.cs index 468b43e54f..d38110a9d0 100644 --- a/osu.Game/Screens/Select/Leaderboards/Placeholder.cs +++ b/osu.Game/Online/Leaderboards/Placeholder.cs @@ -1,18 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public abstract class Placeholder : OsuTextFlowContainer, IEquatable { protected const float TEXT_SIZE = 22; protected Placeholder() - : base(cp => cp.TextSize = TEXT_SIZE) + : base(cp => cp.Font = cp.Font.With(size: TEXT_SIZE)) { Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game/Online/Leaderboards/PlaceholderState.cs b/osu.Game/Online/Leaderboards/PlaceholderState.cs new file mode 100644 index 0000000000..930e1df484 --- /dev/null +++ b/osu.Game/Online/Leaderboards/PlaceholderState.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Online.Leaderboards +{ + public enum PlaceholderState + { + Successful, + Retrieving, + NetworkFailure, + Unavailable, + NoScores, + NotLoggedIn, + NotSupporter, + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs similarity index 84% rename from osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs rename to osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs index 66a7793f7c..801f3f8ff0 100644 --- a/osu.Game/Screens/Select/Leaderboards/RetrievalFailurePlaceholder.cs +++ b/osu.Game/Online/Leaderboards/RetrievalFailurePlaceholder.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; -namespace osu.Game.Screens.Select.Leaderboards +namespace osu.Game.Online.Leaderboards { public class RetrievalFailurePlaceholder : Placeholder { @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select.Leaderboards Action = () => Action?.Invoke(), Child = icon = new SpriteIcon { - Icon = FontAwesome.fa_refresh, + Icon = FontAwesome.Solid.Sync, Size = new Vector2(TEXT_SIZE), Shadow = true, }, diff --git a/osu.Game/Online/Leaderboards/UpdateableRank.cs b/osu.Game/Online/Leaderboards/UpdateableRank.cs new file mode 100644 index 0000000000..64230a92db --- /dev/null +++ b/osu.Game/Online/Leaderboards/UpdateableRank.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Scoring; + +namespace osu.Game.Online.Leaderboards +{ + public class UpdateableRank : ModelBackedDrawable + { + public ScoreRank Rank + { + get => Model; + set => Model = value; + } + + public UpdateableRank(ScoreRank rank) + { + Rank = rank; + } + + protected override Drawable CreateDrawable(ScoreRank rank) => new DrawableRank(rank) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + }; + } +} diff --git a/osu.Game/Online/Multiplayer/GameType.cs b/osu.Game/Online/Multiplayer/GameType.cs index 750401c067..10381d93bb 100644 --- a/osu.Game/Online/Multiplayer/GameType.cs +++ b/osu.Game/Online/Multiplayer/GameType.cs @@ -1,11 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; namespace osu.Game.Online.Multiplayer @@ -13,134 +9,10 @@ namespace osu.Game.Online.Multiplayer public abstract class GameType { public abstract string Name { get; } + public abstract Drawable GetIcon(OsuColour colours, float size); - } - public class GameTypeTag : GameType - { - public override string Name => "Tag"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = FontAwesome.fa_refresh, - Size = new Vector2(size), - Colour = colours.Blue, - Shadow = false, - }; - } - } - - public class GameTypeVersus : GameType - { - public override string Name => "Versus"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new VersusRow(colours.Blue, colours.Blue, size * 0.6f) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - } - } - - public class GameTypeTagTeam : GameType - { - public override string Name => "Tag Team"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(2f), - Children = new[] - { - new SpriteIcon - { - Icon = FontAwesome.fa_refresh, - Size = new Vector2(size * 0.75f), - Colour = colours.Blue, - Shadow = false, - }, - new SpriteIcon - { - Icon = FontAwesome.fa_refresh, - Size = new Vector2(size * 0.75f), - Colour = colours.Pink, - Shadow = false, - }, - }, - }; - } - } - - public class GameTypeTeamVersus : GameType - { - public override string Name => "Team Versus"; - public override Drawable GetIcon(OsuColour colours, float size) - { - return new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(2f), - Children = new[] - { - new VersusRow(colours.Blue, colours.Pink, size * 0.5f), - new VersusRow(colours.Blue, colours.Pink, size * 0.5f), - }, - }; - } - } - - public class VersusRow : FillFlowContainer - { - public VersusRow(Color4 first, Color4 second, float size) - { - var triangleSize = new Vector2(size); - AutoSizeAxes = Axes.Both; - Spacing = new Vector2(2f, 0f); - - Children = new[] - { - new Container - { - Size = triangleSize, - Colour = first, - Children = new[] - { - new EquilateralTriangle - { - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Rotation = 90, - EdgeSmoothness = new Vector2(1f), - }, - }, - }, - new Container - { - Size = triangleSize, - Colour = second, - Children = new[] - { - new EquilateralTriangle - { - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - Rotation = -90, - EdgeSmoothness = new Vector2(1f), - }, - }, - }, - }; - } + public override int GetHashCode() => GetType().GetHashCode(); + public override bool Equals(object obj) => GetType() == obj?.GetType(); } } diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs new file mode 100644 index 0000000000..5ba5f1a415 --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTag.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Online.Multiplayer.GameTypes +{ + public class GameTypeTag : GameType + { + public override string Name => "Tag"; + + public override Drawable GetIcon(OsuColour colours, float size) + { + return new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.Sync, + Size = new Vector2(size), + Colour = colours.Blue, + Shadow = false, + }; + } + } +} diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs new file mode 100644 index 0000000000..ef0a00a9f0 --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTagTeam.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Online.Multiplayer.GameTypes +{ + public class GameTypeTagTeam : GameType + { + public override string Name => "Tag Team"; + + public override Drawable GetIcon(OsuColour colours, float size) + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2f), + Children = new[] + { + new SpriteIcon + { + Icon = FontAwesome.Solid.Sync, + Size = new Vector2(size * 0.75f), + Colour = colours.Blue, + Shadow = false, + }, + new SpriteIcon + { + Icon = FontAwesome.Solid.Sync, + Size = new Vector2(size * 0.75f), + Colour = colours.Pink, + Shadow = false, + }, + }, + }; + } + } +} diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTeamVersus.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTeamVersus.cs new file mode 100644 index 0000000000..c25bce1c71 --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTeamVersus.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Online.Multiplayer.GameTypes +{ + public class GameTypeTeamVersus : GameType + { + public override string Name => "Team Versus"; + + public override Drawable GetIcon(OsuColour colours, float size) + { + return new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2f), + Children = new[] + { + new VersusRow(colours.Blue, colours.Pink, size * 0.5f), + new VersusRow(colours.Blue, colours.Pink, size * 0.5f), + }, + }; + } + } +} diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs new file mode 100644 index 0000000000..1a3d2837ce --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeTimeshift.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Online.Multiplayer.GameTypes +{ + public class GameTypeTimeshift : GameType + { + public override string Name => "Timeshift"; + + public override Drawable GetIcon(OsuColour colours, float size) => new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Regular.Clock, + Size = new Vector2(size), + Colour = colours.Blue, + Shadow = false + }; + } +} diff --git a/osu.Game/Online/Multiplayer/GameTypes/GameTypeVersus.cs b/osu.Game/Online/Multiplayer/GameTypes/GameTypeVersus.cs new file mode 100644 index 0000000000..4640c7b361 --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameTypes/GameTypeVersus.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Graphics; + +namespace osu.Game.Online.Multiplayer.GameTypes +{ + public class GameTypeVersus : GameType + { + public override string Name => "Versus"; + + public override Drawable GetIcon(OsuColour colours, float size) + { + return new VersusRow(colours.Blue, colours.Blue, size * 0.6f) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + } + } +} diff --git a/osu.Game/Online/Multiplayer/GameTypes/VersusRow.cs b/osu.Game/Online/Multiplayer/GameTypes/VersusRow.cs new file mode 100644 index 0000000000..b6e8e4458f --- /dev/null +++ b/osu.Game/Online/Multiplayer/GameTypes/VersusRow.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Online.Multiplayer.GameTypes +{ + public class VersusRow : FillFlowContainer + { + public VersusRow(Color4 first, Color4 second, float size) + { + var triangleSize = new Vector2(size); + AutoSizeAxes = Axes.Both; + Spacing = new Vector2(2f, 0f); + + Children = new[] + { + new Container + { + Size = triangleSize, + Colour = first, + Children = new[] + { + new EquilateralTriangle + { + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Rotation = 90, + EdgeSmoothness = new Vector2(1f), + }, + }, + }, + new Container + { + Size = triangleSize, + Colour = second, + Children = new[] + { + new EquilateralTriangle + { + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Rotation = -90, + EdgeSmoothness = new Vector2(1f), + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs new file mode 100644 index 0000000000..e47d497d94 --- /dev/null +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -0,0 +1,93 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Online.Multiplayer +{ + public class PlaylistItem + { + [JsonProperty("id")] + public int ID { get; set; } + + [JsonProperty("beatmap_id")] + public int BeatmapID { get; set; } + + [JsonProperty("ruleset_id")] + public int RulesetID { get; set; } + + [JsonIgnore] + public BeatmapInfo Beatmap + { + get => beatmap; + set + { + beatmap = value; + BeatmapID = value?.OnlineBeatmapID ?? 0; + } + } + + [JsonIgnore] + public RulesetInfo Ruleset { get; set; } + + [JsonIgnore] + public readonly List AllowedMods = new List(); + + [JsonIgnore] + public readonly List RequiredMods = new List(); + + [JsonProperty("beatmap")] + private APIBeatmap apiBeatmap { get; set; } + + [JsonProperty("allowed_mods")] + private APIMod[] allowedMods + { + get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); + set => _allowedMods = value; + } + + [JsonProperty("required_mods")] + private APIMod[] requiredMods + { + get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); + set => _requiredMods = value; + } + + private BeatmapInfo beatmap; + private APIMod[] _allowedMods; + private APIMod[] _requiredMods; + + public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) + { + // If we don't have an api beatmap, the request occurred as a result of room creation, so we can query the local beatmap instead + // Todo: Is this a bug? Room creation only returns the beatmap ID + Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); + Ruleset = rulesets.GetRuleset(RulesetID); + + if (_allowedMods != null) + { + AllowedMods.Clear(); + AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _allowedMods.Any(m => m.Acronym == mod.Acronym))); + + _allowedMods = null; + } + + if (_requiredMods != null) + { + RequiredMods.Clear(); + RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _requiredMods.Any(m => m.Acronym == mod.Acronym))); + + _requiredMods = null; + } + } + + public bool ShouldSerializeID() => false; + public bool ShouldSerializeapiBeatmap() => false; + } +} diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index b076afbcdb..53089897f7 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -1,22 +1,152 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; -using osu.Framework.Configuration; -using osu.Game.Beatmaps; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Multiplayer.GameTypes; +using osu.Game.Online.Multiplayer.RoomStatuses; using osu.Game.Users; namespace osu.Game.Online.Multiplayer { public class Room { - 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>(); + [Cached] + [JsonProperty("id")] + public Bindable RoomID { get; private set; } = new Bindable(); + + [Cached] + [JsonProperty("name")] + public Bindable Name { get; private set; } = new Bindable(); + + [Cached] + [JsonProperty("host")] + public Bindable Host { get; private set; } = new Bindable(); + + [Cached] + [JsonProperty("playlist")] + public BindableList Playlist { get; private set; } = new BindableList(); + + [Cached] + [JsonIgnore] + public Bindable CurrentItem { get; private set; } = new Bindable(); + + [Cached] + [JsonProperty("channel_id")] + public Bindable ChannelId { get; private set; } = new Bindable(); + + [Cached] + [JsonIgnore] + public Bindable Duration { get; private set; } = new Bindable(TimeSpan.FromMinutes(30)); + + [Cached] + [JsonIgnore] + public Bindable MaxAttempts { get; private set; } = new Bindable(); + + [Cached] + [JsonIgnore] + public Bindable Status { get; private set; } = new Bindable(new RoomStatusOpen()); + + [Cached] + [JsonIgnore] + public Bindable Availability { get; private set; } = new Bindable(); + + [Cached] + [JsonIgnore] + public Bindable Type { get; private set; } = new Bindable(new GameTypeTimeshift()); + + [Cached] + [JsonIgnore] + public Bindable MaxParticipants { get; private set; } = new Bindable(); + + [Cached] + [JsonIgnore] + public Bindable> Participants { get; private set; } = new Bindable>(Enumerable.Empty()); + + [Cached] + public Bindable ParticipantCount { get; private set; } = new Bindable(); + + public Room() + { + Playlist.ItemsAdded += updateCurrent; + Playlist.ItemsRemoved += updateCurrent; + updateCurrent(Playlist); + } + + private void updateCurrent(IEnumerable playlist) + { + CurrentItem.Value = playlist.FirstOrDefault(); + } + + // todo: TEMPORARY + [JsonProperty("participant_count")] + private int? participantCount + { + get => ParticipantCount.Value; + set => ParticipantCount.Value = value ?? 0; + } + + [JsonProperty("duration")] + private int duration + { + get => (int)Duration.Value.TotalMinutes; + set => Duration.Value = TimeSpan.FromMinutes(value); + } + + // Only supports retrieval for now + [Cached] + [JsonProperty("ends_at")] + public Bindable EndDate { get; private set; } = new Bindable(); + + // Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930) + [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] + private int? maxAttempts + { + get => MaxAttempts.Value; + set => MaxAttempts.Value = value; + } + + /// + /// The position of this in the list. This is not read from or written to the API. + /// + [JsonIgnore] + public Bindable Position { get; private set; } = new Bindable(-1); + + public void CopyFrom(Room other) + { + RoomID.Value = other.RoomID.Value; + Name.Value = other.Name.Value; + + if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id) + Host.Value = other.Host.Value; + + ChannelId.Value = other.ChannelId.Value; + Status.Value = other.Status.Value; + Availability.Value = other.Availability.Value; + Type.Value = other.Type.Value; + MaxParticipants.Value = other.MaxParticipants.Value; + ParticipantCount.Value = other.ParticipantCount.Value; + Participants.Value = other.Participants.Value.ToArray(); + EndDate.Value = other.EndDate.Value; + + if (DateTimeOffset.Now >= EndDate.Value) + Status.Value = new RoomStatusEnded(); + + // Todo: Temporary, should only remove/add new items (requires framework changes) + if (Playlist.Count == 0) + Playlist.AddRange(other.Playlist); + else if (other.Playlist.Count > 0) + Playlist.First().ID = other.Playlist.First().ID; + + Position = other.Position; + } + + public bool ShouldSerializeRoomID() => false; + public bool ShouldSerializeHost() => false; + public bool ShouldSerializeEndDate() => false; } } diff --git a/osu.Game/Online/Multiplayer/RoomAvailability.cs b/osu.Game/Online/Multiplayer/RoomAvailability.cs index 6c154207ff..08fa853562 100644 --- a/osu.Game/Online/Multiplayer/RoomAvailability.cs +++ b/osu.Game/Online/Multiplayer/RoomAvailability.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; diff --git a/osu.Game/Online/Multiplayer/RoomStatus.cs b/osu.Game/Online/Multiplayer/RoomStatus.cs index 7c6e71a360..3ff2770ab4 100644 --- a/osu.Game/Online/Multiplayer/RoomStatus.cs +++ b/osu.Game/Online/Multiplayer/RoomStatus.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Game.Graphics; @@ -10,17 +10,8 @@ namespace osu.Game.Online.Multiplayer { public abstract string Message { get; } public abstract Color4 GetAppropriateColour(OsuColour colours); - } - public class RoomStatusOpen : RoomStatus - { - public override string Message => @"Welcoming Players"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight; - } - - public class RoomStatusPlaying : RoomStatus - { - public override string Message => @"Now Playing"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple; + public override int GetHashCode() => GetType().GetHashCode(); + public override bool Equals(object obj) => GetType() == obj?.GetType(); } } diff --git a/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusEnded.cs b/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusEnded.cs new file mode 100644 index 0000000000..4177d28a99 --- /dev/null +++ b/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusEnded.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Online.Multiplayer.RoomStatuses +{ + public class RoomStatusEnded : RoomStatus + { + public override string Message => @"Ended"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker; + } +} diff --git a/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusOpen.cs b/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusOpen.cs new file mode 100644 index 0000000000..45a1cb1909 --- /dev/null +++ b/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusOpen.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Online.Multiplayer.RoomStatuses +{ + public class RoomStatusOpen : RoomStatus + { + public override string Message => @"Welcoming Players"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight; + } +} diff --git a/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusPlaying.cs b/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusPlaying.cs new file mode 100644 index 0000000000..b2cb5c4510 --- /dev/null +++ b/osu.Game/Online/Multiplayer/RoomStatuses/RoomStatusPlaying.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Online.Multiplayer.RoomStatuses +{ + public class RoomStatusPlaying : RoomStatus + { + public override string Message => @"Now Playing"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple; + } +} diff --git a/osu.Game/Online/PollingComponent.cs b/osu.Game/Online/PollingComponent.cs new file mode 100644 index 0000000000..acbb2c39f4 --- /dev/null +++ b/osu.Game/Online/PollingComponent.cs @@ -0,0 +1,127 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Threading.Tasks; +using osu.Framework.Graphics; +using osu.Framework.Threading; + +namespace osu.Game.Online +{ + /// + /// A component which requires a constant polling process. + /// + public abstract class PollingComponent : Component + { + private double? lastTimePolled; + + private ScheduledDelegate scheduledPoll; + + private bool pollingActive; + + private double timeBetweenPolls; + + /// + /// The time in milliseconds to wait between polls. + /// Setting to zero stops all polling. + /// + public double TimeBetweenPolls + { + get => timeBetweenPolls; + set + { + timeBetweenPolls = value; + scheduledPoll?.Cancel(); + pollIfNecessary(); + } + } + + /// + /// + /// + /// The initial time in milliseconds to wait between polls. Setting to zero stops all polling. + protected PollingComponent(double timeBetweenPolls = 0) + { + TimeBetweenPolls = timeBetweenPolls; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + pollIfNecessary(); + } + + private bool pollIfNecessary() + { + // we must be loaded so we have access to clock. + if (!IsLoaded) return false; + + // there's already a poll process running. + if (pollingActive) return false; + + // don't try polling if the time between polls hasn't been set. + if (timeBetweenPolls == 0) return false; + + if (!lastTimePolled.HasValue) + { + doPoll(); + return true; + } + + if (Time.Current - lastTimePolled.Value > timeBetweenPolls) + { + doPoll(); + return true; + } + + // not ennough time has passed since the last poll. we do want to schedule a poll to happen, though. + scheduleNextPoll(); + return false; + } + + private void doPoll() + { + scheduledPoll = null; + pollingActive = true; + Poll().ContinueWith(_ => pollComplete()); + } + + /// + /// Performs a poll. Implement but do not call this. + /// + protected virtual Task Poll() + { + return Task.CompletedTask; + } + + /// + /// Immediately performs a . + /// + public void PollImmediately() + { + lastTimePolled = Time.Current - timeBetweenPolls; + scheduleNextPoll(); + } + + /// + /// Call when a poll operation has completed. + /// + private void pollComplete() + { + lastTimePolled = Time.Current; + pollingActive = false; + + if (scheduledPoll == null) + pollIfNecessary(); + } + + private void scheduleNextPoll() + { + scheduledPoll?.Cancel(); + + double lastPollDuration = lastTimePolled.HasValue ? Time.Current - lastTimePolled.Value : 0; + + scheduledPoll = Scheduler.AddDelayed(doPoll, Math.Max(0, timeBetweenPolls - lastPollDuration)); + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1b0a0b2210..f7f2e1b451 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Configuration; using osu.Framework.Screens; using osu.Game.Configuration; @@ -14,28 +15,30 @@ using osu.Framework.Allocation; using osu.Game.Overlays.Toolbar; using osu.Game.Screens; using osu.Game.Screens.Menu; -using osuTK; using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input; using osu.Game.Overlays.Notifications; -using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Input.Bindings; using osu.Game.Online.Chat; -using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using osuTK.Graphics; using osu.Game.Overlays.Volume; +using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; @@ -54,12 +57,8 @@ namespace osu.Game private ChannelManager channelManager; - private MusicController musicController; - private NotificationOverlay notifications; - private DialogOverlay dialogOverlay; - private DirectOverlay direct; private SocialOverlay social; @@ -75,41 +74,33 @@ namespace osu.Game public virtual Storage GetStorageForStableInstall() => null; - private Intro intro - { - get - { - Screen screen = screenStack; - while (screen != null && !(screen is Intro)) - screen = screen.ChildScreen; - return screen as Intro; - } - } - public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; + private IdleTracker idleTracker; + public readonly Bindable OverlayActivationMode = new Bindable(); - private OsuScreen screenStack; - + private OsuScreenStack screenStack; private VolumeOverlay volume; - private OnScreenDisplay onscreenDisplay; + private OsuLogo osuLogo; + private BackButton backButton; + + private MainMenu menuScreen; + private Intro introScreen; private Bindable configRuleset; - private readonly Bindable ruleset = new Bindable(); private Bindable configSkin; private readonly string[] args; - private SettingsOverlay settings; + private SettingsPanel settings; private readonly List overlays = new List(); - // todo: move this to SongSelect once Screen has the ability to unsuspend. - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); + private readonly List toolbarElements = new List(); + + private readonly List visibleBlockingOverlays = new List(); public OsuGame(string[] args = null) { @@ -120,19 +111,36 @@ namespace osu.Game RavenLogger = new RavenLogger(this); } - public void ToggleSettings() => settings.ToggleVisibility(); + private void updateBlockingOverlayFade() => + screenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); - public void ToggleDirect() => direct.ToggleVisibility(); + public void AddBlockingOverlay(OverlayContainer overlay) + { + if (!visibleBlockingOverlays.Contains(overlay)) + visibleBlockingOverlays.Add(overlay); + updateBlockingOverlayFade(); + } + + public void RemoveBlockingOverlay(OverlayContainer overlay) + { + visibleBlockingOverlays.Remove(overlay); + updateBlockingOverlayFade(); + } /// /// Close all game-wide overlays. /// - /// Whether the toolbar should also be hidden. - public void CloseAllOverlays(bool toolbar = true) + /// Whether the toolbar (and accompanying controls) should also be hidden. + public void CloseAllOverlays(bool hideToolbarElements = true) { foreach (var overlay in overlays) - overlay.State = Visibility.Hidden; - if (toolbar) Toolbar.State = Visibility.Hidden; + overlay.Hide(); + + if (hideToolbarElements) + { + foreach (var overlay in toolbarElements) + overlay.Hide(); + } } private DependencyContainer dependencies; @@ -145,8 +153,6 @@ namespace osu.Game { this.frameworkConfig = frameworkConfig; - ScoreStore.ScoreImported += score => Schedule(() => LoadScore(score)); - if (!Host.IsPrimaryInstance) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); @@ -164,27 +170,35 @@ namespace osu.Game dependencies.Cache(RavenLogger); - dependencies.CacheAs(ruleset); - dependencies.CacheAs>(ruleset); + dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); - ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); - ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; + Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); + Ruleset.ValueChanged += r => configRuleset.Value = r.NewValue.ID ?? 0; // bind config int to database SkinInfo configSkin = LocalConfig.GetBindable(OsuSetting.Skin); - SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID; - configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == id) ?? SkinInfo.Default; + SkinManager.CurrentSkinInfo.ValueChanged += skin => configSkin.Value = skin.NewValue.ID; + configSkin.ValueChanged += skinId => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == skinId.NewValue) ?? SkinInfo.Default; configSkin.TriggerChange(); - LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); + IsActive.BindValueChanged(active => updateActiveState(active.NewValue), true); + + Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); + + Beatmap.BindValueChanged(beatmapChanged, true); } private ExternalLinkOpener externalLinkOpener; - public void OpenUrlExternally(string url) => externalLinkOpener.OpenUrlExternally(url); - private ScheduledDelegate scoreLoad; + public void OpenUrlExternally(string url) + { + if (url.StartsWith("/")) + url = $"{API.Endpoint}{url}"; + + externalLinkOpener.OpenUrlExternally(url); + } /// /// Show a beatmap set as an overlay. @@ -192,47 +206,6 @@ namespace osu.Game /// The set to display. public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); - /// - /// Present a beatmap at song select. - /// - /// The beatmap to select. - public void PresentBeatmap(BeatmapSetInfo beatmap) - { - CloseAllOverlays(false); - - void setBeatmap() - { - if (Beatmap.Disabled) - { - Schedule(setBeatmap); - return; - } - - var databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); - - // Use first beatmap available for current ruleset, else switch ruleset. - var first = databasedSet.Beatmaps.FirstOrDefault(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); - - ruleset.Value = first.Ruleset; - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); - } - - switch (currentScreen) - { - case SongSelect _: - break; - default: - // navigate to song select if we are not already there. - var menu = (MainMenu)intro.ChildScreen; - - menu.MakeCurrent(); - menu.LoadToSolo(); - break; - } - - setBeatmap(); - } - /// /// Show a user's profile as an overlay. /// @@ -245,41 +218,143 @@ namespace osu.Game /// The beatmap to show. public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); - protected void LoadScore(Score score) + /// + /// Present a beatmap at song select immediately. + /// The user should have already requested this interactively. + /// + /// The beatmap to select. + public void PresentBeatmap(BeatmapSetInfo beatmap) { - scoreLoad?.Cancel(); + var databasedSet = beatmap.OnlineBeatmapSetID != null + ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) + : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); - var menu = intro.ChildScreen; - - if (menu == null) + if (databasedSet == null) { - scoreLoad = Schedule(() => LoadScore(score)); + Logger.Log("The requested beatmap could not be loaded.", LoggingTarget.Information); return; } - if (!menu.IsCurrentScreen) + performFromMainMenu(() => { - menu.MakeCurrent(); - this.Delay(500).Schedule(() => LoadScore(score), out scoreLoad); + // we might already be at song select, so a check is required before performing the load to solo. + if (menuScreen.IsCurrentScreen()) + menuScreen.LoadToSolo(); + + // we might even already be at the song + if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash) + { + return; + } + + // Use first beatmap available for current ruleset, else switch ruleset. + var first = databasedSet.Beatmaps.Find(b => b.Ruleset == Ruleset.Value) ?? databasedSet.Beatmaps.First(); + + Ruleset.Value = first.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); + }, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect)); + } + + /// + /// Present a score's replay immediately. + /// The user should have already requested this interactively. + /// + public void PresentScore(ScoreInfo score) + { + var databasedScore = ScoreManager.GetScore(score); + var databasedScoreInfo = databasedScore.ScoreInfo; + + if (databasedScore.Replay == null) + { + Logger.Log("The loaded score has no replay data.", LoggingTarget.Information); return; } - if (score.Beatmap == null) + var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID); + + if (databasedBeatmap == null) + { + Logger.Log("Tried to load a score for a beatmap we don't have!", LoggingTarget.Information); + return; + } + + performFromMainMenu(() => + { + Ruleset.Value = databasedScoreInfo.Ruleset; + Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); + Mods.Value = databasedScoreInfo.Mods; + + menuScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore))); + }, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true); + } + + #region Beatmap jukebox progression + + private void beatmapChanged(ValueChangedEvent beatmap) + { + var nextBeatmap = beatmap.NewValue; + if (nextBeatmap?.Track != null) + nextBeatmap.Track.Completed += currentTrackCompleted; + } + + private void currentTrackCompleted() + { + if (!Beatmap.Value.Track.Looping && !Beatmap.Disabled) + musicController.NextTrack(); + } + + #endregion + + private ScheduledDelegate performFromMainMenuTask; + + /// + /// Perform an action only after returning to the main menu. + /// Eagerly tries to exit the current screen until it succeeds. + /// + /// The action to perform once we are in the correct state. + /// The task name to display in a notification (if we can't immediately reach the main menu state). + /// An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu. + /// Whether checking should be bypassed. + private void performFromMainMenu(Action action, string taskName, Type targetScreen = null, bool bypassScreenAllowChecks = false) + { + performFromMainMenuTask?.Cancel(); + + // if the current screen does not allow screen changing, give the user an option to try again later. + if (!bypassScreenAllowChecks && (screenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false) { notifications.Post(new SimpleNotification { - Text = @"Tried to load a score for a beatmap we don't have!", - Icon = FontAwesome.fa_life_saver, + Text = $"Click here to {taskName}", + Activated = () => + { + performFromMainMenu(action, taskName, targetScreen, true); + return true; + } }); + return; } - ruleset.Value = score.Ruleset; + CloseAllOverlays(false); - Beatmap.Value = BeatmapManager.GetWorkingBeatmap(score.Beatmap); - Beatmap.Value.Mods.Value = score.Mods; + // we may already be at the target screen type. + if (targetScreen != null && screenStack.CurrentScreen?.GetType() == targetScreen) + { + action(); + return; + } - menu.Push(new PlayerLoader(new ReplayPlayer(score.Replay))); + // all conditions have been met to continue with the action. + if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled) + { + action(); + return; + } + + // menuScreen may not be initialised yet (null check required). + menuScreen?.MakeCurrent(); + + performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName)); } protected override void Dispose(bool isDisposing) @@ -295,7 +370,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 - MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false; // todo: all archive managers should be able to be looped here. SkinManager.PostNotification = n => notifications?.Post(n); @@ -303,8 +378,14 @@ namespace osu.Game BeatmapManager.PostNotification = n => notifications?.Post(n); BeatmapManager.GetStableStorage = GetStorageForStableInstall; + BeatmapManager.PresentImport = items => PresentBeatmap(items.First()); - BeatmapManager.PresentBeatmap = PresentBeatmap; + ScoreManager.PostNotification = n => notifications?.Post(n); + ScoreManager.PresentImport = items => PresentScore(items.First()); + + Container logoContainer; + + dependencies.CacheAs(idleTracker = new GameIdleTracker(6000)); AddRange(new Drawable[] { @@ -314,76 +395,104 @@ namespace osu.Game ActionRequested = action => volume.Adjust(action), ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), }, - mainContent = new Container { RelativeSizeAxes = Axes.Both }, - overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + backButton = new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = () => + { + if ((screenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) + screenStack.Exit(); + } + }, + logoContainer = new Container { RelativeSizeAxes = Axes.Both }, + } + }, + overlayContent = new Container { RelativeSizeAxes = Axes.Both }, + rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + topMostOverlayContent = new Container { RelativeSizeAxes = Axes.Both }, + idleTracker }); - loadComponentSingleFile(screenStack = new Loader(), d => + screenStack.ScreenPushed += screenPushed; + screenStack.ScreenExited += screenExited; + + loadComponentSingleFile(osuLogo, logo => { - screenStack.ModePushed += screenAdded; - screenStack.Exited += screenRemoved; - mainContent.Add(screenStack); + logoContainer.Add(logo); + + // Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering. + screenStack.Push(new Loader + { + RelativeSizeAxes = Axes.Both + }); }); loadComponentSingleFile(Toolbar = new Toolbar { - Depth = -5, OnHome = delegate { CloseAllOverlays(false); - intro?.ChildScreen?.MakeCurrent(); + menuScreen?.MakeCurrent(); }, - }, overlayContent.Add); - - loadComponentSingleFile(volume = new VolumeOverlay(), overlayContent.Add); - loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add); - - loadComponentSingleFile(screenshotManager, Add); - - //overlay elements - loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal); - loadComponentSingleFile(chatOverlay = new ChatOverlay { Depth = -1 }, mainContent.Add); - loadComponentSingleFile(settings = new MainSettings + }, d => { - GetToolbarHeight = () => ToolbarOffset, - Depth = -1 - }, overlayContent.Add); - loadComponentSingleFile(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add); - loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay { Depth = -3 }, mainContent.Add); - loadComponentSingleFile(musicController = new MusicController - { - Depth = -5, - Position = new Vector2(0, Toolbar.HEIGHT), - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, overlayContent.Add); + topMostOverlayContent.Add(d); + toolbarElements.Add(d); + }); + + loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add); + loadComponentSingleFile(new OnScreenDisplay(), Add, true); loadComponentSingleFile(notifications = new NotificationOverlay { GetToolbarHeight = () => ToolbarOffset, - Depth = -4, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, overlayContent.Add); + }, rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(dialogOverlay = new DialogOverlay + loadComponentSingleFile(screenshotManager, Add); + + //overlay elements + loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true); + loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true); + loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true); + loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true); + loadComponentSingleFile(settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true); + var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true); + loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); + loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); + + loadComponentSingleFile(new LoginOverlay { - Depth = -6, - }, overlayContent.Add); + GetToolbarHeight = () => ToolbarOffset, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, rightFloatingOverlayContent.Add, true); - dependencies.Cache(settings); - dependencies.Cache(onscreenDisplay); - dependencies.Cache(social); - dependencies.Cache(direct); - dependencies.Cache(chatOverlay); - dependencies.Cache(channelManager); - dependencies.Cache(userProfile); - dependencies.Cache(musicController); - dependencies.Cache(beatmapSetOverlay); - dependencies.Cache(notifications); - dependencies.Cache(dialogOverlay); + loadComponentSingleFile(musicController = new MusicController + { + GetToolbarHeight = () => ToolbarOffset, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, d => + { + rightFloatingOverlayContent.Add(d); + toolbarElements.Add(d); + }, true); + + loadComponentSingleFile(new AccountCreationOverlay(), topMostOverlayContent.Add, true); + loadComponentSingleFile(new DialogOverlay(), topMostOverlayContent.Add, true); + loadComponentSingleFile(externalLinkOpener = new ExternalLinkOpener(), topMostOverlayContent.Add); + + chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); @@ -392,9 +501,10 @@ namespace osu.Game foreach (var overlay in singleDisplaySideOverlays) { - overlay.StateChanged += state => + overlay.State.ValueChanged += state => { - if (state == Visibility.Hidden) return; + if (state.NewValue == Visibility.Hidden) return; + singleDisplaySideOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); }; } @@ -405,49 +515,68 @@ namespace osu.Game foreach (var overlay in informationalOverlays) { - overlay.StateChanged += state => + overlay.State.ValueChanged += state => { - if (state == Visibility.Hidden) return; + if (state.NewValue == Visibility.Hidden) return; + informationalOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); }; } // ensure only one of these overlays are open at once. - var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct }; + var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay }; overlays.AddRange(singleDisplayOverlays); foreach (var overlay in singleDisplayOverlays) { - overlay.StateChanged += state => + overlay.State.ValueChanged += state => { // informational overlays should be dismissed on a show or hide of a full overlay. informationalOverlays.ForEach(o => o.Hide()); - if (state == Visibility.Hidden) return; + if (state.NewValue == Visibility.Hidden) return; singleDisplayOverlays.Where(o => o != overlay).ForEach(o => o.Hide()); }; } - OverlayActivationMode.ValueChanged += v => + OverlayActivationMode.ValueChanged += mode => { - if (v != OverlayActivation.All) CloseAllOverlays(); + if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); }; void updateScreenOffset() { float offset = 0; - if (settings.State == Visibility.Visible) + if (settings.State.Value == Visibility.Visible) offset += ToolbarButton.WIDTH / 2; - if (notifications.State == Visibility.Visible) + if (notifications.State.Value == Visibility.Visible) offset -= ToolbarButton.WIDTH / 2; - screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); + screenContainer.MoveToX(offset, SettingsPanel.TRANSITION_LENGTH, Easing.OutQuint); } - settings.StateChanged += _ => updateScreenOffset(); - notifications.StateChanged += _ => updateScreenOffset(); + settings.State.ValueChanged += _ => updateScreenOffset(); + notifications.State.ValueChanged += _ => updateScreenOffset(); + } + + public class GameIdleTracker : IdleTracker + { + private InputManager inputManager; + + public GameIdleTracker(int time) + : base(time) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + inputManager = GetContainingInputManager(); + } + + protected override bool AllowIdle => inputManager.FocusedDrawable == null; } private void forwardLoggedErrorsToNotifications() @@ -466,7 +595,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = entry.Level == LogLevel.Important ? FontAwesome.fa_exclamation_circle : FontAwesome.fa_bomb, + Icon = entry.Level == LogLevel.Important ? FontAwesome.Solid.ExclamationCircle : FontAwesome.Solid.Bomb, Text = entry.Message + (entry.Exception != null && IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string.Empty), })); } @@ -474,7 +603,7 @@ namespace osu.Game { Schedule(() => notifications.Post(new SimpleNotification { - Icon = FontAwesome.fa_ellipsis_h, + Icon = FontAwesome.Solid.EllipsisH, Text = "Subsequent messages have been logged. Click to view log files.", Activated = () => { @@ -490,20 +619,12 @@ namespace osu.Game } private Task asyncLoadStream; - private int visibleOverlayCount; - private void loadComponentSingleFile(T d, Action add) + private T loadComponentSingleFile(T d, Action add, bool cache = false) where T : Drawable { - var focused = d as FocusedOverlayContainer; - if (focused != null) - { - focused.StateChanged += s => - { - visibleOverlayCount += s == Visibility.Visible ? 1 : -1; - screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); - }; - } + if (cache) + dependencies.Cache(d); // schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached). // with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile, @@ -520,29 +641,51 @@ namespace osu.Game try { - Logger.Log($"Loading {d}...", LoggingTarget.Debug); - await LoadComponentAsync(d, add); - Logger.Log($"Loaded {d}!", LoggingTarget.Debug); + Logger.Log($"Loading {d}...", level: LogLevel.Debug); + + // Since this is running in a separate thread, it is possible for OsuGame to be disposed after LoadComponentAsync has been called + // throwing an exception. To avoid this, the call is scheduled on the update thread, which does not run if IsDisposed = true + Task task = null; + var del = new ScheduledDelegate(() => task = LoadComponentAsync(d, add)); + Scheduler.Add(del); + + // The delegate won't complete if OsuGame has been disposed in the meantime + while (!IsDisposed && !del.Completed) + await Task.Delay(10); + + // Either we're disposed or the load process has started successfully + if (IsDisposed) + return; + + Debug.Assert(task != null); + + await task; + + Logger.Log($"Loaded {d}!", level: LogLevel.Debug); } catch (OperationCanceledException) { } }); }); + + return d; } public bool OnPressed(GlobalAction action) { - if (intro == null) return false; + if (introScreen == null) return false; switch (action) { case GlobalAction.ToggleChat: chatOverlay.ToggleVisibility(); return true; + case GlobalAction.ToggleSocial: social.ToggleVisibility(); return true; + case GlobalAction.ResetInputSettings: var sensitivity = frameworkConfig.GetBindable(FrameworkSetting.CursorSensitivity); @@ -553,15 +696,19 @@ namespace osu.Game frameworkConfig.Set(FrameworkSetting.IgnoredInputHandlers, string.Empty); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); return true; + case GlobalAction.ToggleToolbar: Toolbar.ToggleVisibility(); return true; + case GlobalAction.ToggleSettings: settings.ToggleVisibility(); return true; + case GlobalAction.ToggleDirect: direct.ToggleVisibility(); return true; + case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; @@ -570,38 +717,47 @@ namespace osu.Game return false; } - private readonly BindableDouble inactiveVolumeAdjust = new BindableDouble(); + #region Inactive audio dimming - protected override void OnDeactivated() + private readonly BindableDouble inactiveVolumeFade = new BindableDouble(); + + private void updateActiveState(bool isActive) { - base.OnDeactivated(); - Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeAdjust); + if (isActive) + this.TransformBindableTo(inactiveVolumeFade, 1, 400, Easing.OutQuint); + else + this.TransformBindableTo(inactiveVolumeFade, LocalConfig.Get(OsuSetting.VolumeInactive), 4000, Easing.OutQuint); } - protected override void OnActivated() - { - base.OnActivated(); - Audio.RemoveAdjustment(AdjustableProperty.Volume, inactiveVolumeAdjust); - } + #endregion public bool OnReleased(GlobalAction action) => false; - private Container mainContent; - private Container overlayContent; - private OsuScreen currentScreen; + private Container rightFloatingOverlayContent; + + private Container leftFloatingOverlayContent; + + private Container topMostOverlayContent; + private FrameworkConfigManager frameworkConfig; + private ScalingContainer screenContainer; + + private MusicController musicController; + protected override bool OnExiting() { - if (screenStack.ChildScreen == null) return false; + if (screenStack.CurrentScreen is Loader) + return false; - if (intro == null) return true; + if (introScreen == null) + return true; - if (!intro.DidLoadMenu || intro.ChildScreen != null) + if (!introScreen.DidLoadMenu || !(screenStack.CurrentScreen is Intro)) { - Scheduler.Add(intro.MakeCurrent); + Scheduler.Add(introScreen.MakeCurrent); return true; } @@ -624,31 +780,51 @@ namespace osu.Game { base.UpdateAfterChildren(); - // we only want to apply these restrictions when we are inside a screen stack. - // the use case for not applying is in visual/unit tests. - bool applyBeatmapRulesetRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false; + screenContainer.Padding = new MarginPadding { Top = ToolbarOffset }; + overlayContent.Padding = new MarginPadding { Top = ToolbarOffset }; - ruleset.Disabled = applyBeatmapRulesetRestrictions; - Beatmap.Disabled = applyBeatmapRulesetRestrictions; - - mainContent.Padding = new MarginPadding { Top = ToolbarOffset }; - - MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; + MenuCursorContainer.CanShowCursor = (screenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } - private void screenAdded(Screen newScreen) + protected virtual void ScreenChanged(IScreen current, IScreen newScreen) { - currentScreen = (OsuScreen)newScreen; - Logger.Log($"Screen changed → {currentScreen}"); + switch (newScreen) + { + case Intro intro: + introScreen = intro; + break; - newScreen.ModePushed += screenAdded; - newScreen.Exited += screenRemoved; + case MainMenu menu: + menuScreen = menu; + break; + } + + if (newScreen is IOsuScreen newOsuScreen) + { + OverlayActivationMode.Value = newOsuScreen.InitialOverlayActivationMode; + + if (newOsuScreen.HideOverlaysOnEnter) + CloseAllOverlays(); + else + Toolbar.Show(); + + if (newOsuScreen.AllowBackButton) + backButton.Show(); + else + backButton.Hide(); + } } - private void screenRemoved(Screen newScreen) + private void screenPushed(IScreen lastScreen, IScreen newScreen) { - currentScreen = (OsuScreen)newScreen; - Logger.Log($"Screen changed ← {currentScreen}"); + ScreenChanged(lastScreen, newScreen); + Logger.Log($"Screen changed → {newScreen}"); + } + + private void screenExited(IScreen lastScreen, IScreen newScreen) + { + ScreenChanged(lastScreen, newScreen); + Logger.Log($"Screen changed ← {newScreen}"); if (newScreen == null) Exit(); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c267c323a6..3bdf37d769 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -1,14 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Configuration; +using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.IO.Stores; @@ -24,14 +26,15 @@ using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Rulesets; -using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; using osu.Game.Skinning; using osuTK.Input; -using DebugUtils = osu.Game.Utils.DebugUtils; namespace osu.Game { @@ -42,48 +45,62 @@ namespace osu.Game /// public class OsuGameBase : Framework.Game, ICanAcceptFiles { + public const string CLIENT_STREAM_NAME = "lazer"; + protected OsuConfigManager LocalConfig; protected BeatmapManager BeatmapManager; + protected ScoreManager ScoreManager; + protected SkinManager SkinManager; protected RulesetStore RulesetStore; protected FileStore FileStore; - protected ScoreStore ScoreStore; - protected KeyBindingStore KeyBindingStore; protected SettingsStore SettingsStore; protected RulesetConfigCache RulesetConfigCache; + protected APIAccess API; + protected MenuCursorContainer MenuCursorContainer; private Container content; protected override Container Content => content; - private OsuBindableBeatmap beatmap; - protected BindableBeatmap Beatmap => beatmap; + private Bindable beatmap; // cached via load() method + + [Cached] + [Cached(typeof(IBindable))] + protected readonly Bindable Ruleset = new Bindable(); + + // todo: move this to SongSelect once Screen has the ability to unsuspend. + [Cached] + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + + protected Bindable Beatmap => beatmap; private Bindable fpsDisplayVisible; - protected AssemblyName AssemblyName => Assembly.GetEntryAssembly()?.GetName() ?? new AssemblyName { Version = new Version() }; + public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); - public bool IsDeployedBuild => AssemblyName.Version.Major > 0; + public bool IsDeployedBuild => AssemblyVersion.Major > 0; public string Version { get { if (!IsDeployedBuild) - return @"local " + (DebugUtils.IsDebug ? @"debug" : @"release"); + return @"local " + (DebugUtils.IsDebugBuild ? @"debug" : @"release"); - var assembly = AssemblyName; - return $@"{assembly.Version.Major}.{assembly.Version.Minor}.{assembly.Version.Build}"; + var version = AssemblyVersion; + return $@"{version.Major}.{version.Minor}.{version.Build}"; } } @@ -108,16 +125,13 @@ namespace osu.Game dependencies.Cache(contextFactory = new DatabaseContextFactory(Host.Storage)); - var largeStore = new LargeTextureStore(new TextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); - largeStore.AddStore(new TextureLoaderStore(new OnlineStore())); + var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore(Resources, @"Textures"))); + largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore())); dependencies.Cache(largeStore); dependencies.CacheAs(this); dependencies.Cache(LocalConfig); - //this completely overrides the framework default. will need to change once we make a proper FontStore. - dependencies.Cache(Fonts = new FontStore(new GlyphStore(Resources, @"Fonts/FontAwesome"))); - Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-Medium")); Fonts.AddStore(new GlyphStore(Resources, @"Fonts/Exo2.0-MediumItalic")); @@ -146,38 +160,52 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio)); dependencies.CacheAs(SkinManager); - var api = new APIAccess(LocalConfig); + API = new APIAccess(LocalConfig); - dependencies.Cache(api); - dependencies.CacheAs(api); + dependencies.CacheAs(API); + + var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Audio, Host)); - dependencies.Cache(ScoreStore = new ScoreStore(contextFactory, Host, BeatmapManager, RulesetStore)); + + // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Host.Storage, contextFactory, Host)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap)); + + // this should likely be moved to ArchiveModelManager when another case appers where it is necessary + // to have inter-dependent model managers. this could be obtained with an IHasForeign interface to + // allow lookups to be done on the child (ScoreManager in this case) to perform the cascading delete. + List getBeatmapScores(BeatmapSetInfo set) + { + var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList(); + return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList(); + } + + BeatmapManager.ItemRemoved += i => ScoreManager.Delete(getBeatmapScores(i), true); + BeatmapManager.ItemAdded += i => ScoreManager.Undelete(getBeatmapScores(i), true); + dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(RulesetConfigCache = new RulesetConfigCache(SettingsStore)); dependencies.Cache(new OsuColour()); fileImporters.Add(BeatmapManager); - fileImporters.Add(ScoreStore); + fileImporters.Add(ScoreManager); fileImporters.Add(SkinManager); - var defaultBeatmap = new DummyWorkingBeatmap(this); - 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)); + Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); - dependencies.CacheAs(beatmap); - dependencies.CacheAs(beatmap); + beatmap = new OsuBindableBeatmap(defaultBeatmap); + + dependencies.CacheAs>(beatmap); + dependencies.CacheAs(beatmap); FileStore.Cleanup(); - AddInternal(api); + AddInternal(API); GlobalActionContainer globalBinding; @@ -188,7 +216,7 @@ namespace osu.Game Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } }; - base.Content.Add(new DrawSizePreservingFillContainer { Child = MenuCursorContainer }); + base.Content.Add(new ScalingContainer(ScalingMode.Everything) { Child = MenuCursorContainer }); KeyBindingStore.Register(globalBinding); dependencies.Cache(globalBinding); @@ -205,8 +233,10 @@ namespace osu.Game // TODO: This is temporary until we reimplement the local FPS display. // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += val => { FrameStatisticsMode = val ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; + fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; fpsDisplayVisible.TriggerChange(); + + FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; } private void runMigrations() @@ -241,35 +271,23 @@ namespace osu.Game private readonly List fileImporters = new List(); - public void Import(params string[] paths) + public async Task Import(params string[] paths) { var extension = Path.GetExtension(paths.First())?.ToLowerInvariant(); foreach (var importer in fileImporters) - if (importer.HandledExtensions.Contains(extension)) importer.Import(paths); + if (importer.HandledExtensions.Contains(extension)) + await importer.Import(paths); } 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) + public OsuBindableBeatmap(WorkingBeatmap defaultValue) : base(defaultValue) { } - - public override BindableBeatmap GetBoundCopy() - { - var copy = new OsuBindableBeatmap(Default); - copy.BindTo(this); - return copy; - } } private class OsuUserInputManager : UserInputManager diff --git a/osu.Game/Overlays/AccountCreation/AccountCreationBackground.cs b/osu.Game/Overlays/AccountCreation/AccountCreationBackground.cs new file mode 100644 index 0000000000..2e52ca74f3 --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/AccountCreationBackground.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Overlays.AccountCreation +{ + public class AccountCreationBackground : Sprite + { + public AccountCreationBackground() + { + FillMode = FillMode.Fill; + RelativeSizeAxes = Axes.Both; + + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + Texture = textures.Get("Backgrounds/registration"); + } + + protected override void LoadComplete() + { + const float x_movement = 80; + + const float initial_move_time = 5000; + const float loop_move_time = 10000; + + base.LoadComplete(); + this.FadeInFromZero(initial_move_time / 4, Easing.OutQuint); + this.MoveToX(x_movement / 2).MoveToX(0, initial_move_time, Easing.OutQuint); + + using (BeginDelayedSequence(initial_move_time)) + { + this + .MoveToX(x_movement, loop_move_time, Easing.InOutSine) + .Then().MoveToX(0, loop_move_time, Easing.InOutSine) + .Loop(); + } + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs b/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs new file mode 100644 index 0000000000..7e2ae405cb --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/AccountCreationScreen.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Screens; + +namespace osu.Game.Overlays.AccountCreation +{ + public abstract class AccountCreationScreen : Screen + { + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + this.FadeOut().Delay(200).FadeIn(200); + } + + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + this.FadeIn(200); + } + + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + this.FadeOut(200); + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs b/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs new file mode 100644 index 0000000000..87ff4dd398 --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ErrorTextFlowContainer : OsuTextFlowContainer + { + private readonly List errorDrawables = new List(); + + public ErrorTextFlowContainer() + : base(cp => cp.Font = cp.Font.With(size: 12)) + { + } + + public void ClearErrors() + { + errorDrawables.ForEach(d => d.Expire()); + } + + public void AddErrors(string[] errors) + { + ClearErrors(); + + if (errors == null) return; + + foreach (var error in errors) + errorDrawables.AddRange(AddParagraph(error, cp => cp.Colour = Color4.Red)); + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs new file mode 100644 index 0000000000..e136fc1403 --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -0,0 +1,224 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.MathUtils; +using osu.Framework.Platform; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Overlays.Settings; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ScreenEntry : AccountCreationScreen + { + private ErrorTextFlowContainer usernameDescription; + private ErrorTextFlowContainer emailAddressDescription; + private ErrorTextFlowContainer passwordDescription; + + private OsuTextBox usernameTextBox; + private OsuTextBox emailTextBox; + private OsuPasswordTextBox passwordTextBox; + + private IAPIProvider api; + private ShakeContainer registerShake; + private IEnumerable characterCheckText; + + private OsuTextBox[] textboxes; + private ProcessingOverlay processingOverlay; + private GameHost host; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, IAPIProvider api, GameHost host) + { + this.api = api; + this.host = host; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new OsuSpriteText + { + Margin = new MarginPadding { Vertical = 10 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.GetFont(size: 20), + Text = "Let's create an account!", + }, + usernameTextBox = new OsuTextBox + { + PlaceholderText = "username", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this + }, + usernameDescription = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + emailTextBox = new OsuTextBox + { + PlaceholderText = "email address", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this + }, + emailAddressDescription = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + passwordTextBox = new OsuPasswordTextBox + { + PlaceholderText = "password", + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + }, + passwordDescription = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + registerShake = new ShakeContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new SettingsButton + { + Text = "Register", + Margin = new MarginPadding { Vertical = 20 }, + Action = performRegistration + } + } + } + }, + }, + }, + processingOverlay = new ProcessingOverlay { Alpha = 0 } + }; + + textboxes = new[] { usernameTextBox, emailTextBox, passwordTextBox }; + + usernameDescription.AddText("This will be your public presence. No profanity, no impersonation. Avoid exposing your own personal details, too!"); + + emailAddressDescription.AddText("Will be used for notifications, account verification and in the case you forget your password. No spam, ever."); + emailAddressDescription.AddText(" Make sure to get it right!", cp => cp.Font = cp.Font.With(Typeface.Exo, weight: FontWeight.Bold)); + + passwordDescription.AddText("At least "); + characterCheckText = passwordDescription.AddText("8 characters long"); + passwordDescription.AddText(". Choose something long but also something you will remember, like a line from your favourite song."); + + passwordTextBox.Current.ValueChanged += password => { characterCheckText.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; + } + + protected override void Update() + { + base.Update(); + + if (host?.OnScreenKeyboardOverlapsGameWindow != true && !textboxes.Any(t => t.HasFocus)) + focusNextTextbox(); + } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + processingOverlay.Hide(); + } + + private void performRegistration() + { + if (focusNextTextbox()) + { + registerShake.Shake(); + return; + } + + usernameDescription.ClearErrors(); + emailAddressDescription.ClearErrors(); + passwordDescription.ClearErrors(); + + processingOverlay.Show(); + + Task.Run(() => + { + bool success; + RegistrationRequest.RegistrationRequestErrors errors = null; + + try + { + errors = api.CreateAccount(emailTextBox.Text, usernameTextBox.Text, passwordTextBox.Text); + success = errors == null; + } + catch (Exception) + { + success = false; + } + + Schedule(() => + { + if (!success) + { + if (errors != null) + { + usernameDescription.AddErrors(errors.User.Username); + emailAddressDescription.AddErrors(errors.User.Email); + passwordDescription.AddErrors(errors.User.Password); + } + else + { + passwordDescription.AddErrors(new[] { "Something happened... but we're not sure what." }); + } + + registerShake.Shake(); + processingOverlay.Hide(); + return; + } + + api.Login(emailTextBox.Text, passwordTextBox.Text); + }); + }); + } + + private bool focusNextTextbox() + { + var nextTextbox = nextUnfilledTextbox(); + + if (nextTextbox != null) + { + Schedule(() => GetContainingInputManager().ChangeFocus(nextTextbox)); + return true; + } + + return false; + } + + private OsuTextBox nextUnfilledTextbox() => textboxes.FirstOrDefault(t => string.IsNullOrEmpty(t.Text)); + } +} diff --git a/osu.Game/Overlays/AccountCreation/ScreenWarning.cs b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs new file mode 100644 index 0000000000..be417f4aac --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ScreenWarning.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Overlays.Settings; +using osu.Game.Screens.Menu; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ScreenWarning : AccountCreationScreen + { + private OsuTextFlowContainer multiAccountExplanationText; + private LinkFlowContainer furtherAssistance; + private IAPIProvider api; + + private const string help_centre_url = "/help/wiki/Help_Centre#login"; + + public override void OnEntering(IScreen last) + { + if (string.IsNullOrEmpty(api.ProvidedUsername)) + { + this.FadeOut(); + this.Push(new ScreenEntry()); + return; + } + + base.OnEntering(last); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, IAPIProvider api, OsuGame game, TextureStore textures) + { + this.api = api; + + if (string.IsNullOrEmpty(api.ProvidedUsername)) + return; + + InternalChildren = new Drawable[] + { + new Sprite + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Texture = textures.Get(@"Menu/dev-build-footer"), + }, + new Sprite + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Texture = textures.Get(@"Menu/dev-build-footer"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 150, + Child = new OsuLogo + { + Scale = new Vector2(0.1f), + Anchor = Anchor.Centre, + Triangles = false, + }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Colour = Color4.Red, + Font = OsuFont.GetFont(size: 28, weight: FontWeight.Light), + Text = "Warning! 注意!", + }, + multiAccountExplanationText = new OsuTextFlowContainer(cp => cp.Font = cp.Font.With(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + new SettingsButton + { + Text = "Help, I can't access my account!", + Margin = new MarginPadding { Top = 50 }, + Action = () => game?.OpenUrlExternally(help_centre_url) + }, + new DangerousSettingsButton + { + Text = "I understand. This account isn't for me.", + Action = () => this.Push(new ScreenEntry()) + }, + furtherAssistance = new LinkFlowContainer(cp => cp.Font = cp.Font.With(size: 12)) + { + Margin = new MarginPadding { Top = 20 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both + }, + } + } + }; + + multiAccountExplanationText.AddText("Are you "); + multiAccountExplanationText.AddText(api.ProvidedUsername, cp => cp.Colour = colours.BlueLight); + multiAccountExplanationText.AddText("? osu! has a policy of "); + multiAccountExplanationText.AddText("one account per person!", cp => cp.Colour = colours.Yellow); + multiAccountExplanationText.AddText(" Please be aware that creating more than one account per person may result in "); + multiAccountExplanationText.AddText("permanent deactivation of accounts", cp => cp.Colour = colours.Yellow); + multiAccountExplanationText.AddText("."); + + furtherAssistance.AddText("Need further assistance? Contact us via our "); + furtherAssistance.AddLink("support system", help_centre_url); + furtherAssistance.AddText("."); + } + } +} diff --git a/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs b/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs new file mode 100644 index 0000000000..f6ddb135ec --- /dev/null +++ b/osu.Game/Overlays/AccountCreation/ScreenWelcome.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Overlays.AccountCreation +{ + public class ScreenWelcome : AccountCreationScreen + { + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Padding = new MarginPadding(20), + Spacing = new Vector2(0, 5), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 150, + Child = new OsuLogo + { + Scale = new Vector2(0.1f), + Anchor = Anchor.Centre, + Triangles = false, + }, + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), + Text = "New Player Registration", + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.GetFont(size: 12), + Text = "let's get you started", + }, + new SettingsButton + { + Text = "Let's create an account!", + Margin = new MarginPadding { Vertical = 120 }, + Action = () => this.Push(new ScreenWarning()) + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs new file mode 100644 index 0000000000..89d8cbde11 --- /dev/null +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -0,0 +1,117 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Overlays.AccountCreation; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public class AccountCreationOverlay : OsuFocusedOverlayContainer, IOnlineComponent + { + private const float transition_time = 400; + + private ScreenWelcome welcomeScreen; + + public AccountCreationOverlay() + { + Size = new Vector2(620, 450); + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, IAPIProvider api) + { + api.Register(this); + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = 5, + Colour = Color4.Black.Opacity(0.2f), + }, + Masking = true, + CornerRadius = 10, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + new DelayedLoadWrapper(new AccountCreationBackground(), 0), + new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.6f, + AutoSizeDuration = transition_time, + AutoSizeEasing = Easing.OutQuint, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.9f, + }, + new ScreenStack(welcomeScreen = new ScreenWelcome()) + { + RelativeSizeAxes = Axes.Both, + }, + } + } + } + } + }; + } + + protected override void PopIn() + { + base.PopIn(); + this.FadeIn(transition_time, Easing.OutQuint); + + if (welcomeScreen.GetChildScreen() != null) + welcomeScreen.MakeCurrent(); + } + + protected override void PopOut() + { + base.PopOut(); + this.FadeOut(100); + } + + public void APIStateChanged(IAPIProvider api, APIState state) + { + switch (state) + { + case APIState.Offline: + case APIState.Failing: + break; + + case APIState.Connecting: + break; + + case APIState.Online: + Hide(); + break; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index c2f03a4b66..096e91b65b 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -1,17 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Users; +using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Game.Graphics.Containers; -using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Overlays.BeatmapSet { @@ -20,17 +20,17 @@ namespace osu.Game.Overlays.BeatmapSet private const float height = 50; private readonly UpdateableAvatar avatar; - private readonly ClickableArea clickableArea; private readonly FillFlowContainer fields; private BeatmapSetInfo beatmapSet; public BeatmapSetInfo BeatmapSet { - get { return beatmapSet; } + get => beatmapSet; set { if (value == beatmapSet) return; + beatmapSet = value; updateDisplay(); @@ -49,8 +49,8 @@ namespace osu.Game.Overlays.BeatmapSet fields.Children = new Drawable[] { - new Field("mapped by", BeatmapSet.Metadata.Author.Username, @"Exo2.0-RegularItalic"), - new Field("submitted on", online.Submitted.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold") + new Field("mapped by", BeatmapSet.Metadata.Author.Username, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), + new Field("submitted on", online.Submitted.ToString(@"MMMM d, yyyy"), OsuFont.GetFont(weight: FontWeight.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(@"MMMM d, yyyy"), @"Exo2.0-Bold")); + fields.Add(new Field("ranked on", online.Ranked.Value.ToString(@"MMMM d, yyyy"), OsuFont.GetFont(weight: FontWeight.Bold))); } else if (online.LastUpdated.HasValue) { - fields.Add(new Field("last updated on", online.LastUpdated.Value.ToString(@"MMMM d, yyyy"), @"Exo2.0-Bold")); + fields.Add(new Field("last updated on", online.LastUpdated.Value.ToString(@"MMMM d, yyyy"), OsuFont.GetFont(weight: FontWeight.Bold))); } } @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.BeatmapSet Children = new Drawable[] { - clickableArea = new ClickableArea + new Container { AutoSizeAxes = Axes.Both, CornerRadius = 3, @@ -100,20 +100,14 @@ namespace osu.Game.Overlays.BeatmapSet }; } - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) + private void load() { - clickableArea.Action = () => - { - if (avatar.User != null) profile?.ShowUser(avatar.User); - }; - updateDisplay(); } private class Field : FillFlowContainer { - public Field(string first, string second, string secondFont) + public Field(string first, string second, FontUsage secondFont) { AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; @@ -123,21 +117,15 @@ namespace osu.Game.Overlays.BeatmapSet new OsuSpriteText { Text = $"{first} ", - TextSize = 13, + Font = OsuFont.GetFont(size: 13) }, new OsuSpriteText { Text = second, - TextSize = 13, - Font = secondFont, + Font = secondFont.With(size: 13) }, }; } } - - private class ClickableArea : OsuClickableContainer, IHasTooltip - { - public string TooltipText => @"View Profile"; - } } } diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 26402b6e37..6a583baf38 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -21,10 +22,11 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapSetInfo BeatmapSet { - get { return beatmapSet; } + get => beatmapSet; set { if (value == beatmapSet) return; + beatmapSet = value; updateDisplay(); @@ -35,10 +37,11 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapInfo Beatmap { - get { return beatmap; } + get => beatmap; set { if (value == beatmap) return; + beatmap = value; updateDisplay(); @@ -47,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - bpm.Value = BeatmapSet?.OnlineInfo.BPM.ToString(@"0.##") ?? "-"; + bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToString(@"0.##") ?? "-"; if (beatmap == null) { @@ -72,10 +75,10 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Horizontal, Children = new[] { - length = new Statistic(FontAwesome.fa_clock_o, "Length") { Width = 0.25f }, - bpm = new Statistic(FontAwesome.fa_circle, "BPM") { Width = 0.25f }, - circleCount = new Statistic(FontAwesome.fa_circle_o, "Circle Count") { Width = 0.25f }, - sliderCount = new Statistic(FontAwesome.fa_circle, "Slider Count") { Width = 0.25f }, + length = new Statistic(FontAwesome.Regular.Clock, "Length") { Width = 0.25f }, + bpm = new Statistic(FontAwesome.Regular.Circle, "BPM") { Width = 0.25f }, + circleCount = new Statistic(FontAwesome.Regular.Circle, "Circle Count") { Width = 0.25f }, + sliderCount = new Statistic(FontAwesome.Regular.Circle, "Slider Count") { Width = 0.25f }, }, }; } @@ -95,11 +98,11 @@ namespace osu.Game.Overlays.BeatmapSet public string Value { - get { return value.Text; } - set { this.value.Text = value; } + get => value.Text; + set => this.value.Text = value; } - public Statistic(FontAwesome icon, string name) + public Statistic(IconUsage icon, string name) { this.name = name; RelativeSizeAxes = Axes.X; @@ -118,7 +121,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.CentreLeft, Origin = Anchor.Centre, - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Solid.Square, Size = new Vector2(13), Rotation = 45, Colour = OsuColour.FromHex(@"441288"), @@ -136,9 +139,8 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextSize = 13, - Font = @"Exo2.0-Bold", Margin = new MarginPadding { Left = 10 }, + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold), }, }, }, diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs b/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs new file mode 100644 index 0000000000..896c646552 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapAvailability : Container + { + private BeatmapSetInfo beatmapSet; + + private bool downloadDisabled => BeatmapSet?.OnlineInfo.Availability?.DownloadDisabled ?? false; + private bool hasExternalLink => !string.IsNullOrEmpty(BeatmapSet?.OnlineInfo.Availability?.ExternalLink); + + private readonly LinkFlowContainer textContainer; + + public BeatmapAvailability() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Padding = new MarginPadding { Top = 10, Right = 20 }; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.6f), + }, + textContainer = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 14)) + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + }, + }; + } + + public BeatmapSetInfo BeatmapSet + { + get => beatmapSet; + + set + { + if (value == beatmapSet) + return; + + beatmapSet = value; + + if (downloadDisabled || hasExternalLink) + { + Show(); + updateText(); + } + else + Hide(); + } + } + + private void updateText() + { + textContainer.Clear(); + textContainer.AddParagraph(downloadDisabled + ? "This beatmap is currently not available for download." + : "Portions of this beatmap have been removed at the request of the creator or a third-party rights holder.", t => t.Colour = Color4.Orange); + + if (hasExternalLink) + { + textContainer.NewParagraph(); + textContainer.NewParagraph(); + textContainer.AddLink("Check here for more information.", BeatmapSet.OnlineInfo.Availability.ExternalLink, creationParameters: t => t.Font = OsuFont.GetFont(size: 10)); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 64a8a1062c..baf702eebc 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -1,15 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -33,12 +34,14 @@ namespace osu.Game.Overlays.BeatmapSet public readonly Bindable Beatmap = new Bindable(); private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet { - get { return beatmapSet; } + get => beatmapSet; set { if (value == beatmapSet) return; + beatmapSet = value; updateDisplay(); @@ -107,15 +110,13 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - TextSize = 20, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold) }, starRating = new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - TextSize = 13, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold), Text = "Star Difficulty", Alpha = 0, Margin = new MarginPadding { Bottom = 1 }, @@ -130,8 +131,8 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 5 }, Children = new[] { - plays = new Statistic(FontAwesome.fa_play_circle), - favourites = new Statistic(FontAwesome.fa_heart), + plays = new Statistic(FontAwesome.Solid.PlayCircle), + favourites = new Statistic(FontAwesome.Solid.Heart), }, }, }, @@ -140,7 +141,7 @@ namespace osu.Game.Overlays.BeatmapSet Beatmap.ValueChanged += b => { - showBeatmap(b); + showBeatmap(b.NewValue); updateDifficultyButtons(); }; } @@ -196,12 +197,14 @@ namespace osu.Game.Overlays.BeatmapSet public event Action StateChanged; private DifficultySelectorState state; + public DifficultySelectorState State { - get { return state; } + get => state; set { if (value == state) return; + state = value; StateChanged?.Invoke(State); @@ -279,9 +282,10 @@ namespace osu.Game.Overlays.BeatmapSet private readonly OsuSpriteText text; private int value; + public int Value { - get { return value; } + get => value; set { this.value = value; @@ -289,7 +293,7 @@ namespace osu.Game.Overlays.BeatmapSet } } - public Statistic(FontAwesome icon) + public Statistic(IconUsage icon) { AutoSizeAxes = Axes.Both; Direction = FillDirection.Horizontal; @@ -309,8 +313,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = @"Exo2.0-SemiBoldItalic", - TextSize = 14, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) }, }; } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs index 8406dada44..3e8a5a8324 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/DownloadButton.cs @@ -1,112 +1,167 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online; using osu.Game.Online.API; +using osu.Game.Overlays.Direct; using osu.Game.Users; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Buttons { - public class DownloadButton : HeaderButton, IHasTooltip + public class DownloadButton : BeatmapDownloadTrackingComposite, IHasTooltip { - public string TooltipText => Enabled ? null : "You gotta be an osu!supporter to download for now 'yo"; + private readonly bool noVideo; + + public string TooltipText => button.Enabled.Value ? "Download this beatmap" : "Login to download"; private readonly IBindable localUser = new Bindable(); - public DownloadButton(BeatmapSetInfo set, bool noVideo = false) - { - Width = 120; + private ShakeContainer shakeContainer; + private HeaderButton button; - BeatmapSetDownloader downloader; - Add(new Container + public DownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) + : base(beatmapSet) + { + this.noVideo = noVideo; + + Width = 120; + RelativeSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(IAPIProvider api, BeatmapManager beatmaps) + { + FillFlowContainer textSprites; + + AddRangeInternal(new Drawable[] { - Depth = -1, - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 10 }, - Children = new Drawable[] + shakeContainer = new ShakeContainer { - downloader = new BeatmapSetDownloader(set, noVideo), - new FillFlowContainer + Depth = -1, + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new[] + button = new HeaderButton { RelativeSizeAxes = Axes.Both }, + new Container { - new OsuSpriteText + // cannot nest inside here due to the structure of button (putting things in its own content). + // requires framework fix. + Padding = new MarginPadding { Horizontal = 10 }, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Text = "Download", - TextSize = 13, - Font = @"Exo2.0-Bold", - }, - new OsuSpriteText - { - Text = set.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty, - TextSize = 11, - Font = @"Exo2.0-Bold", - }, + textSprites = new FillFlowContainer + { + Depth = -1, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Direction = FillDirection.Vertical, + }, + new SpriteIcon + { + Depth = -1, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Icon = FontAwesome.Solid.Download, + Size = new Vector2(16), + Margin = new MarginPadding { Right = 5 }, + }, + } + }, + new DownloadProgressBar(BeatmapSet.Value) + { + Depth = -2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, }, - }, - new SpriteIcon - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Icon = FontAwesome.fa_download, - Size = new Vector2(16), - Margin = new MarginPadding { Right = 5 }, }, }, }); - Action = () => + button.Action = () => { - if (downloader.DownloadState.Value == BeatmapSetDownloader.DownloadStatus.Downloading) + if (State.Value != DownloadState.NotDownloaded) { - Content.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine); + shakeContainer.Shake(); return; } - downloader.Download(); + beatmaps.Download(BeatmapSet.Value, noVideo); }; - downloader.DownloadState.ValueChanged += state => + localUser.BindTo(api.LocalUser); + localUser.BindValueChanged(userChanged, true); + button.Enabled.BindValueChanged(enabledChanged, true); + + State.BindValueChanged(state => { - switch (state) + switch (state.NewValue) { - case BeatmapSetDownloader.DownloadStatus.Downloaded: + case DownloadState.Downloading: + textSprites.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Downloading...", + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold) + }, + }; + break; + + case DownloadState.Downloaded: + textSprites.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Importing...", + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold) + }, + }; + break; + + case DownloadState.LocallyAvailable: this.FadeOut(200); break; - case BeatmapSetDownloader.DownloadStatus.NotDownloaded: + + case DownloadState.NotDownloaded: + textSprites.Children = new Drawable[] + { + new OsuSpriteText + { + Text = "Download", + Font = OsuFont.GetFont(size: 13, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = BeatmapSet.Value.OnlineInfo.HasVideo && noVideo ? "without Video" : string.Empty, + Font = OsuFont.GetFont(size: 11, weight: FontWeight.Bold) + }, + }; this.FadeIn(200); break; } - }; + }, true); } - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - localUser.BindTo(api.LocalUser); - localUser.BindValueChanged(userChanged, true); - Enabled.BindValueChanged(enabledChanged, true); - } + private void userChanged(ValueChangedEvent e) => button.Enabled.Value = !(e.NewValue is GuestUser); - private void userChanged(User user) => Enabled.Value = user.IsSupporter; - - private void enabledChanged(bool enabled) => this.FadeColour(enabled ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + private void enabledChanged(ValueChangedEvent e) => this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 929f89482c..7207739646 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osuTK; @@ -47,23 +48,23 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_heart_o, + Icon = FontAwesome.Regular.Heart, Size = new Vector2(18), Shadow = false, }, }); - Favourited.ValueChanged += value => + Favourited.ValueChanged += favourited => { - if (value) + if (favourited.NewValue) { pink.FadeIn(200); - icon.Icon = FontAwesome.fa_heart; + icon.Icon = FontAwesome.Solid.Heart; } else { pink.FadeOut(200); - icon.Icon = FontAwesome.fa_heart_o; + icon.Icon = FontAwesome.Regular.Heart; } }; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderButton.cs index b46b5d2a0e..6de1d3fca7 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index b971403443..8c884e0950 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,8 +30,8 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons public BeatmapSetInfo BeatmapSet { - get { return playButton.BeatmapSet; } - set { playButton.BeatmapSet = value; } + get => playButton.BeatmapSet; + set => playButton.BeatmapSet = value; } public PreviewButton() @@ -67,7 +67,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons }; Action = () => playButton.Click(); - Playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100); + Playing.ValueChanged += playing => progress.FadeTo(playing.NewValue ? 1 : 0, 100); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 709d163eaa..d76f6a43db 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -16,22 +16,25 @@ namespace osu.Game.Overlays.BeatmapSet { public class Details : FillFlowContainer { + protected readonly UserRatings Ratings; + private readonly PreviewButton preview; private readonly BasicStats basic; private readonly AdvancedStats advanced; - private readonly UserRatings ratings; private BeatmapSetInfo beatmapSet; public BeatmapSetInfo BeatmapSet { - get { return beatmapSet; } + get => beatmapSet; set { if (value == beatmapSet) return; + beatmapSet = value; basic.BeatmapSet = preview.BeatmapSet = BeatmapSet; + updateDisplay(); } } @@ -39,19 +42,18 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapInfo Beatmap { - get { return beatmap; } + get => beatmap; set { if (value == beatmap) return; basic.Beatmap = advanced.Beatmap = beatmap = value; - updateDisplay(); } } private void updateDisplay() { - ratings.Metrics = Beatmap?.Metrics; + Ratings.Metrics = BeatmapSet?.Metrics; } public Details() @@ -86,7 +88,7 @@ namespace osu.Game.Overlays.BeatmapSet }, new DetailBox { - Child = ratings = new UserRatings + Child = Ratings = new UserRatings { RelativeSizeAxes = Axes.X, Height = 95, diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index d353522d8d..1c1167d08e 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -1,24 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -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.Online; using osu.Game.Overlays.BeatmapSet.Buttons; +using osu.Game.Overlays.Direct; using osuTK; using osuTK.Graphics; +using DownloadButton = osu.Game.Overlays.BeatmapSet.Buttons.DownloadButton; namespace osu.Game.Overlays.BeatmapSet { - public class Header : Container + public class Header : BeatmapDownloadTrackingComposite { private const float transition_duration = 200; private const float tabs_height = 50; @@ -28,76 +32,30 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Box tabsBg; private readonly UpdateableBeatmapSetCover cover; private readonly OsuSpriteText title, artist; - private readonly Container noVideoButtons; - private readonly FillFlowContainer videoButtons; private readonly AuthorInfo author; - private readonly Container downloadButtonsContainer; + private readonly FillFlowContainer downloadButtonsContainer; + private readonly BeatmapAvailability beatmapAvailability; private readonly BeatmapSetOnlineStatusPill onlineStatusPill; public Details Details; + public bool DownloadButtonsVisible => downloadButtonsContainer.Any(); + public readonly BeatmapPicker Picker; - private BeatmapSetInfo beatmapSet; private readonly FavouriteButton favouriteButton; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; + private readonly FillFlowContainer fadeContent; - Picker.BeatmapSet = author.BeatmapSet = Details.BeatmapSet = BeatmapSet; - - updateDisplay(); - } - } - - private void updateDisplay() - { - title.Text = BeatmapSet?.Metadata.Title ?? string.Empty; - artist.Text = BeatmapSet?.Metadata.Artist ?? string.Empty; - onlineStatusPill.Status = BeatmapSet?.OnlineInfo.Status ?? BeatmapSetOnlineStatus.None; - cover.BeatmapSet = BeatmapSet; - - if (BeatmapSet != null) - { - downloadButtonsContainer.FadeIn(transition_duration); - favouriteButton.FadeIn(transition_duration); - - if (BeatmapSet.OnlineInfo.HasVideo) - { - 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 - { - downloadButtonsContainer.FadeOut(transition_duration); - favouriteButton.FadeOut(transition_duration); - } - } + private readonly LoadingAnimation loading; public Header() { ExternalLinkButton externalLink; + RelativeSizeAxes = Axes.X; - Height = 400; + AutoSizeAxes = Axes.Y; Masking = true; + EdgeEffect = new EdgeEffectParameters { Colour = Color4.Black.Opacity(0.25f), @@ -105,7 +63,8 @@ namespace osu.Game.Overlays.BeatmapSet Radius = 3, Offset = new Vector2(0f, 1f), }; - Children = new Drawable[] + + InternalChildren = new Drawable[] { new Container { @@ -121,7 +80,8 @@ namespace osu.Game.Overlays.BeatmapSet }, new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Top = tabs_height }, Children = new Drawable[] { @@ -133,6 +93,7 @@ namespace osu.Game.Overlays.BeatmapSet cover = new UpdateableBeatmapSetCover { RelativeSizeAxes = Axes.Both, + Masking = true, }, new Box { @@ -143,89 +104,89 @@ namespace osu.Game.Overlays.BeatmapSet }, new Container { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 20, Bottom = 30, Horizontal = BeatmapSetOverlay.X_PADDING }, - Child = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + Top = 20, + Bottom = 30, + Left = BeatmapSetOverlay.X_PADDING, + Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH, + }, + Children = new Drawable[] + { + fadeContent = new FillFlowContainer { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 113, - Child = Picker = new BeatmapPicker(), - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + new Container { - 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 - { - Font = @"Exo2.0-SemiBoldItalic", - TextSize = 25, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 20 }, - Child = author = new AuthorInfo(), - }, - new Container - { - RelativeSizeAxes = Axes.X, - Height = buttons_height, - Margin = new MarginPadding { Top = 10 }, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Picker = new BeatmapPicker(), + }, + new FillFlowContainer { - favouriteButton = new FavouriteButton(), - downloadButtonsContainer = new Container + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = buttons_height + buttons_spacing }, - Children = new Drawable[] + title = new OsuSpriteText { - noVideoButtons = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0f, - }, - videoButtons = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Spacing = new Vector2(buttons_spacing), - Alpha = 0f, - }, + Font = OsuFont.GetFont(size: 37, weight: FontWeight.Bold, italics: true) + }, + 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 { Font = OsuFont.GetFont(size: 25, weight: FontWeight.SemiBold, italics: true) }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 20 }, + Child = author = new AuthorInfo(), + }, + beatmapAvailability = new BeatmapAvailability(), + new Container + { + RelativeSizeAxes = Axes.X, + Height = buttons_height, + Margin = new MarginPadding { Top = 10 }, + Children = new Drawable[] + { + favouriteButton = new FavouriteButton(), + downloadButtonsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = buttons_height + buttons_spacing }, + Spacing = new Vector2(buttons_spacing), }, }, }, }, }, - }, + } + }, + loading = new LoadingAnimation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(1.5f), }, new FillFlowContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Right = BeatmapSetOverlay.X_PADDING }, + Margin = new MarginPadding { Top = BeatmapSetOverlay.TOP_PADDING, Right = BeatmapSetOverlay.X_PADDING }, Direction = FillDirection.Vertical, Spacing = new Vector2(10), Children = new Drawable[] @@ -244,15 +205,88 @@ 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}"; + Picker.Beatmap.ValueChanged += b => + { + Details.Beatmap = b.NewValue; + externalLink.Link = $@"https://osu.ppy.sh/beatmapsets/{BeatmapSet.Value?.OnlineBeatmapSetID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineBeatmapID}"; + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { tabsBg.Colour = colours.Gray3; - updateDisplay(); + + State.BindValueChanged(_ => updateDownloadButtons()); + + BeatmapSet.BindValueChanged(setInfo => + { + Picker.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; + cover.BeatmapSet = setInfo.NewValue; + + if (setInfo.NewValue == null) + { + onlineStatusPill.FadeTo(0.5f, 500, Easing.OutQuint); + fadeContent.Hide(); + + loading.Show(); + + downloadButtonsContainer.FadeOut(transition_duration); + favouriteButton.FadeOut(transition_duration); + } + else + { + fadeContent.FadeIn(500, Easing.OutQuint); + + loading.Hide(); + + title.Text = setInfo.NewValue.Metadata.Title ?? string.Empty; + artist.Text = setInfo.NewValue.Metadata.Artist ?? string.Empty; + + onlineStatusPill.FadeIn(500, Easing.OutQuint); + onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; + + downloadButtonsContainer.FadeIn(transition_duration); + favouriteButton.FadeIn(transition_duration); + + updateDownloadButtons(); + } + }, true); + } + + private void updateDownloadButtons() + { + if (BeatmapSet.Value == null) return; + + if (BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false) + { + downloadButtonsContainer.Clear(); + return; + } + + switch (State.Value) + { + case DownloadState.LocallyAvailable: + // temporary for UX until new design is implemented. + downloadButtonsContainer.Child = new Direct.DownloadButton(BeatmapSet.Value) + { + Width = 50, + RelativeSizeAxes = Axes.Y + }; + break; + + case DownloadState.Downloading: + case DownloadState.Downloaded: + // temporary to avoid showing two buttons for maps with novideo. will be fixed in new beatmap overlay design. + downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); + break; + + default: + downloadButtonsContainer.Child = new DownloadButton(BeatmapSet.Value); + if (BeatmapSet.Value.OnlineInfo.HasVideo) + downloadButtonsContainer.Add(new DownloadButton(BeatmapSet.Value, true)); + break; + } } } } diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index e985da0eb7..44827f0a0c 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -1,10 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -21,37 +23,20 @@ namespace osu.Game.Overlays.BeatmapSet private const float metadata_width = 225; private const float spacing = 20; - private readonly MetadataSection source, tags; private readonly Box successRateBackground; private readonly SuccessRate successRate; - private BeatmapSetInfo beatmapSet; - public BeatmapSetInfo BeatmapSet - { - get { return beatmapSet; } - set - { - if (value == beatmapSet) return; - beatmapSet = value; - - updateDisplay(); - } - } - - private void updateDisplay() - { - source.Text = BeatmapSet?.Metadata.Source ?? string.Empty; - tags.Text = BeatmapSet?.Metadata.Tags ?? string.Empty; - } + public readonly Bindable BeatmapSet = new Bindable(); public BeatmapInfo Beatmap { - get { return successRate.Beatmap; } - set { successRate.Beatmap = value; } + get => successRate.Beatmap; + set => successRate.Beatmap = value; } public Info() { + MetadataSection source, tags; RelativeSizeAxes = Axes.X; Height = 220; Masking = true; @@ -129,14 +114,18 @@ namespace osu.Game.Overlays.BeatmapSet }, }, }; + + BeatmapSet.ValueChanged += b => + { + source.Text = b.NewValue?.Metadata.Source ?? string.Empty; + tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; + }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { successRateBackground.Colour = colours.GrayE; - - updateDisplay(); } private class MetadataSection : FillFlowContainer @@ -156,14 +145,14 @@ namespace osu.Game.Overlays.BeatmapSet this.FadeIn(transition_duration); textFlow.Clear(); - textFlow.AddText(value, s => s.TextSize = 14); + textFlow.AddText(value, s => s.Font = s.Font.With(size: 14)); } } public Color4 TextColour { - get { return textFlow.Colour; } - set { textFlow.Colour = value; } + get => textFlow.Colour; + set => textFlow.Colour = value; } public MetadataSection(string title) @@ -177,8 +166,7 @@ namespace osu.Game.Overlays.BeatmapSet header = new OsuSpriteText { Text = title, - Font = @"Exo2.0-Bold", - TextSize = 14, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), Shadow = false, Margin = new MarginPadding { Top = 20 }, }, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs b/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs deleted file mode 100644 index 0be83db39e..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/ClickableUsername.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class ClickableUsername : OsuHoverContainer - { - private readonly OsuSpriteText text; - private UserProfileOverlay profile; - - private User user; - public User User - { - get { return user; } - set - { - if (user == value) return; - user = value; - - text.Text = user.Username; - } - } - - public float TextSize - { - set - { - if (text.TextSize == value) return; - text.TextSize = value; - } - get { return text.TextSize; } - } - - public ClickableUsername() - { - AutoSizeAxes = Axes.Both; - Child = text = new OsuSpriteText - { - Font = @"Exo2.0-BoldItalic", - }; - } - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile) - { - this.profile = profile; - } - - protected override bool OnClick(ClickEvent e) - { - profile?.ShowUser(user); - return true; - } - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs deleted file mode 100644 index b172954c43..0000000000 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableScore.cs +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osuTK; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; - -namespace osu.Game.Overlays.BeatmapSet.Scores -{ - public class DrawableScore : Container - { - private const int fade_duration = 100; - private const float side_margin = 20; - - private readonly Box background; - - public DrawableScore(int index, APIScore score) - { - ScoreModsContainer modsContainer; - - RelativeSizeAxes = Axes.X; - Height = 30; - CornerRadius = 3; - Masking = true; - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = $"#{index + 1}", - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding { Left = side_margin } - }, - new DrawableFlag(score.User.Country) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - Margin = new MarginPadding { Left = 60 } - }, - new ClickableUsername - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - User = score.User, - Margin = new MarginPadding { Left = 100 } - }, - modsContainer = new ScoreModsContainer - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Width = 0.06f, - RelativePositionAxes = Axes.X, - X = 0.42f - }, - new DrawableRank(score.Rank) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(30, 20), - FillMode = FillMode.Fit, - RelativePositionAxes = Axes.X, - X = 0.55f - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = $@"{score.TotalScore:N0}", - Font = @"Venera", - RelativePositionAxes = Axes.X, - X = 0.75f, - FixedWidth = true, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreRight, - Text = $@"{score.Accuracy:P2}", - Font = @"Exo2.0-RegularItalic", - RelativePositionAxes = Axes.X, - X = 0.85f - }, - new OsuSpriteText - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Text = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}", - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding { Right = side_margin } - }, - }; - - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.35f), - }); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Gray4; - } - - protected override bool OnHover(HoverEvent e) - { - background.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - background.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - - protected override bool OnClick(ClickEvent e) => true; - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs index aa785385ba..8e806c6747 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/DrawableTopScore.cs @@ -1,242 +1,125 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Users; +using osu.Game.Scoring; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class DrawableTopScore : Container + public class DrawableTopScore : CompositeDrawable { private const float fade_duration = 100; - private const float height = 200; - private const float avatar_size = 80; - private const float margin = 10; + + private Color4 backgroundIdleColour; + private Color4 backgroundHoveredColour; private readonly Box background; - private readonly Box bottomBackground; - private readonly Box middleLine; - private readonly UpdateableAvatar avatar; - private readonly DrawableFlag flag; - private readonly ClickableUsername username; - private readonly OsuSpriteText rankText; - private readonly OsuSpriteText date; - private readonly DrawableRank rank; - private readonly InfoColumn totalScore; - private readonly InfoColumn accuracy; - private readonly InfoColumn statistics; - private readonly ScoreModsContainer modsContainer; - - private APIScore score; - public APIScore Score - { - get { return score; } - set - { - if (score == value) return; - score = value; - - avatar.User = username.User = score.User; - flag.Country = score.User.Country; - date.Text = $@"achieved {score.Date:MMM d, yyyy}"; - rank.UpdateRank(score.Rank); - - totalScore.Value = $@"{score.TotalScore:N0}"; - accuracy.Value = $@"{score.Accuracy:P2}"; - statistics.Value = $"{score.Statistics[HitResult.Great]}/{score.Statistics[HitResult.Good]}/{score.Statistics[HitResult.Meh]}"; - - modsContainer.Clear(); - foreach (Mod mod in score.Mods) - modsContainer.Add(new ModIcon(mod) - { - AutoSizeAxes = Axes.Both, - Scale = new Vector2(0.45f), - }); - } - } + private readonly TopScoreUserSection userSection; + private readonly TopScoreStatisticsSection statisticsSection; public DrawableTopScore() { RelativeSizeAxes = Axes.X; - Height = height; - CornerRadius = 5; - BorderThickness = 4; + AutoSizeAxes = Axes.Y; + Masking = true; - Children = new Drawable[] + CornerRadius = 10; + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.2f), + Radius = 1, + Offset = new Vector2(0, 1), + }; + + InternalChildren = new Drawable[] { background = new Box { RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, //used for correct border representation - }, - avatar = new UpdateableAvatar - { - Size = new Vector2(avatar_size), - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Offset = new Vector2(0, 2), - Radius = 1, - }, - Margin = new MarginPadding { Top = margin, Left = margin } - }, - flag = new DrawableFlag - { - Size = new Vector2(30, 20), - Position = new Vector2(margin * 2 + avatar_size, height / 4), - }, - username = new ClickableUsername - { - Origin = Anchor.BottomLeft, - TextSize = 30, - Position = new Vector2(margin * 2 + avatar_size, height / 4), - Margin = new MarginPadding { Bottom = 4 } - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.BottomRight, - Text = "#1", - TextSize = 40, - Font = @"Exo2.0-BoldItalic", - Y = height / 4, - Margin = new MarginPadding { Right = margin } - }, - date = new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = height / 4, - Margin = new MarginPadding { Right = margin } }, new Container { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.Both, - Height = 0.5f, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), Children = new Drawable[] { - bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, - middleLine = new Box + new AutoSizingGrid { RelativeSizeAxes = Axes.X, - Height = 1, - }, - rank = new DrawableRank(ScoreRank.F) - { - Origin = Anchor.BottomLeft, - Size = new Vector2(avatar_size, 40), - FillMode = FillMode.Fit, - Y = height / 4, - Margin = new MarginPadding { Left = margin } - }, - new FillFlowContainer - { - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Position = new Vector2(height / 2, height / 4), - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new[] + Content = new[] { - totalScore = new InfoColumn("Score"), - accuracy = new InfoColumn("Accuracy"), - statistics = new InfoColumn("300/100/50"), + new Drawable[] + { + userSection = new TopScoreUserSection + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + null, + statisticsSection = new TopScoreStatisticsSection + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } + }, }, - }, - modsContainer = new ScoreModsContainer - { - AutoSizeAxes = Axes.Y, - Width = 80, - Position = new Vector2(height / 2, height / 4), + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Absolute, 20) }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, } } - }, + } }; } [BackgroundDependencyLoader] private void load(OsuColour colours) { - background.Colour = bottomBackground.Colour = colours.Gray4; - middleLine.Colour = colours.Gray2; - date.Colour = colours.Gray9; - BorderColour = rankText.Colour = colours.Yellow; + backgroundIdleColour = colours.Gray3; + backgroundHoveredColour = colours.Gray4; + + background.Colour = backgroundIdleColour; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + userSection.Score = value; + statisticsSection.Score = value; + } } protected override bool OnHover(HoverEvent e) { - background.FadeIn(fade_duration, Easing.OutQuint); + background.FadeColour(backgroundHoveredColour, fade_duration, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - background.FadeOut(fade_duration, Easing.OutQuint); + background.FadeColour(backgroundIdleColour, fade_duration, Easing.OutQuint); base.OnHoverLost(e); } - private class InfoColumn : FillFlowContainer + private class AutoSizingGrid : GridContainer { - private readonly OsuSpriteText headerText; - private readonly OsuSpriteText valueText; - - public string Value + public AutoSizingGrid() { - set - { - if (valueText.Text == value) - return; - valueText.Text = value; - } - get { return valueText.Text; } - } - - public InfoColumn(string header) - { - AutoSizeAxes = Axes.Both; - Direction = FillDirection.Vertical; - Spacing = new Vector2(0, 3); - Children = new Drawable[] - { - headerText = new OsuSpriteText - { - TextSize = 14, - Text = header, - Font = @"Exo2.0-Bold", - }, - valueText = new OsuSpriteText - { - TextSize = 25, - Font = @"Exo2.0-RegularItalic", - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - headerText.Colour = colours.Gray9; + AutoSizeAxes = Axes.Y; } } } diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs new file mode 100644 index 0000000000..15816be327 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -0,0 +1,196 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osu.Game.Users.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + private Color4 highAccuracyColour; + + public ScoreTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + highAccuracyColour = colours.GreenLight; + } + + public IReadOnlyList Scores + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value == null || !value.Any()) + return; + + for (int i = 0; i < value.Count; i++) + backgroundFlow.Add(new ScoreTableRowBackground(i)); + + Columns = createHeaders(value[0]); + Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders(ScoreInfo score) + { + var columns = new List + { + new TableColumn("rank", Anchor.CentreRight, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("", Anchor.Centre, new Dimension(GridSizeMode.Absolute, 70)), // grade + new TableColumn("score", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("accuracy", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("player", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 150)), + new TableColumn("max combo", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 90)) + }; + + foreach (var statistic in score.Statistics) + columns.Add(new TableColumn(statistic.Key.GetDescription(), Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 50, maxSize: 70))); + + columns.AddRange(new[] + { + new TableColumn("pp", Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 40, maxSize: 70)), + new TableColumn("mods", Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), + }); + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, ScoreInfo score) + { + var content = new List + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new UpdateableRank(score.Rank) + { + Size = new Vector2(30, 20) + }, + new OsuSpriteText + { + Margin = new MarginPadding { Right = horizontal_inset }, + Text = $@"{score.TotalScore:N0}", + Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) + }, + new OsuSpriteText + { + Margin = new MarginPadding { Right = horizontal_inset }, + Text = $@"{score.Accuracy:P2}", + Font = OsuFont.GetFont(size: text_size), + Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White + }, + }; + + var username = new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: text_size)) { AutoSizeAxes = Axes.Both }; + username.AddUserLink(score.User); + + content.AddRange(new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Right = horizontal_inset }, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new UpdateableFlag(score.User.Country) + { + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + username + } + }, + new OsuSpriteText + { + Text = $@"{score.MaxCombo:N0}x", + Font = OsuFont.GetFont(size: text_size) + } + }); + + foreach (var kvp in score.Statistics) + { + content.Add(new OsuSpriteText + { + Text = $"{kvp.Value}", + Font = OsuFont.GetFont(size: text_size), + Colour = kvp.Value == 0 ? Color4.Gray : Color4.White + }); + } + + content.AddRange(new Drawable[] + { + new OsuSpriteText + { + Text = $@"{score.PP:N0}", + Font = OsuFont.GetFont(size: text_size) + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + ChildrenEnumerable = score.Mods.Select(m => new ModIcon(m) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f) + }) + }, + }); + + return content.ToArray(); + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs new file mode 100644 index 0000000000..d820f4d89d --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTableRowBackground.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class ScoreTableRowBackground : CompositeDrawable + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + private readonly Box background; + + public ScoreTableRowBackground(int index) + { + RelativeSizeAxes = Axes.X; + Height = 25; + + CornerRadius = 3; + Masking = true; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + + if (index % 2 != 0) + background.Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Gray4; + background.Colour = colours.Gray3; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 38107c047d..3e6c938802 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -1,40 +1,93 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Allocation; 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; +using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osuTK; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; namespace osu.Game.Overlays.BeatmapSet.Scores { - public class ScoresContainer : Container + public class ScoresContainer : CompositeDrawable { private const int spacing = 15; private const int fade_duration = 200; - private readonly FillFlowContainer flow; + private readonly Box background; + private readonly ScoreTable scoreTable; + private readonly DrawableTopScore topScore; private readonly LoadingAnimation loadingAnimation; + [Resolved] + private IAPIProvider api { get; set; } + + public ScoresContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Width = 0.95f, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, spacing), + Margin = new MarginPadding { Vertical = spacing }, + Children = new Drawable[] + { + topScore = new DrawableTopScore(), + scoreTable = new ScoreTable + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } + }, + loadingAnimation = new LoadingAnimation + { + Alpha = 0, + Margin = new MarginPadding(20) + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray2; + updateDisplay(); + } + private bool loading { set => loadingAnimation.FadeTo(value ? 1 : 0, fade_duration); } - private IEnumerable scores; - private BeatmapInfo beatmap; + private GetScoresRequest getScoresRequest; + private IReadOnlyList scores; - public IEnumerable Scores + public IReadOnlyList Scores { - get { return scores; } + get => scores; set { getScoresRequest?.Cancel(); @@ -44,8 +97,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private GetScoresRequest getScoresRequest; - private APIAccess api; + private BeatmapInfo beatmap; public BeatmapInfo Beatmap { @@ -71,68 +123,16 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { loading = false; - var scoreCount = scores?.Count() ?? 0; + scoreTable.Scores = scores?.Count > 1 ? scores : new List(); + scoreTable.FadeTo(scores?.Count > 1 ? 1 : 0); - if (scoreCount == 0) + if (scores?.Any() == true) { - topScore.Hide(); - flow.Clear(); - return; + topScore.Score = scores.FirstOrDefault(); + topScore.Show(); } - - topScore.Score = scores.FirstOrDefault(); - topScore.Show(); - - flow.Clear(); - - if (scoreCount < 2) - return; - - for (int i = 1; i < scoreCount; i++) - flow.Add(new DrawableScore(i, scores.ElementAt(i))); - } - - public ScoresContainer() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.95f, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, spacing), - Margin = new MarginPadding { Vertical = spacing }, - Children = new Drawable[] - { - topScore = new DrawableTopScore(), - flow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 1), - }, - } - }, - loadingAnimation = new LoadingAnimation - { - Alpha = 0, - Margin = new MarginPadding(20) - }, - }; - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - updateDisplay(); + else + topScore.Hide(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs new file mode 100644 index 0000000000..6761d0f710 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -0,0 +1,204 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class TopScoreStatisticsSection : CompositeDrawable + { + private const float margin = 10; + + private readonly FontUsage smallFont = OsuFont.GetFont(size: 20); + private readonly FontUsage largeFont = OsuFont.GetFont(size: 25); + + private readonly TextColumn totalScoreColumn; + private readonly TextColumn accuracyColumn; + private readonly TextColumn maxComboColumn; + private readonly TextColumn ppColumn; + + private readonly FillFlowContainer statisticsColumns; + private readonly ModsInfoColumn modsColumn; + + public TopScoreStatisticsSection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + statisticsColumns = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + }, + ppColumn = new TextColumn("pp", smallFont), + modsColumn = new ModsInfoColumn(), + } + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(margin, 0), + Children = new Drawable[] + { + totalScoreColumn = new TextColumn("total score", largeFont), + accuracyColumn = new TextColumn("accuracy", largeFont), + maxComboColumn = new TextColumn("max combo", largeFont) + } + }, + } + }; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + totalScoreColumn.Text = $@"{value.TotalScore:N0}"; + accuracyColumn.Text = $@"{value.Accuracy:P2}"; + maxComboColumn.Text = $@"{value.MaxCombo:N0}x"; + ppColumn.Text = $@"{value.PP:N0}"; + + statisticsColumns.ChildrenEnumerable = value.Statistics.Select(kvp => createStatisticsColumn(kvp.Key, kvp.Value)); + modsColumn.Mods = value.Mods; + } + } + + private TextColumn createStatisticsColumn(HitResult hitResult, int count) => new TextColumn(hitResult.GetDescription(), smallFont) + { + Text = count.ToString() + }; + + private class InfoColumn : CompositeDrawable + { + private readonly Box separator; + + public InfoColumn(string title, Drawable content) + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2), + Children = new[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black), + Text = title.ToUpper() + }, + separator = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2 + }, + content + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + separator.Colour = colours.Gray5; + } + } + + private class TextColumn : InfoColumn + { + private readonly SpriteText text; + + public TextColumn(string title, FontUsage font) + : this(title, new OsuSpriteText { Font = font }) + { + } + + private TextColumn(string title, SpriteText text) + : base(title, text) + { + this.text = text; + } + + public LocalisedString Text + { + set => text.Text = value; + } + } + + private class ModsInfoColumn : InfoColumn + { + private readonly FillFlowContainer modsContainer; + + public ModsInfoColumn() + : this(new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal + }) + { + } + + private ModsInfoColumn(FillFlowContainer modsContainer) + : base("mods", modsContainer) + { + this.modsContainer = modsContainer; + } + + public IEnumerable Mods + { + set + { + modsContainer.Clear(); + + foreach (Mod mod in value) + { + modsContainer.Add(new ModIcon(mod) + { + AutoSizeAxes = Axes.Both, + Scale = new Vector2(0.3f), + }); + } + } + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs new file mode 100644 index 0000000000..1d9c4e7fc8 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -0,0 +1,130 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; +using osu.Game.Users.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.BeatmapSet.Scores +{ + public class TopScoreUserSection : CompositeDrawable + { + private readonly SpriteText rankText; + private readonly UpdateableRank rank; + private readonly UpdateableAvatar avatar; + private readonly LinkFlowContainer usernameText; + private readonly SpriteText date; + private readonly UpdateableFlag flag; + + public TopScoreUserSection() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + rankText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "#1", + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold, italics: true) + }, + rank = new UpdateableRank(ScoreRank.D) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(40), + FillMode = FillMode.Fit, + }, + avatar = new UpdateableAvatar(hideImmediately: true) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(80), + Masking = true, + CornerRadius = 5, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Colour = Color4.Black.Opacity(0.25f), + Offset = new Vector2(0, 2), + Radius = 1, + }, + ShowGuestOnNull = false, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + Children = new Drawable[] + { + usernameText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold, italics: true)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + }, + date = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) + }, + flag = new UpdateableFlag(hideImmediately: true) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(20, 13), + ShowPlaceholderOnNull = false, + }, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + rankText.Colour = colours.Yellow; + } + + /// + /// Sets the score to be displayed. + /// + public ScoreInfo Score + { + set + { + avatar.User = value.User; + flag.Country = value.User.Country; + date.Text = $@"achieved {value.Date.Humanize()}"; + + usernameText.Clear(); + usernameText.AddUserLink(value.User); + + rank.Rank = value.Rank; + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index 6572e520bd..0258a0301a 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -14,19 +14,22 @@ namespace osu.Game.Overlays.BeatmapSet { public class SuccessRate : Container { + protected readonly FailRetryGraph Graph; + private readonly FillFlowContainer header; private readonly OsuSpriteText successRateLabel, successPercent, graphLabel; private readonly Bar successRate; private readonly Container percentContainer; - private readonly FailRetryGraph graph; private BeatmapInfo beatmap; + public BeatmapInfo Beatmap { - get { return beatmap; } + get => beatmap; set { if (value == beatmap) return; + beatmap = value; updateDisplay(); @@ -35,15 +38,15 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - int passCount = beatmap?.OnlineInfo.PassCount ?? 0; - int playCount = beatmap?.OnlineInfo.PlayCount ?? 0; + int passCount = beatmap?.OnlineInfo?.PassCount ?? 0; + int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0; var rate = playCount != 0 ? (float)passCount / playCount : 0; successPercent.Text = rate.ToString("P0"); successRate.Length = rate; percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); - graph.Metrics = beatmap?.Metrics; + Graph.Metrics = beatmap?.Metrics; } public SuccessRate() @@ -62,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Success Rate", - TextSize = 13, + Font = OsuFont.GetFont(size: 13) }, successRate = new Bar { @@ -79,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet { Anchor = Anchor.TopRight, Origin = Anchor.TopCentre, - TextSize = 13, + Font = OsuFont.GetFont(size: 13), }, }, graphLabel = new OsuSpriteText @@ -87,12 +90,12 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Points of Failure", - TextSize = 13, + Font = OsuFont.GetFont(size: 13), Margin = new MarginPadding { Vertical = 20 }, }, }, }, - graph = new FailRetryGraph + Graph = new FailRetryGraph { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -115,7 +118,7 @@ namespace osu.Game.Overlays.BeatmapSet { base.UpdateAfterChildren(); - graph.Padding = new MarginPadding { Top = header.DrawHeight }; + Graph.Padding = new MarginPadding { Top = header.DrawHeight }; } } } diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 6294851415..19f6a3f692 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -11,69 +11,36 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { - public class BeatmapSetOverlay : WaveOverlayContainer + public class BeatmapSetOverlay : FullscreenOverlay { private const int fade_duration = 300; public const float X_PADDING = 40; + public const float TOP_PADDING = 25; public const float RIGHT_WIDTH = 275; - private readonly Header header; - private readonly Info info; + protected readonly Header Header; - private APIAccess api; private RulesetStore rulesets; - private readonly ScrollContainer scroll; - - private BeatmapSetInfo beatmapSet; - - public BeatmapSetInfo BeatmapSet - { - get => beatmapSet; - set - { - if (value == beatmapSet) - return; - - header.BeatmapSet = info.BeatmapSet = beatmapSet = value; - } - } + private readonly Bindable beatmapSet = new Bindable(); // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; public BeatmapSetOverlay() { + OsuScrollContainer scroll; + Info info; ScoresContainer scores; - Waves.FirstWaveColour = OsuColour.Gray(0.4f); - Waves.SecondWaveColour = OsuColour.Gray(0.3f); - Waves.ThirdWaveColour = OsuColour.Gray(0.2f); - Waves.FourthWaveColour = OsuColour.Gray(0.1f); - - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - RelativeSizeAxes = Axes.Both; - Width = 0.85f; - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0), - Type = EdgeEffectType.Shadow, - Radius = 3, - Offset = new Vector2(0f, 1f), - }; Children = new Drawable[] { @@ -82,7 +49,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.2f) }, - scroll = new ScrollContainer + scroll = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, @@ -93,7 +60,7 @@ namespace osu.Game.Overlays Direction = FillDirection.Vertical, Children = new Drawable[] { - header = new Header(), + Header = new Header(), info = new Info(), scores = new ScoresContainer(), }, @@ -101,65 +68,66 @@ namespace osu.Game.Overlays }, }; - header.Picker.Beatmap.ValueChanged += b => + Header.BeatmapSet.BindTo(beatmapSet); + info.BeatmapSet.BindTo(beatmapSet); + + Header.Picker.Beatmap.ValueChanged += b => { - info.Beatmap = b; - scores.Beatmap = b; + info.Beatmap = b.NewValue; + scores.Beatmap = b.NewValue; + + scroll.ScrollToStart(); }; } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(RulesetStore rulesets) { - this.api = api; this.rulesets = rulesets; } - protected override void PopIn() + protected override void PopOutComplete() { - base.PopIn(); - FadeEdgeEffectTo(0.25f, WaveContainer.APPEAR_DURATION, Easing.In); - } - - protected override void PopOut() - { - base.PopOut(); - FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out).OnComplete(_ => BeatmapSet = null); + base.PopOutComplete(); + beatmapSet.Value = null; } protected override bool OnClick(ClickEvent e) { - State = Visibility.Hidden; + Hide(); return true; } public void FetchAndShowBeatmap(int beatmapId) { - BeatmapSet = null; + beatmapSet.Value = null; var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => { - BeatmapSet = res.ToBeatmapSet(rulesets); - header.Picker.Beatmap.Value = header.BeatmapSet.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); + beatmapSet.Value = res.ToBeatmapSet(rulesets); + Header.Picker.Beatmap.Value = Header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); }; - api.Queue(req); + API.Queue(req); Show(); } public void FetchAndShowBeatmapSet(int beatmapSetId) { - BeatmapSet = null; + beatmapSet.Value = null; var req = new GetBeatmapSetRequest(beatmapSetId); - req.Success += res => BeatmapSet = res.ToBeatmapSet(rulesets); - api.Queue(req); + req.Success += res => beatmapSet.Value = res.ToBeatmapSet(rulesets); + API.Queue(req); Show(); } + /// + /// Show an already fully-populated beatmap set. + /// + /// The set to show. public void ShowBeatmapSet(BeatmapSetInfo set) { - BeatmapSet = set; + beatmapSet.Value = set; Show(); - scroll.ScrollTo(0); } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs new file mode 100644 index 0000000000..3d145af562 --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -0,0 +1,207 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using System; +using System.Linq; +using System.Text.RegularExpressions; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using osuTK.Graphics; +using osu.Framework.Allocation; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogBuild : FillFlowContainer + { + public const float HORIZONTAL_PADDING = 70; + + public Action SelectBuild; + + protected readonly APIChangelogBuild Build; + + public readonly FillFlowContainer ChangelogEntries; + + public ChangelogBuild(APIChangelogBuild build) + { + Build = build; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Padding = new MarginPadding { Horizontal = HORIZONTAL_PADDING }; + + Children = new Drawable[] + { + CreateHeader(), + ChangelogEntries = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + foreach (var categoryEntries in Build.ChangelogEntries.GroupBy(b => b.Category).OrderBy(c => c.Key)) + { + ChangelogEntries.Add(new OsuSpriteText + { + Text = categoryEntries.Key, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 24), + Margin = new MarginPadding { Top = 35, Bottom = 15 }, + }); + + var fontLarge = OsuFont.GetFont(size: 18); + var fontMedium = OsuFont.GetFont(size: 14); + var fontSmall = OsuFont.GetFont(size: 12); + + foreach (APIChangelogEntry entry in categoryEntries) + { + LinkFlowContainer title = new LinkFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Vertical = 5 }, + }; + + var entryColour = entry.Major ? colours.YellowLight : Color4.White; + + title.AddIcon(FontAwesome.Solid.Check, t => + { + t.Font = fontSmall; + t.Colour = entryColour; + t.Padding = new MarginPadding { Left = -17, Right = 5 }; + }); + + title.AddText(entry.Title, t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + + if (!string.IsNullOrEmpty(entry.Repository)) + { + title.AddText(" (", t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External, + creationParameters: t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + title.AddText(")", t => + { + t.Font = fontLarge; + t.Colour = entryColour; + }); + } + + title.AddText(" by ", t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + + if (entry.GithubUser.UserId != null) + title.AddUserLink(new User + { + Username = entry.GithubUser.OsuUsername, + Id = entry.GithubUser.UserId.Value + }, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + else if (entry.GithubUser.GithubUrl != null) + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t => + { + t.Font = fontMedium; + t.Colour = entryColour; + }); + else + title.AddText(entry.GithubUser.DisplayName, t => + { + t.Font = fontSmall; + t.Colour = entryColour; + }); + + ChangelogEntries.Add(title); + + if (!string.IsNullOrEmpty(entry.MessageHtml)) + { + TextFlowContainer message = new TextFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }; + + // todo: use markdown parsing once API returns markdown + message.AddText(Regex.Replace(entry.MessageHtml, @"<(.|\n)*?>", string.Empty), t => + { + t.Font = fontSmall; + t.Colour = new Color4(235, 184, 254, 255); + }); + + ChangelogEntries.Add(message); + } + } + } + } + + protected virtual FillFlowContainer CreateHeader() => new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Top = 20 }, + Children = new Drawable[] + { + new OsuHoverContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Action = () => SelectBuild?.Invoke(Build), + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Horizontal = 40 }, + Children = new[] + { + new OsuSpriteText + { + Text = Build.UpdateStream.DisplayName, + Font = OsuFont.GetFont(weight: FontWeight.Medium, size: 19), + }, + new OsuSpriteText + { + Text = " ", + Font = OsuFont.GetFont(weight: FontWeight.Medium, size: 19), + }, + new OsuSpriteText + { + Text = Build.DisplayVersion, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 19), + Colour = Build.UpdateStream.Colour, + }, + } + } + }, + } + }; + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogContent.cs b/osu.Game/Overlays/Changelog/ChangelogContent.cs new file mode 100644 index 0000000000..f8d5bbd66c --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogContent.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using System; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogContent : FillFlowContainer + { + public Action BuildSelected; + + public void SelectBuild(APIChangelogBuild build) => BuildSelected?.Invoke(build); + + public ChangelogContent() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Padding = new MarginPadding { Bottom = 100 }; + } + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs new file mode 100644 index 0000000000..fca62fbb44 --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -0,0 +1,170 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogHeader : OverlayHeader + { + public readonly Bindable Current = new Bindable(); + + public Action ListingSelected; + + public UpdateStreamBadgeArea Streams; + + private const string listing_string = "Listing"; + + public ChangelogHeader() + { + TabControl.AddItem(listing_string); + TabControl.Current.ValueChanged += e => + { + if (e.NewValue == listing_string) + ListingSelected?.Invoke(); + }; + + Current.ValueChanged += showBuild; + + Streams.Current.ValueChanged += e => + { + if (e.NewValue?.LatestBuild != null && e.NewValue != Current.Value?.UpdateStream) + Current.Value = e.NewValue.LatestBuild; + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + TabControl.AccentColour = colours.Violet; + } + + private ChangelogHeaderTitle title; + + private void showBuild(ValueChangedEvent e) + { + if (e.OldValue != null) + TabControl.RemoveItem(e.OldValue.ToString()); + + if (e.NewValue != null) + { + TabControl.AddItem(e.NewValue.ToString()); + TabControl.Current.Value = e.NewValue.ToString(); + + Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name); + + title.Version = e.NewValue.UpdateStream.DisplayName; + } + else + { + TabControl.Current.Value = listing_string; + Streams.Current.Value = null; + title.Version = null; + } + } + + protected override Drawable CreateBackground() => new HeaderBackground(); + + protected override Drawable CreateContent() => new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + Streams = new UpdateStreamBadgeArea(), + } + }; + + protected override ScreenTitle CreateTitle() => title = new ChangelogHeaderTitle(); + + public class HeaderBackground : Sprite + { + public HeaderBackground() + { + RelativeSizeAxes = Axes.Both; + FillMode = FillMode.Fill; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Texture = textures.Get(@"Headers/changelog"); + } + } + + private class ChangelogHeaderTitle : ScreenTitle + { + public string Version + { + set => Section = value ?? listing_string; + } + + public ChangelogHeaderTitle() + { + Title = "Changelog"; + Version = null; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Violet; + } + + protected override Drawable CreateIcon() => new ChangelogIcon(); + + internal class ChangelogIcon : CompositeDrawable + { + private const float circle_allowance = 0.8f; + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + Size = new Vector2(ICON_SIZE / circle_allowance); + + InternalChildren = new Drawable[] + { + new CircularContainer + { + Masking = true, + BorderColour = colours.Violet, + BorderThickness = 3, + MaskingSmoothness = 1, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(@"Icons/changelog"), + Size = new Vector2(circle_allowance), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Violet, + Alpha = 0, + AlwaysPresent = true, + }, + } + }, + }; + } + } + } + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogListing.cs b/osu.Game/Overlays/Changelog/ChangelogListing.cs new file mode 100644 index 0000000000..41d8228475 --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogListing.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogListing : ChangelogContent + { + private readonly List entries; + + public ChangelogListing(List entries) + { + this.entries = entries; + } + + [BackgroundDependencyLoader] + private void load() + { + DateTime currentDate = DateTime.MinValue; + + if (entries == null) return; + + foreach (APIChangelogBuild build in entries) + { + if (build.CreatedAt.Date != currentDate) + { + if (Children.Count != 0) + { + Add(new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Colour = new Color4(17, 17, 17, 255), + Margin = new MarginPadding { Top = 30 }, + }); + } + + Add(new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 15 }, + Text = build.CreatedAt.Date.ToString("dd MMM yyyy"), + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 24), + Colour = OsuColour.FromHex(@"FD5"), + }); + + currentDate = build.CreatedAt.Date; + } + else + { + Add(new Container + { + RelativeSizeAxes = Axes.X, + Height = 1, + Padding = new MarginPadding { Horizontal = ChangelogBuild.HORIZONTAL_PADDING }, + Margin = new MarginPadding { Top = 30 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new Color4(32, 24, 35, 255), + } + }); + } + + Add(new ChangelogBuild(build) { SelectBuild = SelectBuild }); + } + } + } +} diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs new file mode 100644 index 0000000000..36ae5a756c --- /dev/null +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -0,0 +1,142 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Overlays.Changelog +{ + public class ChangelogSingleBuild : ChangelogContent + { + private APIChangelogBuild build; + + public ChangelogSingleBuild(APIChangelogBuild build) + { + this.build = build; + } + + [BackgroundDependencyLoader] + private void load(CancellationToken? cancellation, IAPIProvider api) + { + bool complete = false; + + var req = new GetChangelogBuildRequest(build.UpdateStream.Name, build.Version); + req.Success += res => + { + build = res; + complete = true; + }; + req.Failure += _ => complete = true; + + // This is done on a separate thread to support cancellation below + Task.Run(() => req.Perform(api)); + + while (!complete) + { + if (cancellation?.IsCancellationRequested == true) + { + req.Cancel(); + return; + } + + Thread.Sleep(10); + } + + if (build != null) + Child = new ChangelogBuildWithNavigation(build) { SelectBuild = SelectBuild }; + } + + public class ChangelogBuildWithNavigation : ChangelogBuild + { + public ChangelogBuildWithNavigation(APIChangelogBuild build) + : base(build) + { + } + + protected override FillFlowContainer CreateHeader() + { + var fill = base.CreateHeader(); + + foreach (var existing in fill.Children.OfType()) + { + existing.Scale = new Vector2(1.25f); + existing.Action = null; + + existing.Add(new OsuSpriteText + { + Text = Build.CreatedAt.Date.ToString("dd MMM yyyy"), + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14), + Colour = OsuColour.FromHex(@"FD5"), + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 5 }, + }); + } + + NavigationIconButton left, right; + + fill.AddRange(new[] + { + left = new NavigationIconButton(Build.Versions?.Previous) + { + Icon = FontAwesome.Solid.ChevronLeft, + SelectBuild = b => SelectBuild(b) + }, + right = new NavigationIconButton(Build.Versions?.Next) + { + Icon = FontAwesome.Solid.ChevronRight, + SelectBuild = b => SelectBuild(b) + }, + }); + + fill.SetLayoutPosition(left, -1); + fill.SetLayoutPosition(right, 1); + + return fill; + } + } + + private class NavigationIconButton : IconButton + { + public Action SelectBuild; + + public NavigationIconButton(APIChangelogBuild build) + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + + if (build == null) return; + + TooltipText = build.DisplayVersion; + + Action = () => + { + SelectBuild?.Invoke(build); + Enabled.Value = false; + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + HoverColour = colours.GreyVioletLight.Opacity(0.6f); + FlashColour = colours.GreyVioletLighter; + } + } + } +} diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs new file mode 100644 index 0000000000..52b77604d9 --- /dev/null +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadge.cs @@ -0,0 +1,157 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class UpdateStreamBadge : TabItem + { + private const float badge_height = 66.5f; + private const float badge_width = 100; + private const float transition_duration = 100; + + private readonly ExpandingBar expandingBar; + private SampleChannel sampleClick; + private SampleChannel sampleHover; + + private readonly FillFlowContainer text; + + public readonly Bindable SelectedTab = new Bindable(); + + private readonly Container fadeContainer; + + public UpdateStreamBadge(APIUpdateStream stream) + : base(stream) + { + Size = new Vector2(stream.IsFeatured ? badge_width * 2 : badge_width, badge_height); + Padding = new MarginPadding(5); + + Child = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + text = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new[] + { + new OsuSpriteText + { + Text = stream.DisplayName, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), + Margin = new MarginPadding { Top = 6 }, + }, + new OsuSpriteText + { + Text = stream.LatestBuild.DisplayVersion, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 16), + }, + new OsuSpriteText + { + Text = stream.LatestBuild.Users > 0 ? $"{stream.LatestBuild.Users:N0} {"user".Pluralize(stream.LatestBuild.Users == 1)} online" : null, + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 10), + Colour = new Color4(203, 164, 218, 255), + }, + } + }, + expandingBar = new ExpandingBar + { + Anchor = Anchor.TopCentre, + Colour = stream.Colour, + ExpandedSize = 4, + CollapsedSize = 2, + IsCollapsed = true + }, + } + }; + + SelectedTab.BindValueChanged(_ => updateState(), true); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleClick = audio.Samples.Get(@"UI/generic-select-soft"); + sampleHover = audio.Samples.Get(@"UI/generic-hover-soft"); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + protected override bool OnClick(ClickEvent e) + { + sampleClick?.Play(); + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + sampleHover?.Play(); + updateState(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + // Expand based on the local state + bool shouldExpand = Active.Value || IsHovered; + + // Expand based on whether no build is selected and the badge area is hovered + shouldExpand |= SelectedTab.Value == null && !externalDimRequested; + + if (shouldExpand) + { + expandingBar.Expand(); + fadeContainer.FadeTo(1, transition_duration); + } + else + { + expandingBar.Collapse(); + fadeContainer.FadeTo(0.5f, transition_duration); + } + + text.FadeTo(externalDimRequested && !IsHovered ? 0.5f : 1, transition_duration); + } + + private bool externalDimRequested; + + public void EnableDim() + { + externalDimRequested = true; + updateState(); + } + + public void DisableDim() + { + externalDimRequested = false; + updateState(); + } + } +} diff --git a/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs new file mode 100644 index 0000000000..2b48811bd6 --- /dev/null +++ b/osu.Game/Overlays/Changelog/UpdateStreamBadgeArea.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Online.API.Requests.Responses; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Changelog +{ + public class UpdateStreamBadgeArea : TabControl + { + public UpdateStreamBadgeArea() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + AddInternal(new Box + { + Colour = Color4.Black, + Alpha = 0.12f, + RelativeSizeAxes = Axes.Both, + }); + } + + public void Populate(List streams) + { + Current.Value = null; + + foreach (APIUpdateStream updateStream in streams) + AddItem(updateStream); + } + + protected override bool OnHover(HoverEvent e) + { + foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType()) + streamBadge.EnableDim(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + foreach (UpdateStreamBadge streamBadge in TabContainer.Children.OfType()) + streamBadge.DisableDim(); + + base.OnHoverLost(e); + } + + protected override TabFillFlowContainer CreateTabFlow() + { + var flow = base.CreateTabFlow(); + + flow.RelativeSizeAxes = Axes.X; + flow.AutoSizeAxes = Axes.Y; + flow.AllowMultiline = true; + flow.Padding = new MarginPadding + { + Vertical = 20, + Horizontal = 85, + }; + + return flow; + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(APIUpdateStream value) => + new UpdateStreamBadge(value) { SelectedTab = { BindTarget = Current } }; + } +} diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs new file mode 100644 index 0000000000..7755c8a6a6 --- /dev/null +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -0,0 +1,210 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Input.Bindings; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Changelog; + +namespace osu.Game.Overlays +{ + public class ChangelogOverlay : FullscreenOverlay + { + public readonly Bindable Current = new Bindable(); + + private ChangelogHeader header; + + private Container content; + + private SampleChannel sampleBack; + + private List builds; + + private List streams; + + [BackgroundDependencyLoader] + private void load(AudioManager audio, OsuColour colour) + { + Waves.FirstWaveColour = colour.GreyVioletLight; + Waves.SecondWaveColour = colour.GreyViolet; + Waves.ThirdWaveColour = colour.GreyVioletDark; + Waves.FourthWaveColour = colour.GreyVioletDarker; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour.PurpleDarkAlternative, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new ReverseChildIDFillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + header = new ChangelogHeader + { + ListingSelected = ShowListing, + }, + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + }, + }, + }, + }; + + sampleBack = audio.Samples.Get(@"UI/generic-select-soft"); + + header.Current.BindTo(Current); + + Current.BindValueChanged(e => + { + if (e.NewValue != null) + loadContent(new ChangelogSingleBuild(e.NewValue)); + else + loadContent(new ChangelogListing(builds)); + }); + } + + public void ShowListing() + { + Current.Value = null; + Show(); + } + + /// + /// Fetches and shows a specific build from a specific update stream. + /// + /// Must contain at least and + /// . If and + /// are specified, the header will instantly display them. + public void ShowBuild([NotNull] APIChangelogBuild build) + { + if (build == null) throw new ArgumentNullException(nameof(build)); + + Current.Value = build; + Show(); + } + + public void ShowBuild([NotNull] string updateStream, [NotNull] string version) + { + if (updateStream == null) throw new ArgumentNullException(nameof(updateStream)); + if (version == null) throw new ArgumentNullException(nameof(version)); + + performAfterFetch(() => + { + var build = builds.Find(b => b.Version == version && b.UpdateStream.Name == updateStream) + ?? streams.Find(s => s.Name == updateStream)?.LatestBuild; + + if (build != null) + ShowBuild(build); + }); + + Show(); + } + + public override bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + if (Current.Value == null) + { + Hide(); + } + else + { + Current.Value = null; + sampleBack?.Play(); + } + + return true; + } + + return false; + } + + protected override void PopIn() + { + base.PopIn(); + + if (initialFetchTask == null) + // fetch and refresh to show listing, if no other request was made via Show methods + performAfterFetch(() => Current.TriggerChange()); + } + + private Task initialFetchTask; + + private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ => Schedule(action)); + + private Task fetchListing() + { + if (initialFetchTask != null) + return initialFetchTask; + + return initialFetchTask = Task.Run(async () => + { + var tcs = new TaskCompletionSource(); + + var req = new GetChangelogRequest(); + req.Success += res => + { + // remap streams to builds to ensure model equality + res.Builds.ForEach(b => b.UpdateStream = res.Streams.Find(s => s.Id == b.UpdateStream.Id)); + res.Streams.ForEach(s => s.LatestBuild.UpdateStream = res.Streams.Find(s2 => s2.Id == s.LatestBuild.UpdateStream.Id)); + + builds = res.Builds; + streams = res.Streams; + + header.Streams.Populate(res.Streams); + + tcs.SetResult(true); + }; + req.Failure += _ => initialFetchTask = null; + req.Perform(API); + + await tcs.Task; + }); + } + + private CancellationTokenSource loadContentCancellation; + + private void loadContent(ChangelogContent newContent) + { + content.FadeTo(0.2f, 300, Easing.OutQuint); + + loadContentCancellation?.Cancel(); + + LoadComponentAsync(newContent, c => + { + content.FadeIn(300, Easing.OutQuint); + + c.BuildSelected = ShowBuild; + content.Child = c; + }, (loadContentCancellation = new CancellationTokenSource()).Token); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index c11de48430..e29216dffd 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -1,27 +1,240 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; -using osuTK; -using osuTK.Graphics; +using System.Collections.Generic; 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.Effects; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Users; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Chat { - public class ChatLine : Container + public class ChatLine : CompositeDrawable { + public const float LEFT_PADDING = default_message_padding + default_horizontal_padding * 2; + + private const float default_message_padding = 200; + + protected virtual float MessagePadding => default_message_padding; + + private const float default_horizontal_padding = 15; + + protected virtual float HorizontalPadding => default_horizontal_padding; + + protected virtual float TextSize => 20; + + private Color4 customUsernameColour; + + private OsuSpriteText timestamp; + + public ChatLine(Message message) + { + Message = message; + Padding = new MarginPadding { Left = HorizontalPadding, Right = HorizontalPadding }; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [Resolved(CanBeNull = true)] + private ChannelManager chatManager { get; set; } + + private Message message; + private OsuSpriteText username; + private LinkFlowContainer contentFlow; + + public LinkFlowContainer ContentFlow => contentFlow; + + public Message Message + { + get => message; + set + { + if (message == value) return; + + message = MessageFormatter.FormatMessage(value); + + if (!IsLoaded) + return; + + updateMessageContent(); + } + } + + private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + customUsernameColour = colours.ChatBlue; + + bool hasBackground = senderHasBackground; + + Drawable effectedUsername = username = new OsuSpriteText + { + Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], + Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true) + }; + + if (hasBackground) + { + // Background effect + effectedUsername = new Container + { + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Roundness = 1, + Offset = new Vector2(0, 3), + Radius = 3, + Colour = Color4.Black.Opacity(0.3f), + Type = EdgeEffectType.Shadow, + }, + // Drop shadow effect + Child = new Container + { + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4, + EdgeEffect = new EdgeEffectParameters + { + Radius = 1, + Colour = OsuColour.FromHex(message.Sender.Colour), + Type = EdgeEffectType.Shadow, + }, + Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 }, + Y = 3, + Child = username, + } + }; + } + + InternalChildren = new Drawable[] + { + new Container + { + Size = new Vector2(MessagePadding, TextSize), + Children = new Drawable[] + { + timestamp = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true) + }, + new MessageSender(message.Sender) + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Child = effectedUsername, + }, + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = MessagePadding + HorizontalPadding }, + Children = new Drawable[] + { + contentFlow = new LinkFlowContainer(t => + { + if (Message.IsAction) + { + t.Font = OsuFont.GetFont(italics: true); + + if (senderHasBackground) + t.Colour = OsuColour.FromHex(message.Sender.Colour); + } + + t.Font = t.Font.With(size: TextSize); + }) + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + } + } + } + }; + + updateMessageContent(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + FinishTransforms(true); + } + + private void updateMessageContent() + { + this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); + timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); + + timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; + username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); + + // remove non-existent channels from the link list + message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true); + + contentFlow.Clear(); + contentFlow.AddLinks(message.DisplayContent, message.Links); + } + + private class MessageSender : OsuClickableContainer, IHasContextMenu + { + private readonly User sender; + + private Action startChatAction; + + [Resolved] + private IAPIProvider api { get; set; } + + public MessageSender(User sender) + { + this.sender = sender; + } + + [BackgroundDependencyLoader(true)] + private void load(UserProfileOverlay profile, ChannelManager chatManager) + { + Action = () => profile?.ShowUser(sender); + startChatAction = () => chatManager?.OpenPrivateChannel(sender); + } + + public MenuItem[] ContextMenuItems + { + get + { + List items = new List + { + new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) + }; + + if (sender.Id != api.LocalUser.Value.Id) + items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction)); + + return items.ToArray(); + } + } + } + private static readonly Color4[] username_colours = { OsuColour.FromHex("588c7e"), @@ -60,202 +273,5 @@ namespace osu.Game.Overlays.Chat OsuColour.FromHex("812a96"), OsuColour.FromHex("992861"), }; - - public const float LEFT_PADDING = message_padding + padding * 2; - - private const float padding = 15; - private const float message_padding = 200; - private const float action_padding = 3; - private const float text_size = 20; - - private Color4 customUsernameColour; - - private OsuSpriteText timestamp; - - public ChatLine(Message message) - { - Message = message; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Left = padding, Right = padding }; - } - - private ChannelManager chatManager; - - private Message message; - private OsuSpriteText username; - private LinkFlowContainer contentFlow; - - public LinkFlowContainer ContentFlow => contentFlow; - - public Message Message - { - get => message; - set - { - if (message == value) return; - - message = MessageFormatter.FormatMessage(value); - - if (!IsLoaded) - return; - - updateMessageContent(); - } - } - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, ChannelManager chatManager) - { - this.chatManager = chatManager; - customUsernameColour = colours.ChatBlue; - } - - private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); - - protected override void LoadComplete() - { - base.LoadComplete(); - - bool hasBackground = senderHasBackground; - - Drawable effectedUsername = username = new OsuSpriteText - { - Font = @"Exo2.0-BoldItalic", - Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], - TextSize = text_size, - }; - - if (hasBackground) - { - // Background effect - effectedUsername = new Container - { - AutoSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters - { - Roundness = 1, - Offset = new Vector2(0, 3), - Radius = 3, - Colour = Color4.Black.Opacity(0.3f), - Type = EdgeEffectType.Shadow, - }, - // Drop shadow effect - Child = new Container - { - AutoSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters - { - Radius = 1, - Colour = OsuColour.FromHex(message.Sender.Colour), - Type = EdgeEffectType.Shadow, - }, - Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 }, - Y = 3, - Child = username, - } - }; - } - - Children = new Drawable[] - { - new Container - { - Size = new Vector2(message_padding, text_size), - Children = new Drawable[] - { - timestamp = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = @"Exo2.0-SemiBold", - FixedWidth = true, - TextSize = text_size * 0.75f, - }, - new MessageSender(message.Sender) - { - AutoSizeAxes = Axes.Both, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Child = effectedUsername, - }, - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = message_padding + padding }, - Children = new Drawable[] - { - contentFlow = new LinkFlowContainer(t => - { - if (Message.IsAction) - { - t.Font = @"Exo2.0-MediumItalic"; - - if (senderHasBackground) - t.Colour = OsuColour.FromHex(message.Sender.Colour); - } - - t.TextSize = text_size; - }) - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - } - } - } - }; - - updateMessageContent(); - FinishTransforms(true); - } - - private void updateMessageContent() - { - this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); - timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); - - timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; - username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); - - // remove non-existent channels from the link list - message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true); - - contentFlow.Clear(); - contentFlow.AddLinks(message.DisplayContent, message.Links); - } - - private class MessageSender : OsuClickableContainer, IHasContextMenu - { - private readonly User sender; - - private Action startChatAction; - - public MessageSender(User sender) - { - this.sender = sender; - } - - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profile, ChannelManager chatManager) - { - Action = () => profile?.ShowUser(sender); - startChatAction = () => chatManager?.OpenPrivateChannel(sender); - } - - public MenuItem[] ContextMenuItems => new MenuItem[] - { - new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action), - new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction), - }; - } } } diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs deleted file mode 100644 index 1f8c5d38b9..0000000000 --- a/osu.Game/Overlays/Chat/ChatTabControl.cs +++ /dev/null @@ -1,348 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Chat; -using osuTK; -using osuTK.Input; -using osuTK.Graphics; -using osu.Framework.Configuration; -using System; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Overlays.Chat -{ - public class ChatTabControl : OsuTabControl - { - private const float shear_width = 10; - - public Action OnRequestLeave; - - public readonly Bindable ChannelSelectorActive = new Bindable(); - - private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab; - - public ChatTabControl() - { - TabContainer.Margin = new MarginPadding { Left = 50 }; - TabContainer.Spacing = new Vector2(-shear_width, 0); - TabContainer.Masking = false; - - AddInternal(new SpriteIcon - { - Icon = FontAwesome.fa_comments, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Size = new Vector2(20), - Margin = new MarginPadding(10), - }); - - AddTabItem(selectorTab = new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" })); - - ChannelSelectorActive.BindTo(selectorTab.Active); - } - - protected override void AddTabItem(TabItem item, bool addToDropdown = true) - { - if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue) - // performTabSort might've made selectorTab's position wonky, fix it - TabContainer.SetLayoutPosition(selectorTab, float.MaxValue); - - base.AddTabItem(item, addToDropdown); - - if (SelectedTab == null) - SelectTab(item); - } - - protected override TabItem CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; - - protected override void SelectTab(TabItem tab) - { - if (tab is ChannelTabItem.ChannelSelectorTabItem) - { - tab.Active.Toggle(); - return; - } - - selectorTab.Active.Value = false; - - base.SelectTab(tab); - } - - private void tabCloseRequested(TabItem tab) - { - int totalTabs = TabContainer.Count - 1; // account for selectorTab - int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); - - if (tab == SelectedTab && totalTabs > 1) - // Select the tab after tab-to-be-removed's index, or the tab before if current == last - SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]); - else if (totalTabs == 1 && !selectorTab.Active) - // Open channel selection overlay if all channel tabs will be closed after removing this tab - SelectTab(selectorTab); - - OnRequestLeave?.Invoke(tab.Value); - } - - private class ChannelTabItem : TabItem - { - private Color4 backgroundInactive; - private Color4 backgroundHover; - private Color4 backgroundActive; - - public override bool IsRemovable => !Pinned; - - private readonly SpriteText text; - private readonly SpriteText textBold; - private readonly ClickableContainer closeButton; - private readonly Box box; - private readonly Box highlightBox; - private readonly SpriteIcon icon; - - public Action OnRequestClose; - - private void updateState() - { - if (Active) - fadeActive(); - else - fadeInactive(); - } - - private const float transition_length = 400; - - private void fadeActive() - { - this.ResizeTo(new Vector2(Width, 1.1f), transition_length, Easing.OutQuint); - - box.FadeColour(backgroundActive, transition_length, Easing.OutQuint); - highlightBox.FadeIn(transition_length, Easing.OutQuint); - - text.FadeOut(transition_length, Easing.OutQuint); - textBold.FadeIn(transition_length, Easing.OutQuint); - } - - private void fadeInactive() - { - this.ResizeTo(new Vector2(Width, 1), transition_length, Easing.OutQuint); - - box.FadeColour(backgroundInactive, transition_length, Easing.OutQuint); - highlightBox.FadeOut(transition_length, Easing.OutQuint); - - text.FadeIn(transition_length, Easing.OutQuint); - textBold.FadeOut(transition_length, Easing.OutQuint); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - if (e.Button == MouseButton.Middle) - { - closeButton.Action(); - return true; - } - - return false; - } - - protected override bool OnHover(HoverEvent e) - { - if (IsRemovable) - closeButton.FadeIn(200, Easing.OutQuint); - - if (!Active) - box.FadeColour(backgroundHover, transition_length, Easing.OutQuint); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) - { - closeButton.FadeOut(200, Easing.OutQuint); - updateState(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - backgroundActive = colours.ChatBlue; - backgroundInactive = colours.Gray4; - backgroundHover = colours.Gray7; - - highlightBox.Colour = colours.Yellow; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateState(); - } - - public ChannelTabItem(Channel value) : base(value) - { - Width = 150; - - RelativeSizeAxes = Axes.Y; - - Anchor = Anchor.BottomLeft; - Origin = Anchor.BottomLeft; - - Shear = new Vector2(shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0); - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Radius = 10, - Colour = Color4.Black.Opacity(0.2f), - }; - - Children = new Drawable[] - { - box = new Box - { - EdgeSmoothness = new Vector2(1, 0), - RelativeSizeAxes = Axes.Both, - }, - highlightBox = new Box - { - Width = 5, - Alpha = 0, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - EdgeSmoothness = new Vector2(1, 0), - RelativeSizeAxes = Axes.Y, - }, - new Container - { - Shear = new Vector2(-shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0), - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - icon = new SpriteIcon - { - Icon = FontAwesome.fa_hashtag, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Colour = Color4.Black, - X = -10, - Alpha = 0.2f, - Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT), - }, - text = new OsuSpriteText - { - Margin = new MarginPadding(5), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = value.ToString(), - TextSize = 18, - }, - textBold = new OsuSpriteText - { - Alpha = 0, - Margin = new MarginPadding(5), - Origin = Anchor.CentreLeft, - Anchor = Anchor.CentreLeft, - Text = value.ToString(), - Font = @"Exo2.0-Bold", - TextSize = 18, - }, - closeButton = new CloseButton - { - Alpha = 0, - Margin = new MarginPadding { Right = 20 }, - Origin = Anchor.CentreRight, - Anchor = Anchor.CentreRight, - Action = delegate - { - if (IsRemovable) OnRequestClose?.Invoke(this); - }, - }, - }, - }, - }; - } - - public class CloseButton : OsuClickableContainer - { - private readonly SpriteIcon icon; - - public CloseButton() - { - Size = new Vector2(20); - - Child = icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.75f), - Icon = FontAwesome.fa_close, - RelativeSizeAxes = Axes.Both, - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - icon.ScaleTo(0.5f, 1000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - icon.ScaleTo(0.75f, 1000, Easing.OutElastic); - return base.OnMouseUp(e); - } - - protected override bool OnHover(HoverEvent e) - { - icon.FadeColour(Color4.Red, 200, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - icon.FadeColour(Color4.White, 200, Easing.OutQuint); - base.OnHoverLost(e); - } - } - - public class ChannelSelectorTabItem : ChannelTabItem - { - public override bool IsRemovable => false; - - public override bool IsSwitchable => false; - - public ChannelSelectorTabItem(Channel value) : base(value) - { - Depth = float.MaxValue; - Width = 45; - - icon.Alpha = 0; - - text.TextSize = 45; - textBold.TextSize = 45; - } - - [BackgroundDependencyLoader] - private new void load(OsuColour colour) - { - backgroundInactive = colour.Gray2; - backgroundActive = colour.Gray3; - } - } - - protected override void OnActivated() => updateState(); - - protected override void OnDeactivated() => updateState(); - } - } -} diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index ce5d961282..8d56e250fc 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osuTK.Graphics; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,15 +18,18 @@ namespace osu.Game.Overlays.Chat public class DrawableChannel : Container { public readonly Channel Channel; - private readonly ChatLineContainer flow; - private readonly ScrollContainer scroll; + protected ChatLineContainer ChatLineFlow; + private OsuScrollContainer scroll; public DrawableChannel(Channel channel) { Channel = channel; - RelativeSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] + private void load() + { Children = new Drawable[] { scroll = new OsuScrollContainer @@ -38,7 +42,7 @@ namespace osu.Game.Overlays.Chat { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = flow = new ChatLineContainer + Child = ChatLineFlow = new ChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, RelativeSizeAxes = Axes.X, @@ -49,6 +53,8 @@ namespace osu.Game.Overlays.Chat } }; + newMessagesArrived(Channel.Messages); + Channel.NewMessagesArrived += newMessagesArrived; Channel.MessageRemoved += messageRemoved; Channel.PendingMessageResolved += pendingMessageResolved; @@ -57,8 +63,6 @@ namespace osu.Game.Overlays.Chat protected override void LoadComplete() { base.LoadComplete(); - - newMessagesArrived(Channel.Messages); scrollToEnd(); } @@ -71,19 +75,19 @@ namespace osu.Game.Overlays.Chat Channel.PendingMessageResolved -= pendingMessageResolved; } + protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); + private void newMessagesArrived(IEnumerable newMessages) { // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); - flow.AddRange(displayMessages.Select(m => new ChatLine(m))); + ChatLineFlow.AddRange(displayMessages.Select(CreateChatLine)); - if (!IsLoaded) return; - - if (scroll.IsScrolledToEnd(10) || !flow.Children.Any() || newMessages.Any(m => m is LocalMessage)) + if (scroll.IsScrolledToEnd(10) || !ChatLineFlow.Children.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); - var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + var staleMessages = ChatLineFlow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MaxHistory; for (int i = 0; i < count; i++) @@ -97,25 +101,26 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { - var found = flow.Children.LastOrDefault(c => c.Message == existing); + var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); + if (found != null) { Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - flow.Remove(found); + ChatLineFlow.Remove(found); found.Message = updated; - flow.Add(found); + ChatLineFlow.Add(found); } } private void messageRemoved(Message removed) { - flow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + ChatLineFlow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - private class ChatLineContainer : FillFlowContainer + protected class ChatLineContainer : FillFlowContainer { protected override int Compare(Drawable x, Drawable y) { diff --git a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs index b3f7b282a9..dbae091fb0 100644 --- a/osu.Game/Overlays/Chat/ExternalLinkDialog.cs +++ b/osu.Game/Overlays/Chat/ExternalLinkDialog.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Chat @@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Chat HeaderText = "Just checking..."; BodyText = $"You are about to leave osu! and open the following link in a web browser:\n\n{url}"; - Icon = FontAwesome.fa_warning; + Icon = FontAwesome.Solid.ExclamationTriangle; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs index 5a96465520..4d77e5f93d 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelListItem.cs @@ -1,14 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -36,14 +37,14 @@ namespace osu.Game.Overlays.Chat.Selection private Color4 hoverColour; public IEnumerable FilterTerms => new[] { channel.Name }; + public bool MatchingFilter { - set - { - this.FadeTo(value ? 1f : 0f, 100); - } + set => this.FadeTo(value ? 1f : 0f, 100); } + public bool FilteringActive { get; set; } + public Action OnRequestJoin; public Action OnRequestLeave; @@ -54,7 +55,7 @@ namespace osu.Game.Overlays.Chat.Selection RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Action = () => { (channel.Joined ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); }; + Action = () => { (channel.Joined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); }; Children = new Drawable[] { @@ -73,7 +74,7 @@ namespace osu.Game.Overlays.Chat.Selection { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Icon = FontAwesome.fa_check_circle, + Icon = FontAwesome.Solid.CheckCircle, Size = new Vector2(text_size), Shadow = false, Margin = new MarginPadding { Right = 10f }, @@ -89,8 +90,7 @@ namespace osu.Game.Overlays.Chat.Selection name = new OsuSpriteText { Text = channel.ToString(), - TextSize = text_size, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), Shadow = false, }, }, @@ -106,8 +106,7 @@ namespace osu.Game.Overlays.Chat.Selection topic = new OsuSpriteText { Text = channel.Topic, - TextSize = text_size, - Font = @"Exo2.0-SemiBold", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.SemiBold), Shadow = false, }, }, @@ -122,7 +121,7 @@ namespace osu.Game.Overlays.Chat.Selection { new SpriteIcon { - Icon = FontAwesome.fa_user, + Icon = FontAwesome.Solid.User, Size = new Vector2(text_size - 2), Shadow = false, Margin = new MarginPadding { Top = 1 }, @@ -130,8 +129,7 @@ namespace osu.Game.Overlays.Chat.Selection new OsuSpriteText { Text = @"0", - TextSize = text_size, - Font = @"Exo2.0-SemiBold", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.SemiBold), Shadow = false, }, }, @@ -148,7 +146,7 @@ namespace osu.Game.Overlays.Chat.Selection joinedColour = colours.Blue; hoverColour = colours.Yellow; - joinedBind.ValueChanged += updateColour; + joinedBind.ValueChanged += joined => updateColour(joined.NewValue); joinedBind.BindTo(channel.Joined); joinedBind.TriggerChange(); diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs index 94ee9d4bf6..eac48ca5cb 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -1,11 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; @@ -18,24 +20,24 @@ namespace osu.Game.Overlays.Chat.Selection public readonly FillFlowContainer ChannelFlow; public IEnumerable FilterableChildren => ChannelFlow.Children; - public IEnumerable FilterTerms => new[] { Header }; + public IEnumerable FilterTerms => Array.Empty(); + public bool MatchingFilter { - set - { - this.FadeTo(value ? 1f : 0f, 100); - } + set => this.FadeTo(value ? 1f : 0f, 100); } + public bool FilteringActive { get; set; } + public string Header { - get { return header.Text; } - set { header.Text = value.ToUpperInvariant(); } + get => header.Text; + set => header.Text = value.ToUpperInvariant(); } public IEnumerable Channels { - set { ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c)); } + set => ChannelFlow.ChildrenEnumerable = value.Select(c => new ChannelListItem(c)); } public ChannelSection() @@ -47,8 +49,7 @@ namespace osu.Game.Overlays.Chat.Selection { header = new OsuSpriteText { - TextSize = 15, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), }, ChannelFlow = new FillFlowContainer { diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs index 3afac211f1..e0ded11ec9 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSelectionOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -20,7 +20,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Chat.Selection { - public class ChannelSelectionOverlay : OsuFocusedOverlayContainer + public class ChannelSelectionOverlay : WaveOverlayContainer { public static readonly float WIDTH_PADDING = 170; @@ -32,6 +32,8 @@ namespace osu.Game.Overlays.Chat.Selection private readonly SearchTextBox search; private readonly SearchContainer sectionsFlow; + protected override bool DimMainContent => false; + public Action OnRequestJoin; public Action OnRequestLeave; @@ -39,6 +41,11 @@ namespace osu.Game.Overlays.Chat.Selection { RelativeSizeAxes = Axes.X; + Waves.FirstWaveColour = OsuColour.FromHex("353535"); + Waves.SecondWaveColour = OsuColour.FromHex("434343"); + Waves.ThirdWaveColour = OsuColour.FromHex("515151"); + Waves.FourthWaveColour = OsuColour.FromHex("595959"); + Children = new Drawable[] { new Container @@ -105,7 +112,7 @@ namespace osu.Game.Overlays.Chat.Selection new OsuSpriteText { Text = @"Chat Channels", - TextSize = 20, + Font = OsuFont.GetFont(size: 20), Shadow = false, }, search = new HeaderSearchTextBox @@ -120,7 +127,7 @@ namespace osu.Game.Overlays.Chat.Selection }, }; - search.Current.ValueChanged += newValue => sectionsFlow.SearchTerm = newValue; + search.Current.ValueChanged += term => sectionsFlow.SearchTerm = term.NewValue; } public void UpdateAvailableChannels(IEnumerable channels) @@ -159,7 +166,7 @@ namespace osu.Game.Overlays.Chat.Selection protected override void OnFocus(FocusEvent e) { - GetContainingInputManager().ChangeFocus(search); + search.TakeFocus(); base.OnFocus(e); } @@ -185,8 +192,12 @@ namespace osu.Game.Overlays.Chat.Selection private class HeaderSearchTextBox : SearchTextBox { - protected override Color4 BackgroundFocused => Color4.Black.Opacity(0.2f); - protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.2f); + [BackgroundDependencyLoader] + private void load() + { + BackgroundFocused = Color4.Black.Opacity(0.2f); + BackgroundUnfocused = Color4.Black.Opacity(0.2f); + } } } } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs index 0b1721741a..7386bffb1a 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelSelectorTabItem.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; @@ -11,22 +11,33 @@ namespace osu.Game.Overlays.Chat.Tabs { public override bool IsRemovable => false; - public ChannelSelectorTabItem(Channel value) : base(value) + public override bool IsSwitchable => false; + + public ChannelSelectorTabItem() + : base(new ChannelSelectorTabChannel()) { Depth = float.MaxValue; Width = 45; Icon.Alpha = 0; - Text.TextSize = 45; - TextBold.TextSize = 45; + Text.Font = Text.Font.With(size: 45); + TextBold.Font = Text.Font.With(size: 45); } [BackgroundDependencyLoader] - private new void load(OsuColour colour) + private void load(OsuColour colour) { BackgroundInactive = colour.Gray2; BackgroundActive = colour.Gray3; } + + public class ChannelSelectorTabChannel : Channel + { + public ChannelSelectorTabChannel() + { + Name = "+"; + } + } } } diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index 79cb0a4d14..612379d339 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Chat; using osuTK; -using osu.Framework.Configuration; using System; using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Chat.Tabs { @@ -31,14 +31,14 @@ namespace osu.Game.Overlays.Chat.Tabs AddInternal(new SpriteIcon { - Icon = FontAwesome.fa_comments, + Icon = FontAwesome.Solid.Comments, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(20), Margin = new MarginPadding(10), }); - AddTabItem(selectorTab = new ChannelSelectorTabItem(new Channel { Name = "+" })); + AddTabItem(selectorTab = new ChannelSelectorTabItem()); ChannelSelectorActive.BindTo(selectorTab.Active); } @@ -49,6 +49,8 @@ namespace osu.Game.Overlays.Chat.Tabs // performTabSort might've made selectorTab's position wonky, fix it TabContainer.SetLayoutPosition(selectorTab, float.MaxValue); + ((ChannelTabItem)item).OnRequestClose += tabCloseRequested; + base.AddTabItem(item, addToDropdown); } @@ -57,9 +59,10 @@ namespace osu.Game.Overlays.Chat.Tabs switch (value.Type) { default: - return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + return new ChannelTabItem(value); + case ChannelType.PM: - return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + return new PrivateChannelTabItem(value); } } @@ -94,13 +97,12 @@ namespace osu.Game.Overlays.Chat.Tabs { if (tab is ChannelSelectorTabItem) { - tab.Active.Toggle(); + tab.Active.Value = true; return; } - selectorTab.Active.Value = false; - base.SelectTab(tab); + selectorTab.Active.Value = false; } private void tabCloseRequested(TabItem tab) @@ -111,7 +113,7 @@ namespace osu.Game.Overlays.Chat.Tabs if (tab == SelectedTab && totalTabs > 1) // Select the tab after tab-to-be-removed's index, or the tab before if current == last SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]); - else if (totalTabs == 1 && !selectorTab.Active) + else if (totalTabs == 1 && !selectorTab.Active.Value) // Open channel selection overlay if all channel tabs will be closed after removing this tab SelectTab(selectorTab); diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs index e98eac0aa2..7f820e4ff7 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabItem.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -90,7 +91,7 @@ namespace osu.Game.Overlays.Chat.Tabs Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Text = value.ToString(), - TextSize = 18, + Font = OsuFont.GetFont(size: 18) }, TextBold = new OsuSpriteText { @@ -99,8 +100,7 @@ namespace osu.Game.Overlays.Chat.Tabs Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Text = value.ToString(), - Font = @"Exo2.0-Bold", - TextSize = 18, + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold) }, CloseButton = new TabCloseButton { @@ -118,7 +118,7 @@ namespace osu.Game.Overlays.Chat.Tabs }; } - protected virtual FontAwesome DisplayIcon => FontAwesome.fa_hashtag; + protected virtual IconUsage DisplayIcon => FontAwesome.Solid.Hashtag; protected virtual bool ShowCloseOnHover => true; @@ -127,7 +127,7 @@ namespace osu.Game.Overlays.Chat.Tabs if (IsRemovable && ShowCloseOnHover) CloseButton.FadeIn(200, Easing.OutQuint); - if (!Active) + if (!Active.Value) box.FadeColour(backgroundHover, TRANSITION_LENGTH, Easing.OutQuint); return true; } @@ -158,7 +158,7 @@ namespace osu.Game.Overlays.Chat.Tabs private void updateState() { - if (Active) + if (Active.Value) FadeActive(); else FadeInactive(); diff --git a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs index e04d7891f8..9e87bae864 100644 --- a/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs +++ b/osu.Game/Overlays/Chat/Tabs/PrivateChannelTabItem.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -7,20 +7,17 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; -using osu.Game.Users; +using osu.Game.Users.Drawables; using osuTK; namespace osu.Game.Overlays.Chat.Tabs { public class PrivateChannelTabItem : ChannelTabItem { - private readonly OsuSpriteText username; - private readonly Avatar avatarContainer; - - protected override FontAwesome DisplayIcon => FontAwesome.fa_at; + protected override IconUsage DisplayIcon => FontAwesome.Solid.At; public PrivateChannelTabItem(Channel value) : base(value) @@ -28,6 +25,8 @@ namespace osu.Game.Overlays.Chat.Tabs if (value.Type != ChannelType.PM) throw new ArgumentException("Argument value needs to have the targettype user!"); + DrawableAvatar avatar; + AddRange(new Drawable[] { new Container @@ -49,10 +48,10 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Masking = true, - Child = new DelayedLoadWrapper(new Avatar(value.Users.First()) + Child = new DelayedLoadWrapper(avatar = new DrawableAvatar(value.Users.First()) { RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), + OpenOnClick = { Value = false }, }) { RelativeSizeAxes = Axes.Both, @@ -62,6 +61,8 @@ namespace osu.Game.Overlays.Chat.Tabs }, }); + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + Text.X = ChatOverlay.TAB_AREA_HEIGHT; TextBold.X = ChatOverlay.TAB_AREA_HEIGHT; } @@ -76,7 +77,6 @@ namespace osu.Game.Overlays.Chat.Tabs CloseButton.FadeIn(TRANSITION_LENGTH, Easing.OutQuint); } - protected override void FadeInactive() { base.FadeInactive(); diff --git a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs index d7d9b3f95c..bde930d4fb 100644 --- a/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs +++ b/osu.Game/Overlays/Chat/Tabs/TabCloseButton.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; using osuTK.Graphics; @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Chat.Tabs Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(0.75f), - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Solid.TimesCircle, RelativeSizeAxes = Axes.Both, }; } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index b1edfe0548..e223856b27 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Collections.Specialized; +using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -20,6 +20,7 @@ using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat.Selection; using osu.Game.Overlays.Chat.Tabs; +using osuTK.Input; namespace osu.Game.Overlays { @@ -30,12 +31,13 @@ namespace osu.Game.Overlays private ChannelManager channelManager; - private readonly Container currentChannelContainer; + private Container currentChannelContainer; + private readonly List loadedChannels = new List(); - private readonly LoadingAnimation loading; + private LoadingAnimation loading; - private readonly FocusedTextBox textbox; + private FocusedTextBox textbox; private const int transition_length = 500; @@ -43,19 +45,22 @@ namespace osu.Game.Overlays public const float TAB_AREA_HEIGHT = 50; - private readonly ChannelTabControl channelTabControl; + protected ChannelTabControl ChannelTabControl; - private readonly Container chatContainer; - private readonly TabsArea tabsArea; - private readonly Box chatBackground; - private readonly Box tabBackground; + protected virtual ChannelTabControl CreateChannelTabControl() => new ChannelTabControl(); + + private Container chatContainer; + private TabsArea tabsArea; + private Box chatBackground; + private Box tabBackground; public Bindable ChatHeight { get; set; } - private readonly Container channelSelectionContainer; - private readonly ChannelSelectionOverlay channelSelection; + private Container channelSelectionContainer; + protected ChannelSelectionOverlay ChannelSelectionOverlay; - public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || channelSelection.State == Visibility.Visible && channelSelection.ReceivePositionalInputAt(screenSpacePos); + public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) + || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); public ChatOverlay() { @@ -63,7 +68,11 @@ namespace osu.Game.Overlays RelativePositionAxes = Axes.Both; Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; + } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, OsuColour colours, ChannelManager channelManager) + { const float padding = 5; Children = new Drawable[] @@ -75,7 +84,7 @@ namespace osu.Game.Overlays Masking = true, Children = new[] { - channelSelection = new ChannelSelectionOverlay + ChannelSelectionOverlay = new ChannelSelectionOverlay { RelativeSizeAxes = Axes.Both, }, @@ -129,7 +138,7 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Height = 1, PlaceholderText = "type your message", - Exit = () => State = Visibility.Hidden, + Exit = Hide, OnCommit = postMessage, ReleaseFocusOnCommit = false, HoldFocus = true, @@ -148,26 +157,27 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - channelTabControl = new ChannelTabControl + ChannelTabControl = CreateChannelTabControl().With(d => { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - OnRequestLeave = channel => channelManager.LeaveChannel(channel) - }, + d.Anchor = Anchor.BottomLeft; + d.Origin = Anchor.BottomLeft; + d.RelativeSizeAxes = Axes.Both; + d.OnRequestLeave = channelManager.LeaveChannel; + }), } }, }, }, }; - channelTabControl.Current.ValueChanged += chat => channelManager.CurrentChannel.Value = chat; - channelTabControl.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden; - channelSelection.StateChanged += state => + ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; + ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; + ChannelSelectionOverlay.State.ValueChanged += state => { - channelTabControl.ChannelSelectorActive.Value = state == Visibility.Visible; + // Propagate the visibility state to ChannelSelectorActive + ChannelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible; - if (state == Visibility.Visible) + if (state.NewValue == Visibility.Visible) { textbox.HoldFocus = false; if (1f - ChatHeight.Value < channel_selection_min_height) @@ -177,53 +187,72 @@ namespace osu.Game.Overlays textbox.HoldFocus = true; }; - channelSelection.OnRequestJoin = channel => channelManager.JoinChannel(channel); - channelSelection.OnRequestLeave = channel => channelManager.LeaveChannel(channel); - } + ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); + ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) - { - switch (args.Action) + ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); + ChatHeight.ValueChanged += height => { - case NotifyCollectionChangedAction.Add: - foreach (Channel newChannel in args.NewItems) - { - channelTabControl.AddChannel(newChannel); - } + chatContainer.Height = (float)height.NewValue; + channelSelectionContainer.Height = 1f - (float)height.NewValue; + tabBackground.FadeTo(height.NewValue == 1 ? 1 : 0.8f, 200); + }; + ChatHeight.TriggerChange(); - break; - case NotifyCollectionChangedAction.Remove: - foreach (Channel removedChannel in args.OldItems) - { - channelTabControl.RemoveChannel(removedChannel); - loadedChannels.Remove(loadedChannels.Find(c => c.Channel == removedChannel)); - } + chatBackground.Colour = colours.ChatBlue; - break; - } + this.channelManager = channelManager; + + loading.Show(); + + // This is a relatively expensive (and blocking) operation. + // Scheduling it ensures that it won't be performed unless the user decides to open chat. + // TODO: Refactor OsuFocusedOverlayContainer / OverlayContainer to support delayed content loading. + Schedule(() => + { + // TODO: consider scheduling bindable callbacks to not perform when overlay is not present. + channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; + foreach (Channel channel in channelManager.JoinedChannels) + ChannelTabControl.AddChannel(channel); + + channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; + ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + + currentChannel = channelManager.CurrentChannel.GetBoundCopy(); + currentChannel.BindValueChanged(currentChannelChanged, true); + }); } - private void currentChannelChanged(Channel channel) + private Bindable currentChannel; + + private void currentChannelChanged(ValueChangedEvent e) { - if (channel == null) + if (e.NewValue == null) { textbox.Current.Disabled = true; currentChannelContainer.Clear(false); + ChannelSelectionOverlay.Show(); return; } - textbox.Current.Disabled = channel.ReadOnly; + if (e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel) + return; - if (channelTabControl.Current.Value != channel) - Scheduler.Add(() => channelTabControl.Current.Value = channel); + textbox.Current.Disabled = e.NewValue.ReadOnly; + + if (ChannelTabControl.Current.Value != e.NewValue) + Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue); + + var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); - var loaded = loadedChannels.Find(d => d.Channel == channel); if (loaded == null) { currentChannelContainer.FadeOut(500, Easing.OutQuint); loading.Show(); - loaded = new DrawableChannel(channel); + loaded = new DrawableChannel(e.NewValue); loadedChannels.Add(loaded); LoadComponentAsync(loaded, l => { @@ -237,7 +266,7 @@ namespace osu.Game.Overlays else { currentChannelContainer.Clear(false); - Scheduler.Add(() => currentChannelContainer.Add(loaded)); + currentChannelContainer.Add(loaded); } } @@ -262,7 +291,7 @@ namespace osu.Game.Overlays double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y; // If the channel selection screen is shown, mind its minimum height - if (channelSelection.State == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) + if (ChannelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) targetChatHeight = 1f - channel_selection_min_height; ChatHeight.Value = targetChatHeight; @@ -277,12 +306,46 @@ namespace osu.Game.Overlays return base.OnDragEnd(e); } + private void selectTab(int index) + { + var channel = ChannelTabControl.Items.Skip(index).FirstOrDefault(); + if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel)) + ChannelTabControl.Current.Value = channel; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.AltPressed) + { + switch (e.Key) + { + case Key.Number1: + case Key.Number2: + case Key.Number3: + case Key.Number4: + case Key.Number5: + case Key.Number6: + case Key.Number7: + case Key.Number8: + case Key.Number9: + selectTab((int)e.Key - (int)Key.Number1); + return true; + + case Key.Number0: + selectTab(9); + return true; + } + } + + return base.OnKeyDown(e); + } + public override bool AcceptsFocus => true; protected override void OnFocus(FocusEvent e) { //this is necessary as textbox is masked away and therefore can't get focus :( - GetContainingInputManager().ChangeFocus(textbox); + textbox.TakeFocus(); base.OnFocus(e); } @@ -292,6 +355,7 @@ namespace osu.Game.Overlays this.FadeIn(transition_length, Easing.OutQuint); textbox.HoldFocus = true; + base.PopIn(); } @@ -300,40 +364,29 @@ namespace osu.Game.Overlays this.MoveToY(Height, transition_length, Easing.InSine); this.FadeOut(transition_length, Easing.InSine); + ChannelSelectionOverlay.Hide(); + textbox.HoldFocus = false; base.PopOut(); } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuColour colours, ChannelManager channelManager) + private void onChannelAddedToJoinedChannels(IEnumerable channels) { - ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); - ChatHeight.ValueChanged += h => + foreach (Channel channel in channels) + ChannelTabControl.AddChannel(channel); + } + + private void onChannelRemovedFromJoinedChannels(IEnumerable channels) + { + foreach (Channel channel in channels) { - chatContainer.Height = (float)h; - channelSelectionContainer.Height = 1f - (float)h; - tabBackground.FadeTo(h == 1 ? 1 : 0.8f, 200); - }; - ChatHeight.TriggerChange(); - - chatBackground.Colour = colours.ChatBlue; - - loading.Show(); - - this.channelManager = channelManager; - channelManager.CurrentChannel.ValueChanged += currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; - - //for the case that channelmanager was faster at fetching the channels than our attachment to CollectionChanged. - channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); - joinedChannelsChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, channelManager.JoinedChannels)); + ChannelTabControl.RemoveChannel(channel); + loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); + } } - private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); - } + private void availableChannelsChanged(IEnumerable channels) + => ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); protected override void Dispose(bool isDisposing) { @@ -342,8 +395,10 @@ namespace osu.Game.Overlays if (channelManager != null) { channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; + channelManager.JoinedChannels.ItemsAdded -= onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved -= onChannelRemovedFromJoinedChannels; + channelManager.AvailableChannels.ItemsAdded -= availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved -= availableChannelsChanged; } } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index ca921b7882..1022edfe81 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -1,12 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; @@ -38,7 +40,7 @@ namespace osu.Game.Overlays.Dialog private bool actionInvoked; - public FontAwesome Icon + public IconUsage Icon { get => icon.Icon; set => icon.Icon = value; @@ -53,6 +55,7 @@ namespace osu.Game.Overlays.Dialog { if (text == value) return; + text = value; header.Text = value; @@ -70,6 +73,7 @@ namespace osu.Game.Overlays.Dialog set { buttonsContainer.ChildrenEnumerable = value; + foreach (PopupDialogButton b in value) { var action = b.Action; @@ -130,9 +134,9 @@ namespace osu.Game.Overlays.Dialog Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Position = new Vector2(0f, -50f), Direction = FillDirection.Vertical, Spacing = new Vector2(0f, 10f), + Padding = new MarginPadding { Bottom = 10 }, Children = new Drawable[] { new Container @@ -140,10 +144,6 @@ namespace osu.Game.Overlays.Dialog Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Size = ringSize, - Margin = new MarginPadding - { - Bottom = 30, - }, Children = new Drawable[] { ring = new CircularContainer @@ -164,28 +164,28 @@ namespace osu.Game.Overlays.Dialog { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(50), }, }, }, }, }, - header = new OsuTextFlowContainer(t => t.TextSize = 25) + header = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 25)) { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(15), TextAnchor = Anchor.TopCentre, }, - body = new OsuTextFlowContainer(t => t.TextSize = 18) + body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18)) { + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(15), - TextAnchor = Anchor.TopCentre, }, }, }, @@ -220,6 +220,7 @@ namespace osu.Game.Overlays.Dialog // press button at number if 1-9 on number row or keypad are pressed var k = e.Key; + if (k >= Key.Number1 && k <= Key.Number9) { pressButtonAtIndex(k - Key.Number1); diff --git a/osu.Game/Overlays/Dialog/PopupDialogButton.cs b/osu.Game/Overlays/Dialog/PopupDialogButton.cs index 8c382c6f56..75bae25b73 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs b/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs index 45a8dfd29e..754003c1c1 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogCancelButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs b/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs index 6c5abbe161..6c2bf6f1e6 100644 --- a/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs +++ b/osu.Game/Overlays/Dialog/PopupDialogOkButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index dae502dbd9..aaae7bcf5c 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -37,8 +37,8 @@ namespace osu.Game.Overlays dialogContainer.Add(currentDialog); currentDialog.Show(); - currentDialog.StateChanged += state => onDialogOnStateChanged(dialog, state); - State = Visibility.Visible; + currentDialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue); + Show(); } protected override bool PlaySamplesOnStateChange => false; @@ -53,7 +53,7 @@ namespace osu.Game.Overlays dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); if (dialog == currentDialog) - State = Visibility.Hidden; + Hide(); } protected override void PopIn() @@ -66,7 +66,7 @@ namespace osu.Game.Overlays { base.PopOut(); - if (currentDialog?.State == Visibility.Visible) + if (currentDialog?.State.Value == Visibility.Visible) { currentDialog.Hide(); return; diff --git a/osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs b/osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs new file mode 100644 index 0000000000..fd04a1541e --- /dev/null +++ b/osu.Game/Overlays/Direct/BeatmapDownloadTrackingComposite.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online; + +namespace osu.Game.Overlays.Direct +{ + public abstract class BeatmapDownloadTrackingComposite : DownloadTrackingComposite + { + public Bindable BeatmapSet => Model; + + protected BeatmapDownloadTrackingComposite(BeatmapSetInfo set = null) + : base(set) + { + } + } +} diff --git a/osu.Game/Overlays/Direct/DirectGridPanel.cs b/osu.Game/Overlays/Direct/DirectGridPanel.cs index 006e3a034b..5756a4593d 100644 --- a/osu.Game/Overlays/Direct/DirectGridPanel.cs +++ b/osu.Game/Overlays/Direct/DirectGridPanel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -10,10 +10,12 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Direct { @@ -29,7 +31,8 @@ namespace osu.Game.Overlays.Direct protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) + public DirectGridPanel(BeatmapSetInfo beatmap) + : base(beatmap) { Width = 380; Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image) @@ -75,13 +78,12 @@ namespace osu.Game.Overlays.Direct new OsuSpriteText { Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)), - TextSize = 18, - Font = @"Exo2.0-BoldItalic", + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, new OsuSpriteText { Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)), - Font = @"Exo2.0-BoldItalic", + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, }, }, @@ -118,29 +120,16 @@ namespace osu.Game.Overlays.Direct }, Children = new Drawable[] { - new FillFlowContainer + new LinkFlowContainer(s => { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "mapped by ", - TextSize = 14, - Shadow = false, - Colour = colours.Gray5, - }, - new OsuSpriteText - { - Text = SetInfo.Metadata.Author.Username, - TextSize = 14, - Font = @"Exo2.0-SemiBoldItalic", - Shadow = false, - Colour = colours.BlueDark, - }, - }, - }, + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText("mapped by ", t => t.Colour = colours.Gray5); + d.AddUserLink(SetInfo.Metadata.Author); + }), new Container { AutoSizeAxes = Axes.X, @@ -150,7 +139,7 @@ namespace osu.Game.Overlays.Direct new OsuSpriteText { Text = SetInfo.Metadata.Source, - TextSize = 14, + Font = OsuFont.GetFont(size: 14), Shadow = false, Colour = colours.Gray5, Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, @@ -186,11 +175,8 @@ namespace osu.Game.Overlays.Direct Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, Children = new[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) - { - Margin = new MarginPadding { Right = 1 }, - }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), }, }, statusContainer = new FillFlowContainer @@ -209,12 +195,12 @@ namespace osu.Game.Overlays.Direct if (SetInfo.OnlineInfo?.HasVideo ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_film)); + statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); } if (SetInfo.OnlineInfo?.HasStoryboard ?? false) { - statusContainer.Add(new IconPill(FontAwesome.fa_image)); + statusContainer.Add(new IconPill(FontAwesome.Solid.Image)); } statusContainer.Add(new BeatmapSetOnlineStatusPill @@ -239,6 +225,6 @@ namespace osu.Game.Overlays.Direct updateStatusContainer(); } - private void updateStatusContainer() => statusContainer.FadeTo(IsHovered || PreviewPlaying ? 0 : 1, 120, Easing.InOutQuint); + private void updateStatusContainer() => statusContainer.FadeTo(IsHovered || PreviewPlaying.Value ? 0 : 1, 120, Easing.InOutQuint); } } diff --git a/osu.Game/Overlays/Direct/DirectListPanel.cs b/osu.Game/Overlays/Direct/DirectListPanel.cs index a922ceb484..6f3b5bc5f1 100644 --- a/osu.Game/Overlays/Direct/DirectListPanel.cs +++ b/osu.Game/Overlays/Direct/DirectListPanel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -11,8 +11,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Framework.Allocation; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Direct { @@ -23,6 +26,8 @@ namespace osu.Game.Overlays.Direct private const float vertical_padding = 5; private const float height = 70; + private FillFlowContainer statusContainer; + protected DownloadButton DownloadButton; private PlayButton playButton; private Box progressBar; @@ -83,8 +88,9 @@ namespace osu.Game.Overlays.Direct { Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Size = new Vector2(height / 2), + Size = new Vector2(height / 3), FillMode = FillMode.Fit, + Margin = new MarginPadding { Right = 10 }, }, new FillFlowContainer { @@ -95,13 +101,12 @@ namespace osu.Game.Overlays.Direct new OsuSpriteText { Text = new LocalisedString((SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title)), - TextSize = 18, - Font = @"Exo2.0-BoldItalic", + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, new OsuSpriteText { Text = new LocalisedString((SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist)), - Font = @"Exo2.0-BoldItalic", + Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, } }, @@ -109,10 +114,24 @@ namespace osu.Game.Overlays.Direct }, new FillFlowContainer { - AutoSizeAxes = Axes.X, - Height = 20, - Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, - Children = GetDifficultyIcons(), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + statusContainer = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 }, + Spacing = new Vector2(5), + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + Height = 20, + Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding }, + Children = GetDifficultyIcons(), + }, + }, }, }, }, @@ -131,10 +150,10 @@ namespace osu.Game.Overlays.Direct Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, AutoSizeAxes = Axes.Both, - Child = new DownloadButton(SetInfo) + Child = DownloadButton = new DownloadButton(SetInfo) { Size = new Vector2(height - vertical_padding * 3), - Margin = new MarginPadding { Left = vertical_padding, Right = vertical_padding }, + Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding }, }, }, new FillFlowContainer @@ -145,38 +164,29 @@ namespace osu.Game.Overlays.Direct Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.fa_play_circle, SetInfo.OnlineInfo?.PlayCount ?? 0) + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), + new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new LinkFlowContainer(s => { - Margin = new MarginPadding { Right = 1 }, - }, - new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), - new FillFlowContainer + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14); + }) { 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", - }, - }, - }, + }.With(d => + { + d.AutoSizeAxes = Axes.Both; + d.AddText("mapped by "); + d.AddUserLink(SetInfo.Metadata.Author); + }), new OsuSpriteText { Text = SetInfo.Metadata.Source, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - TextSize = 14, + Font = OsuFont.GetFont(size: 14), Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, }, }, @@ -196,6 +206,23 @@ namespace osu.Game.Overlays.Direct Colour = colours.Yellow, }, }); + + if (SetInfo.OnlineInfo?.HasVideo ?? false) + { + statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + } + + if (SetInfo.OnlineInfo?.HasStoryboard ?? false) + { + statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + } + + statusContainer.Add(new BeatmapSetOnlineStatusPill + { + TextSize = 12, + TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 }, + Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, + }); } } } diff --git a/osu.Game/Overlays/Direct/DirectPanel.cs b/osu.Game/Overlays/Direct/DirectPanel.cs index 44f7b6d49c..8199d80528 100644 --- a/osu.Game/Overlays/Direct/DirectPanel.cs +++ b/osu.Game/Overlays/Direct/DirectPanel.cs @@ -1,13 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -16,8 +18,6 @@ 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.Online.API.Requests; using osuTK; using osuTK.Graphics; @@ -31,12 +31,11 @@ namespace osu.Game.Overlays.Direct private Container content; - private ProgressBar progressBar; - private BeatmapManager beatmaps; private BeatmapSetOverlay beatmapSetOverlay; public PreviewTrack Preview => PlayButton.Preview; public Bindable PreviewPlaying => PlayButton.Playing; + protected abstract PlayButton PlayButton { get; } protected abstract Box PreviewBar { get; } @@ -46,6 +45,8 @@ namespace osu.Game.Overlays.Direct protected DirectPanel(BeatmapSetInfo setInfo) { + Debug.Assert(setInfo.OnlineBeatmapSetID != null); + SetInfo = setInfo; } @@ -65,14 +66,10 @@ namespace osu.Game.Overlays.Direct Colour = Color4.Black.Opacity(0.3f), }; - private OsuColour colours; - [BackgroundDependencyLoader(permitNulls: true)] private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay) { - this.beatmaps = beatmaps; this.beatmapSetOverlay = beatmapSetOverlay; - this.colours = colours; AddInternal(content = new Container { @@ -82,40 +79,21 @@ namespace osu.Game.Overlays.Direct Children = new[] { CreateBackground(), - progressBar = new ProgressBar + new DownloadProgressBar(SetInfo) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Height = 0, - Alpha = 0, - BackgroundColour = Color4.Black.Opacity(0.7f), - FillColour = colours.Blue, Depth = -1, }, } }); - - var downloadRequest = beatmaps.GetExistingDownload(SetInfo); - - if (downloadRequest != null) - attachDownload(downloadRequest); - - beatmaps.BeatmapDownloadBegan += attachDownload; - beatmaps.ItemAdded += setAdded; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - beatmaps.BeatmapDownloadBegan -= attachDownload; - beatmaps.ItemAdded -= setAdded; } protected override void Update() { base.Update(); - if (PreviewPlaying && Preview != null && Preview.TrackLoaded) + if (PreviewPlaying.Value && Preview != null && Preview.TrackLoaded) { PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length); } @@ -135,7 +113,7 @@ namespace osu.Game.Overlays.Direct { content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint); content.MoveToY(0, hover_transition_time, Easing.OutQuint); - if (FadePlayButton && !PreviewPlaying) + if (FadePlayButton && !PreviewPlaying.Value) PlayButton.FadeOut(120, Easing.InOutQuint); base.OnHoverLost(e); @@ -143,50 +121,21 @@ namespace osu.Game.Overlays.Direct protected override bool OnClick(ClickEvent e) { - ShowInformation(); + Debug.Assert(SetInfo.OnlineBeatmapSetID != null); + beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value); return true; } - protected void ShowInformation() => beatmapSetOverlay?.ShowBeatmapSet(SetInfo); - - private void attachDownload(DownloadBeatmapSetRequest request) - { - if (request.BeatmapSet.OnlineBeatmapSetID != SetInfo.OnlineBeatmapSetID) - return; - - progressBar.FadeIn(400, Easing.OutQuint); - progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); - - progressBar.Current.Value = 0; - - request.Failure += e => - { - progressBar.Current.Value = 0; - progressBar.FadeOut(500); - }; - - request.DownloadProgressed += progress => Schedule(() => progressBar.Current.Value = progress); - - request.Success += data => - { - progressBar.Current.Value = 1; - progressBar.FillColour = colours.Yellow; - }; - } - - private void setAdded(BeatmapSetInfo s) => Schedule(() => - { - if (s.OnlineBeatmapSetID == SetInfo.OnlineBeatmapSetID) - progressBar.FadeOut(500); - }); - protected override void LoadComplete() { base.LoadComplete(); this.FadeInFromZero(200, Easing.Out); - PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered || !FadePlayButton ? 1 : 0, 120, Easing.InOutQuint); - PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint); + PreviewPlaying.ValueChanged += playing => + { + PlayButton.FadeTo(playing.NewValue || IsHovered || !FadePlayButton ? 1 : 0, 120, Easing.InOutQuint); + PreviewBar.FadeTo(playing.NewValue ? 1 : 0, 120, Easing.InOutQuint); + }; } protected List GetDifficultyIcons() @@ -213,7 +162,7 @@ namespace osu.Game.Overlays.Direct public int Value { - get { return value; } + get => value; set { this.value = value; @@ -221,7 +170,7 @@ namespace osu.Game.Overlays.Direct } } - public Statistic(FontAwesome icon, int value = 0) + public Statistic(IconUsage icon, int value = 0) { Anchor = Anchor.TopRight; Origin = Anchor.TopRight; @@ -231,10 +180,7 @@ namespace osu.Game.Overlays.Direct Children = new Drawable[] { - text = new OsuSpriteText - { - Font = @"Exo2.0-SemiBoldItalic", - }, + text = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.SemiBold, italics: true) }, new SpriteIcon { Icon = icon, diff --git a/osu.Game/Overlays/Direct/DownloadButton.cs b/osu.Game/Overlays/Direct/DownloadButton.cs index e326f5e592..81709187e7 100644 --- a/osu.Game/Overlays/Direct/DownloadButton.cs +++ b/osu.Game/Overlays/Direct/DownloadButton.cs @@ -1,107 +1,131 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; using osuTK; namespace osu.Game.Overlays.Direct { - public class DownloadButton : OsuAnimatedButton + public class DownloadButton : BeatmapDownloadTrackingComposite { - private readonly BeatmapSetInfo beatmapSet; + protected bool DownloadEnabled => button.Enabled.Value; + + private readonly bool noVideo; private readonly SpriteIcon icon; private readonly SpriteIcon checkmark; - private readonly BeatmapSetDownloader downloader; private readonly Box background; private OsuColour colours; + private readonly ShakeContainer shakeContainer; + private readonly OsuAnimatedButton button; public DownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) + : base(beatmapSet) { - this.beatmapSet = beatmapSet; + this.noVideo = noVideo; - AddRange(new Drawable[] + InternalChild = shakeContainer = new ShakeContainer { - downloader = new BeatmapSetDownloader(beatmapSet, noVideo), - background = new Box + RelativeSizeAxes = Axes.Both, + Child = button = new OsuAnimatedButton { RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue - }, - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(13), - Icon = FontAwesome.fa_download, - }, - checkmark = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - X = 8, - Size = Vector2.Zero, - Icon = FontAwesome.fa_check, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(13), + Icon = FontAwesome.Solid.Download, + }, + checkmark = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = 8, + Size = Vector2.Zero, + Icon = FontAwesome.Solid.Check, + } + } } - }); + }; } protected override void LoadComplete() { base.LoadComplete(); - downloader.DownloadState.BindValueChanged(updateState, true); + + State.BindValueChanged(state => updateState(state.NewValue), true); FinishTransforms(true); } - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, OsuGame game) + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, OsuGame game, BeatmapManager beatmaps) { this.colours = colours; - Action = () => + if (BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false) { - switch (downloader.DownloadState.Value) + button.Enabled.Value = false; + button.TooltipText = "This beatmap is currently not available for download."; + return; + } + + button.Action = () => + { + switch (State.Value) { - case BeatmapSetDownloader.DownloadStatus.Downloading: - // todo: replace with ShakeContainer after https://github.com/ppy/osu/pull/2909 is merged. - Content.MoveToX(-5, 50, Easing.OutSine).Then() - .MoveToX(5, 100, Easing.InOutSine).Then() - .MoveToX(-5, 100, Easing.InOutSine).Then() - .MoveToX(0, 50, Easing.InSine); + case DownloadState.Downloading: + case DownloadState.Downloaded: + shakeContainer.Shake(); break; - case BeatmapSetDownloader.DownloadStatus.Downloaded: - game.PresentBeatmap(beatmapSet); + + case DownloadState.LocallyAvailable: + game.PresentBeatmap(BeatmapSet.Value); break; + default: - downloader.Download(); + beatmaps.Download(BeatmapSet.Value, noVideo); break; } }; } - private void updateState(BeatmapSetDownloader.DownloadStatus state) + private void updateState(DownloadState state) { switch (state) { - case BeatmapSetDownloader.DownloadStatus.NotDownloaded: + case DownloadState.NotDownloaded: background.FadeColour(colours.Gray4, 500, Easing.InOutExpo); icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); break; - case BeatmapSetDownloader.DownloadStatus.Downloading: + case DownloadState.Downloading: background.FadeColour(colours.Blue, 500, Easing.InOutExpo); icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); break; - case BeatmapSetDownloader.DownloadStatus.Downloaded: + case DownloadState.Downloaded: + background.FadeColour(colours.Yellow, 500, Easing.InOutExpo); + break; + + case DownloadState.LocallyAvailable: background.FadeColour(colours.Green, 500, Easing.InOutExpo); icon.MoveToX(-8, 500, Easing.InOutExpo); checkmark.ScaleTo(new Vector2(13), 500, Easing.InOutExpo); diff --git a/osu.Game/Overlays/Direct/DownloadProgressBar.cs b/osu.Game/Overlays/Direct/DownloadProgressBar.cs new file mode 100644 index 0000000000..a6cefaae84 --- /dev/null +++ b/osu.Game/Overlays/Direct/DownloadProgressBar.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Direct +{ + public class DownloadProgressBar : BeatmapDownloadTrackingComposite + { + private readonly ProgressBar progressBar; + + public DownloadProgressBar(BeatmapSetInfo beatmapSet) + : base(beatmapSet) + { + AddInternal(progressBar = new ProgressBar + { + Height = 0, + Alpha = 0, + }); + + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours) + { + progressBar.FillColour = colours.Blue; + progressBar.BackgroundColour = Color4.Black.Opacity(0.7f); + progressBar.Current = Progress; + + State.BindValueChanged(state => + { + switch (state.NewValue) + { + case DownloadState.NotDownloaded: + progressBar.Current.Value = 0; + progressBar.FadeOut(500); + break; + + case DownloadState.Downloading: + progressBar.FadeIn(400, Easing.OutQuint); + progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); + break; + + case DownloadState.Downloaded: + progressBar.FadeIn(400, Easing.OutQuint); + progressBar.ResizeHeightTo(4, 400, Easing.OutQuint); + + progressBar.Current.Value = 1; + progressBar.FillColour = colours.Yellow; + break; + + case DownloadState.LocallyAvailable: + progressBar.FadeOut(500); + break; + } + }, true); + } + } +} diff --git a/osu.Game/Overlays/Direct/FilterControl.cs b/osu.Game/Overlays/Direct/FilterControl.cs index 3f2c3a4fbf..268e011350 100644 --- a/osu.Game/Overlays/Direct/FilterControl.cs +++ b/osu.Game/Overlays/Direct/FilterControl.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Direct { DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark; - Ruleset.Value = ruleset ?? rulesets.GetRuleset(0); + Ruleset.Value = ruleset.Value ?? rulesets.GetRuleset(0); foreach (var r in rulesets.AvailableRulesets) modeButtons.Add(new RulesetToggleButton(Ruleset, r)); } @@ -68,9 +68,9 @@ namespace osu.Game.Overlays.Direct private readonly ConstrainedIconContainer iconContainer; - private void Bindable_ValueChanged(RulesetInfo obj) + private void Bindable_ValueChanged(ValueChangedEvent e) { - iconContainer.FadeTo(Ruleset.ID == obj?.ID ? 1f : 0.5f, 100); + iconContainer.FadeTo(Ruleset.ID == e.NewValue?.ID ? 1f : 0.5f, 100); } public override bool HandleNonPositionalInput => !bindable.Disabled && base.HandleNonPositionalInput; @@ -93,7 +93,7 @@ namespace osu.Game.Overlays.Direct Ruleset = ruleset; bindable.ValueChanged += Bindable_ValueChanged; - Bindable_ValueChanged(bindable.Value); + Bindable_ValueChanged(new ValueChangedEvent(bindable.Value, bindable.Value)); Action = () => bindable.Value = Ruleset; } @@ -116,5 +116,6 @@ namespace osu.Game.Overlays.Direct Ranked, Rating, Plays, + Favourites, } } diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index 2ca7b558ba..80870dcb68 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; using osuTK.Graphics; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.SearchableList; @@ -15,8 +16,8 @@ namespace osu.Game.Overlays.Direct protected override Color4 BackgroundColour => OsuColour.FromHex(@"252f3a"); protected override DirectTab DefaultTab => DirectTab.Search; - protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", TextSize = 25 }; - protected override FontAwesome Icon => FontAwesome.fa_osu_chevron_down_o; + protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", Font = OsuFont.GetFont(size: 25) }; + protected override IconUsage Icon => OsuIcon.ChevronDownCircle; public Header() { @@ -28,10 +29,13 @@ namespace osu.Game.Overlays.Direct public enum DirectTab { Search, + [Description("Newest Maps")] NewestMaps = DirectSortCriteria.Ranked, + [Description("Top Rated")] TopRated = DirectSortCriteria.Rating, + [Description("Most Played")] MostPlayed = DirectSortCriteria.Plays, } diff --git a/osu.Game/Overlays/Direct/IconPill.cs b/osu.Game/Overlays/Direct/IconPill.cs index 9ac544ea78..d63bb2a292 100644 --- a/osu.Game/Overlays/Direct/IconPill.cs +++ b/osu.Game/Overlays/Direct/IconPill.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -12,7 +12,15 @@ namespace osu.Game.Overlays.Direct { public class IconPill : CircularContainer { - public IconPill(FontAwesome icon) + public Vector2 IconSize + { + get => iconContainer.Size; + set => iconContainer.Size = value; + } + + private readonly Container iconContainer; + + public IconPill(IconUsage icon) { AutoSizeAxes = Axes.Both; Masking = true; @@ -25,16 +33,16 @@ namespace osu.Game.Overlays.Direct Colour = Color4.Black, Alpha = 0.5f, }, - new Container + iconContainer = new Container { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding(5), + Size = new Vector2(22), + Padding = new MarginPadding(5), Child = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, Icon = icon, - Size = new Vector2(12), }, }, }; diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index d9b36e2367..2a77e7ca26 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -24,10 +25,11 @@ namespace osu.Game.Overlays.Direct public BeatmapSetInfo BeatmapSet { - get { return beatmapSet; } + get => beatmapSet; set { if (value == beatmapSet) return; + beatmapSet = value; Preview?.Stop(); @@ -72,7 +74,7 @@ namespace osu.Game.Overlays.Direct Origin = Anchor.Centre, FillMode = FillMode.Fit, RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_play, + Icon = FontAwesome.Solid.Play, }, loadingAnimation = new LoadingAnimation { @@ -112,12 +114,12 @@ namespace osu.Game.Overlays.Direct base.OnHoverLost(e); } - private void playingStateChanged(bool playing) + private void playingStateChanged(ValueChangedEvent e) { - icon.Icon = playing ? FontAwesome.fa_stop : FontAwesome.fa_play; - icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); + icon.Icon = e.NewValue ? FontAwesome.Solid.Stop : FontAwesome.Solid.Play; + icon.FadeColour(e.NewValue || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint); - if (playing) + if (e.NewValue) { if (BeatmapSet == null) { @@ -127,7 +129,7 @@ namespace osu.Game.Overlays.Direct if (Preview != null) { - Preview.Start(); + attemptStart(); return; } @@ -144,8 +146,8 @@ namespace osu.Game.Overlays.Direct preview.Stopped += () => Playing.Value = false; // user may have changed their mind. - if (Playing) - preview.Start(); + if (Playing.Value) + attemptStart(); }); } else @@ -155,6 +157,12 @@ namespace osu.Game.Overlays.Direct } } + private void attemptStart() + { + if (Preview?.Start() != true) + Playing.Value = false; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 765c86e6ad..7dcf76e41f 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Humanizer; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; @@ -13,7 +14,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Direct; using osu.Game.Overlays.SearchableList; @@ -27,7 +27,6 @@ namespace osu.Game.Overlays { private const float panel_padding = 10f; - private APIAccess api; private RulesetStore rulesets; private readonly FillFlowContainer resultCountsContainer; @@ -45,7 +44,7 @@ namespace osu.Game.Overlays public IEnumerable BeatmapSets { - get { return beatmapSets; } + get => beatmapSets; set { if (beatmapSets?.Equals(value) ?? false) return; @@ -57,6 +56,7 @@ namespace osu.Game.Overlays var artists = new List(); var songs = new List(); var tags = new List(); + foreach (var s in beatmapSets) { artists.Add(s.Metadata.Artist); @@ -72,10 +72,11 @@ namespace osu.Game.Overlays public ResultCounts ResultAmounts { - get { return resultAmounts; } + get => resultAmounts; set { if (value == ResultAmounts) return; + resultAmounts = value; updateResultCounts(); @@ -84,8 +85,6 @@ namespace osu.Game.Overlays public DirectOverlay() { - RelativeSizeAxes = Axes.Both; - // osu!direct colours are not part of the standard palette Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2"); @@ -105,12 +104,11 @@ namespace osu.Game.Overlays new OsuSpriteText { Text = "Found ", - TextSize = 15, + Font = OsuFont.GetFont(size: 15) }, resultCountsText = new OsuSpriteText { - TextSize = 15, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold) }, } }, @@ -118,7 +116,7 @@ namespace osu.Game.Overlays Filter.Search.Current.ValueChanged += text => { - if (text != string.Empty) + if (text.NewValue != string.Empty) { Header.Tabs.Current.Value = DirectTab.Search; @@ -133,52 +131,38 @@ namespace osu.Game.Overlays Filter.Tabs.Current.Value = DirectSortCriteria.Ranked; } }; - ((FilterControl)Filter).Ruleset.ValueChanged += ruleset => Scheduler.AddOnce(updateSearch); - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += rankStatus => Scheduler.AddOnce(updateSearch); + ((FilterControl)Filter).Ruleset.ValueChanged += _ => queueUpdateSearch(); + Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdateSearch(); Header.Tabs.Current.ValueChanged += tab => { - if (tab != DirectTab.Search) + if (tab.NewValue != DirectTab.Search) { currentQuery.Value = string.Empty; Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value; - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); } }; - currentQuery.ValueChanged += v => - { - queryChangedDebounce?.Cancel(); - - if (string.IsNullOrEmpty(v)) - Scheduler.AddOnce(updateSearch); - else - { - BeatmapSets = null; - ResultAmounts = null; - - queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); - } - }; + currentQuery.ValueChanged += text => queueUpdateSearch(!string.IsNullOrEmpty(text.NewValue)); currentQuery.BindTo(Filter.Search.Current); - Filter.Tabs.Current.ValueChanged += sortCriteria => + Filter.Tabs.Current.ValueChanged += tab => { - if (Header.Tabs.Current.Value != DirectTab.Search && sortCriteria != (DirectSortCriteria)Header.Tabs.Current.Value) + if (Header.Tabs.Current.Value != DirectTab.Search && tab.NewValue != (DirectSortCriteria)Header.Tabs.Current.Value) Header.Tabs.Current.Value = DirectTab.Search; - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); }; updateResultCounts(); } [BackgroundDependencyLoader] - private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, PreviewTrackManager previewTrackManager) + private void load(OsuColour colours, RulesetStore rulesets, PreviewTrackManager previewTrackManager) { - this.api = api; this.rulesets = rulesets; this.previewTrackManager = previewTrackManager; @@ -190,14 +174,9 @@ namespace osu.Game.Overlays resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint); if (ResultAmounts == null) return; - resultCountsText.Text = pluralize("Artist", ResultAmounts.Artists) + ", " + - pluralize("Song", ResultAmounts.Songs) + ", " + - pluralize("Tag", ResultAmounts.Tags); - } - - private string pluralize(string prefix, int value) - { - return $@"{value} {prefix}" + (value == 1 ? string.Empty : @"s"); + resultCountsText.Text = "Artist".ToQuantity(ResultAmounts.Artists) + ", " + + "Song".ToQuantity(ResultAmounts.Songs) + ", " + + "Tag".ToQuantity(ResultAmounts.Tags); } private void recreatePanels(PanelDisplayStyle displayStyle) @@ -227,6 +206,7 @@ namespace osu.Game.Overlays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }; + default: return new DirectListPanel(b); } @@ -246,40 +226,42 @@ namespace osu.Game.Overlays // Queries are allowed to be run only on the first pop-in if (getSetsRequest == null) - Scheduler.AddOnce(updateSearch); + queueUpdateSearch(); } private SearchBeatmapSetsRequest getSetsRequest; - private readonly Bindable currentQuery = new Bindable(); + private readonly Bindable currentQuery = new Bindable(string.Empty); private ScheduledDelegate queryChangedDebounce; private PreviewTrackManager previewTrackManager; - private void updateSearch() + private void queueUpdateSearch(bool queryTextChanged = false) { - queryChangedDebounce?.Cancel(); - - if (!IsLoaded) - return; - - if (State == Visibility.Hidden) - return; - BeatmapSets = null; ResultAmounts = null; getSetsRequest?.Cancel(); - if (api == null) + queryChangedDebounce?.Cancel(); + queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100); + } + + private void updateSearch() + { + if (!IsLoaded) return; - if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) + if (State.Value == Visibility.Hidden) + return; + + if (API == null) return; previewTrackManager.StopAnyPlaying(this); - getSetsRequest = new SearchBeatmapSetsRequest(currentQuery.Value ?? string.Empty, + getSetsRequest = new SearchBeatmapSetsRequest( + currentQuery.Value, ((FilterControl)Filter).Ruleset.Value, Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.Tabs.Current.Value); //todo: sort direction (?) @@ -299,7 +281,7 @@ namespace osu.Game.Overlays }); }; - api.Queue(getSetsRequest); + API.Queue(getSetsRequest); } private int distinctCount(List list) => list.Distinct().ToArray().Length; diff --git a/osu.Game/Overlays/FullscreenOverlay.cs b/osu.Game/Overlays/FullscreenOverlay.cs new file mode 100644 index 0000000000..0911ee84de --- /dev/null +++ b/osu.Game/Overlays/FullscreenOverlay.cs @@ -0,0 +1,89 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public abstract class FullscreenOverlay : WaveOverlayContainer, IOnlineComponent + { + [Resolved] + protected IAPIProvider API { get; private set; } + + protected FullscreenOverlay() + { + Waves.FirstWaveColour = OsuColour.Gray(0.4f); + Waves.SecondWaveColour = OsuColour.Gray(0.3f); + Waves.ThirdWaveColour = OsuColour.Gray(0.2f); + Waves.FourthWaveColour = OsuColour.Gray(0.1f); + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Width = 0.85f; + Anchor = Anchor.TopCentre; + Origin = Anchor.TopCentre; + + Masking = true; + + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0), + Type = EdgeEffectType.Shadow, + Radius = 10 + }; + } + + public override void Show() + { + if (State.Value == Visibility.Visible) + { + // re-trigger the state changed so we can potentially surface to front + State.TriggerChange(); + } + else + { + base.Show(); + } + } + + protected override void PopIn() + { + base.PopIn(); + FadeEdgeEffectTo(0.4f, WaveContainer.APPEAR_DURATION, Easing.Out); + } + + protected override void PopOut() + { + base.PopOut(); + FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.In).OnComplete(_ => PopOutComplete()); + } + + protected virtual void PopOutComplete() + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + API.Register(this); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + API?.Unregister(this); + } + + public virtual void APIStateChanged(IAPIProvider api, APIState state) + { + } + } +} diff --git a/osu.Game/Overlays/HoldToConfirmOverlay.cs b/osu.Game/Overlays/HoldToConfirmOverlay.cs index b2c1ad20be..fdc6f096bc 100644 --- a/osu.Game/Overlays/HoldToConfirmOverlay.cs +++ b/osu.Game/Overlays/HoldToConfirmOverlay.cs @@ -1,7 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; @@ -11,12 +13,17 @@ namespace osu.Game.Overlays { /// /// An overlay which will display a black screen that dims over a period before confirming an exit action. - /// Action is BYO (derived class will need to call and from a user event). + /// Action is BYO (derived class will need to call and from a user event). /// public abstract class HoldToConfirmOverlay : HoldToConfirmContainer { private Box overlay; + private readonly BindableDouble audioVolume = new BindableDouble(1); + + [Resolved] + private AudioManager audio { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -33,7 +40,19 @@ namespace osu.Game.Overlays } }; - Progress.ValueChanged += v => overlay.Alpha = (float)v; + Progress.ValueChanged += p => + { + audioVolume.Value = 1 - p.NewValue; + overlay.Alpha = (float)p.NewValue; + }; + + audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioVolume); + } + + protected override void Dispose(bool isDisposing) + { + audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, audioVolume); + base.Dispose(isDisposing); } } } diff --git a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs index b939483cd8..7e33d7ba27 100644 --- a/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/GlobalKeyBindingsSection.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Overlays.Settings; @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.KeyBinding { public class GlobalKeyBindingsSection : SettingsSection { - public override FontAwesome Icon => FontAwesome.fa_osu_hot; + public override IconUsage Icon => FontAwesome.Solid.Globe; public override string Header => "Global"; public GlobalKeyBindingsSection(GlobalActionContainer manager) @@ -18,7 +18,6 @@ namespace osu.Game.Overlays.KeyBinding Add(new InGameKeyBindingsSubsection(manager)); } - private class DefaultBindingsSubsection : KeyBindingsSubsection { protected override string Header => string.Empty; @@ -34,7 +33,8 @@ namespace osu.Game.Overlays.KeyBinding { protected override string Header => "In Game"; - public InGameKeyBindingsSubsection(GlobalActionContainer manager) : base(null) + public InGameKeyBindingsSubsection(GlobalActionContainer manager) + : base(null) { Defaults = manager.InGameKeyBindings; } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index fe2903d393..8317951c8a 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -8,13 +8,15 @@ using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Input; +using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -35,7 +37,7 @@ namespace osu.Game.Overlays.KeyBinding public bool MatchingFilter { - get { return matchingFilter; } + get => matchingFilter; set { matchingFilter = value; @@ -43,8 +45,10 @@ namespace osu.Game.Overlays.KeyBinding } } + public bool FilteringActive { get; set; } + private OsuSpriteText text; - private OsuTextFlowContainer pressAKey; + private Drawable pressAKey; private FillFlowContainer buttons; @@ -77,7 +81,7 @@ namespace osu.Game.Overlays.KeyBinding Hollow = true, }; - Children = new Drawable[] + Children = new[] { new Box { @@ -96,15 +100,19 @@ namespace osu.Game.Overlays.KeyBinding Anchor = Anchor.TopRight, Origin = Anchor.TopRight }, - pressAKey = new OsuTextFlowContainer + pressAKey = new FillFlowContainer { - Text = "Press a key to change binding, Shift+Delete to delete, Escape to cancel.", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding(padding), - Padding = new MarginPadding { Top = height }, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(padding) { Top = height + padding * 2 }, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Alpha = 0, - Colour = colours.YellowDark + Spacing = new Vector2(5), + Children = new Drawable[] + { + new CancelButton { Action = finalise }, + new ClearButton { Action = clear }, + }, } }; @@ -115,6 +123,7 @@ namespace osu.Game.Overlays.KeyBinding public void RestoreDefaults() { int i = 0; + foreach (var d in Defaults) { var button = buttons[i++]; @@ -201,21 +210,6 @@ namespace osu.Game.Overlays.KeyBinding if (!HasFocus) return false; - switch (e.Key) - { - case Key.Delete: - { - if (e.ShiftPressed) - { - bindTarget.UpdateKeyCombination(InputKey.None); - finalise(); - return true; - } - - break; - } - } - bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); if (!isModifier(e.Key)) finalise(); @@ -250,6 +244,12 @@ namespace osu.Game.Overlays.KeyBinding return true; } + private void clear() + { + bindTarget.UpdateKeyCombination(InputKey.None); + finalise(); + } + private void finalise() { if (bindTarget != null) @@ -296,6 +296,41 @@ namespace osu.Game.Overlays.KeyBinding if (bindTarget != null) bindTarget.IsBinding = true; } + private class CancelButton : TriangleButton + { + public CancelButton() + { + Text = "Cancel"; + Size = new Vector2(80, 20); + } + } + + private class ClearButton : TriangleButton + { + public ClearButton() + { + Text = "Clear"; + Size = new Vector2(80, 20); + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + base.OnMouseUp(e); + + // without this, the mouse up triggers a finalise (and deselection) of the current binding target. + return true; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Pink; + + Triangles.ColourDark = colours.PinkDark; + Triangles.ColourLight = colours.PinkLight; + } + } + private class KeyButton : Container { public readonly Framework.Input.Bindings.KeyBinding KeyBinding; @@ -309,10 +344,11 @@ namespace osu.Game.Overlays.KeyBinding public bool IsBinding { - get { return isBinding; } + get => isBinding; set { if (value == isBinding) return; + isBinding = value; updateHoverState(); @@ -349,8 +385,7 @@ namespace osu.Game.Overlays.KeyBinding }, Text = new OsuSpriteText { - Font = "Venera", - TextSize = 10, + Font = OsuFont.Numeric.With(size: 10), Margin = new MarginPadding(5), Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs index 9e57b9eefa..d784b7aec9 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingsSubsection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.KeyBinding this.variant = variant; FlowContent.Spacing = new Vector2(0, 1); - FlowContent.Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; + FlowContent.Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; } [BackgroundDependencyLoader] @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.KeyBinding [BackgroundDependencyLoader] private void load(OsuColour colours) { - Text = "Reset"; + Text = "Reset all bindings in section"; RelativeSizeAxes = Axes.X; Margin = new MarginPadding { Top = 5 }; Height = 20; diff --git a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs index c4405216e3..1f4042c57c 100644 --- a/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.cs +++ b/osu.Game/Overlays/KeyBinding/RulesetBindingsSection.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets; @@ -9,7 +10,7 @@ namespace osu.Game.Overlays.KeyBinding { public class RulesetBindingsSection : SettingsSection { - public override FontAwesome Icon => FontAwesome.fa_osu_hot; + public override IconUsage Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? OsuIcon.Hot; public override string Header => ruleset.Name; private readonly RulesetInfo ruleset; diff --git a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs index 4f33695e5d..07af657686 100644 --- a/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs +++ b/osu.Game/Overlays/KeyBinding/VariantBindingsSubsection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets; diff --git a/osu.Game/Overlays/KeyBindingOverlay.cs b/osu.Game/Overlays/KeyBindingPanel.cs similarity index 71% rename from osu.Game/Overlays/KeyBindingOverlay.cs rename to osu.Game/Overlays/KeyBindingPanel.cs index 06432cfcea..928bd080fa 100644 --- a/osu.Game/Overlays/KeyBindingOverlay.cs +++ b/osu.Game/Overlays/KeyBindingPanel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,7 +10,7 @@ using osu.Game.Rulesets; namespace osu.Game.Overlays { - public class KeyBindingOverlay : SettingsOverlay + public class KeyBindingPanel : SettingsSubPanel { protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); @@ -22,10 +22,5 @@ namespace osu.Game.Overlays foreach (var ruleset in rulesets.AvailableRulesets) AddSection(new RulesetBindingsSection(ruleset)); } - - public KeyBindingOverlay() - : base(false) - { - } } } diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index e094f8ef48..d0411ba9e7 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,6 +10,7 @@ using osuTK.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; +using System; namespace osu.Game.Overlays { @@ -19,6 +20,11 @@ namespace osu.Game.Overlays private const float transition_time = 400; + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + public LoginOverlay() { AutoSizeAxes = Axes.Both; @@ -88,5 +94,12 @@ namespace osu.Game.Overlays settingsSection.Bounding = false; this.FadeOut(transition_time); } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } } } diff --git a/osu.Game/Overlays/MainSettings.cs b/osu.Game/Overlays/MainSettings.cs deleted file mode 100644 index cd9cf8d50e..0000000000 --- a/osu.Game/Overlays/MainSettings.cs +++ /dev/null @@ -1,167 +0,0 @@ -// 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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Input.Bindings; -using osu.Game.Overlays.Settings; -using osu.Game.Overlays.Settings.Sections; -using osu.Game.Screens.Ranking; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays -{ - public class MainSettings : SettingsOverlay - { - private readonly KeyBindingOverlay keyBindingOverlay; - private BackButton backButton; - - protected override IEnumerable CreateSections() => new SettingsSection[] - { - new GeneralSection(), - new GraphicsSection(), - new GameplaySection(), - new AudioSection(), - new SkinSection(), - new InputSection(keyBindingOverlay), - new OnlineSection(), - new MaintenanceSection(), - new DebugSection(), - }; - - protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); - protected override Drawable CreateFooter() => new SettingsFooter(); - - public MainSettings() - : base(true) - { - keyBindingOverlay = new KeyBindingOverlay - { - Depth = 1, - Anchor = Anchor.TopRight, - }; - keyBindingOverlay.StateChanged += keyBindingOverlay_StateChanged; - } - - public override bool AcceptsFocus => keyBindingOverlay.State != Visibility.Visible; - - private const float hidden_width = 120; - - private void keyBindingOverlay_StateChanged(Visibility visibility) - { - switch (visibility) - { - case Visibility.Visible: - Background.FadeTo(0.9f, 300, Easing.OutQuint); - Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); - - SectionsContainer.FadeOut(300, Easing.OutQuint); - ContentContainer.MoveToX(hidden_width - WIDTH, 500, Easing.OutQuint); - - backButton.Delay(100).FadeIn(100); - break; - case Visibility.Hidden: - Background.FadeTo(0.6f, 500, Easing.OutQuint); - Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); - - SectionsContainer.FadeIn(500, Easing.OutQuint); - ContentContainer.MoveToX(0, 500, Easing.OutQuint); - - backButton.FadeOut(100); - break; - } - } - - protected override float ExpandedPosition => keyBindingOverlay.State == Visibility.Visible ? hidden_width - WIDTH : base.ExpandedPosition; - - [BackgroundDependencyLoader] - private void load() - { - ContentContainer.Add(keyBindingOverlay); - - ContentContainer.Add(backButton = new BackButton - { - Alpha = 0, - Width = hidden_width, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Action = () => keyBindingOverlay.Hide() - }); - } - - private class BackButton : OsuClickableContainer, IKeyBindingHandler - { - private AspectContainer aspect; - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - aspect = new AspectContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -15, - Size = new Vector2(15), - Shadow = true, - Icon = FontAwesome.fa_chevron_left - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 15, - TextSize = 12, - Font = @"Exo2.0-Bold", - Text = @"back", - }, - } - } - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - aspect.ScaleTo(1, 1000, Easing.OutElastic); - return base.OnMouseUp(e); - } - - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - Click(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => false; - } - } -} diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 85875d2052..1f15c773f4 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Textures; using osuTK.Input; using osu.Framework.Graphics.Shapes; using System; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.MathUtils; @@ -144,7 +145,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(OsuColour colours, TextureStore textures, AudioManager audio) { - getSample = audio.Sample.Get(@"MedalSplash/medal-get"); + getSample = audio.Samples.Get(@"MedalSplash/medal-get"); innerSpin.Texture = outerSpin.Texture = textures.Get(@"MedalSplash/disc-spin"); disc.EdgeEffect = leftStrip.EdgeEffect = rightStrip.EdgeEffect = new EdgeEffectParameters @@ -219,13 +220,11 @@ namespace osu.Game.Overlays { if (drawableMedal.State != DisplayState.Full) drawableMedal.State = DisplayState.Icon; - }) - .Delay(step_duration).Schedule(() => + }).Delay(step_duration).Schedule(() => { if (drawableMedal.State != DisplayState.Full) drawableMedal.State = DisplayState.MedalUnlocked; - }) - .Delay(step_duration).Schedule(() => + }).Delay(step_duration).Schedule(() => { if (drawableMedal.State != DisplayState.Full) drawableMedal.State = DisplayState.Full; diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 87d5724d24..f1ae5d64f5 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework; @@ -29,6 +29,7 @@ namespace osu.Game.Overlays.MedalSplash private readonly OsuSpriteText unlocked, name; private readonly TextFlowContainer description; private DisplayState state; + public DrawableMedal(Medal medal) { this.medal = medal; @@ -63,8 +64,7 @@ namespace osu.Game.Overlays.MedalSplash Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Medal Unlocked".ToUpperInvariant(), - TextSize = 24, - Font = @"Exo2.0-Light", + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), Alpha = 0f, Scale = new Vector2(1f / scale_when_unlocked), }, @@ -84,8 +84,7 @@ namespace osu.Game.Overlays.MedalSplash Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = medal.Name, - TextSize = 20, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), Alpha = 0f, Scale = new Vector2(1f / scale_when_full), }, @@ -107,10 +106,10 @@ namespace osu.Game.Overlays.MedalSplash { s.Anchor = Anchor.TopCentre; s.Origin = Anchor.TopCentre; - s.TextSize = 16; + s.Font = s.Font.With(size: 16); }); - medalContainer.OnLoadComplete = d => + medalContainer.OnLoadComplete += d => { unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10); infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90); @@ -134,7 +133,7 @@ namespace osu.Game.Overlays.MedalSplash public DisplayState State { - get { return state; } + get => state; set { if (state == value) return; @@ -157,11 +156,13 @@ namespace osu.Game.Overlays.MedalSplash case DisplayState.None: medalContainer.ScaleTo(0); break; + case DisplayState.Icon: medalContainer .FadeIn(duration) .ScaleTo(1, duration, Easing.OutElastic); break; + case DisplayState.MedalUnlocked: medalContainer .FadeTo(1) @@ -171,6 +172,7 @@ namespace osu.Game.Overlays.MedalSplash this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 30, duration, Easing.OutExpo); unlocked.FadeInFromZero(duration); break; + case DisplayState.Full: medalContainer .FadeTo(1) diff --git a/osu.Game/Overlays/Mods/ModButton.cs b/osu.Game/Overlays/Mods/ModButton.cs index 239688e284..fa1ee500a8 100644 --- a/osu.Game/Overlays/Mods/ModButton.cs +++ b/osu.Game/Overlays/Mods/ModButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -14,6 +14,7 @@ using System; using System.Linq; using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Mods @@ -80,6 +81,7 @@ namespace osu.Game.Overlays.Mods backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing); backgroundIcon.Icon = modAfter.Icon; + using (BeginDelayedSequence(mod_switch_duration, true)) { foregroundIcon @@ -106,10 +108,11 @@ namespace osu.Game.Overlays.Mods public Color4 SelectedColour { - get { return selectedColour; } + get => selectedColour; set { if (value == selectedColour) return; + selectedColour = value; if (Selected) foregroundIcon.Colour = value; } @@ -120,7 +123,7 @@ namespace osu.Game.Overlays.Mods public Mod Mod { - get { return mod; } + get => mod; set { mod = value; @@ -137,6 +140,7 @@ namespace osu.Game.Overlays.Mods } createIcons(); + if (Mods.Length > 0) { displayMod(Mods[0]); @@ -166,6 +170,7 @@ namespace osu.Game.Overlays.Mods case MouseButton.Left: SelectNext(1); break; + case MouseButton.Right: SelectNext(-1); break; @@ -217,6 +222,7 @@ namespace osu.Game.Overlays.Mods private void createIcons() { iconsContainer.Clear(); + if (Mods.Length > 1) { iconsContainer.AddRange(new[] @@ -275,7 +281,7 @@ namespace osu.Game.Overlays.Mods Y = 75, Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - TextSize = 18, + Font = OsuFont.GetFont(size: 18) }, new HoverClickSounds() }; diff --git a/osu.Game/Overlays/Mods/ModButtonEmpty.cs b/osu.Game/Overlays/Mods/ModButtonEmpty.cs index dfe14e066e..03afe5adba 100644 --- a/osu.Game/Overlays/Mods/ModButtonEmpty.cs +++ b/osu.Game/Overlays/Mods/ModButtonEmpty.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 8ac98ddff6..dedd397fa5 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Input; @@ -10,7 +10,9 @@ using osu.Game.Rulesets.Mods; using System; using System.Linq; using System.Collections.Generic; +using System.Threading; using osu.Framework.Input.Events; +using osu.Game.Graphics; namespace osu.Game.Overlays.Mods { @@ -32,6 +34,13 @@ namespace osu.Game.Overlays.Mods public IEnumerable SelectedMods => buttons.Select(b => b.SelectedMod).Where(m => m != null); + private CancellationTokenSource modsLoadCts; + + /// + /// True when all mod icons have completed loading. + /// + public bool ModIconsLoaded { get; private set; } = true; + public IEnumerable Mods { set @@ -47,8 +56,28 @@ namespace osu.Game.Overlays.Mods }; }).ToArray(); - ButtonsContainer.Children = modContainers; + modsLoadCts?.Cancel(); + ModIconsLoaded = false; + + LoadComponentsAsync(modContainers, c => + { + ModIconsLoaded = true; + ButtonsContainer.ChildrenEnumerable = c; + }, (modsLoadCts = new CancellationTokenSource()).Token); + buttons = modContainers.OfType().ToArray(); + + if (value.Any()) + { + headerLabel.FadeIn(200); + this.FadeIn(200); + } + else + { + // transition here looks weird as mods instantly disappear. + headerLabel.Hide(); + Hide(); + } } } @@ -76,10 +105,12 @@ namespace osu.Game.Overlays.Mods public void DeselectTypes(IEnumerable modTypes, bool immediate = false) { int delay = 0; + foreach (var button in buttons) { Mod selected = button.SelectedMod; if (selected == null) continue; + foreach (var type in modTypes) if (type.IsInstanceOfType(selected)) { @@ -123,7 +154,7 @@ namespace osu.Game.Overlays.Mods Origin = Anchor.TopLeft, Anchor = Anchor.TopLeft, Position = new Vector2(0f, 0f), - Font = @"Exo2.0-Bold" + Font = OsuFont.GetFont(weight: FontWeight.Bold) }, ButtonsContainer = new FillFlowContainer { diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 742a3830b4..9ff320841a 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -1,201 +1,59 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osuTK; -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Mods; using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; -using osu.Game.Rulesets; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods.Sections; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.Mods { public class ModSelectOverlay : WaveOverlayContainer { - /// - /// How much this container should overflow the sides of the screen to account for parallax shifting. - /// - private const float overflow_padding = 50; - - private const float content_width = 0.8f; - - protected Color4 LowMultiplierColour, HighMultiplierColour; - protected readonly TriangleButton DeselectAllButton; - protected readonly OsuSpriteText MultiplierLabel, UnrankedLabel; - private readonly FillFlowContainer footerContainer; + protected readonly TriangleButton CloseButton; + + protected readonly OsuSpriteText MultiplierLabel; + protected readonly OsuSpriteText UnrankedLabel; protected override bool BlockNonPositionalInput => false; + protected override bool DimMainContent => false; + protected readonly FillFlowContainer ModSectionsContainer; - protected readonly Bindable> SelectedMods = new Bindable>(new Mod[] { }); + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); protected readonly IBindable Ruleset = new Bindable(); - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> selectedMods) - { - LowMultiplierColour = colours.Red; - HighMultiplierColour = colours.Green; - UnrankedLabel.Colour = colours.Blue; - - Ruleset.BindTo(ruleset); - if (selectedMods != null) SelectedMods.BindTo(selectedMods); - - sampleOn = audio.Sample.Get(@"UI/check-on"); - sampleOff = audio.Sample.Get(@"UI/check-off"); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - Ruleset.BindValueChanged(rulesetChanged, true); - SelectedMods.BindValueChanged(selectedModsChanged, true); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - Ruleset.UnbindAll(); - SelectedMods.UnbindAll(); - } - - private void rulesetChanged(RulesetInfo newRuleset) - { - if (newRuleset == null) return; - - var instance = newRuleset.CreateInstance(); - - foreach (ModSection section in ModSectionsContainer.Children) - section.Mods = instance.GetModsFor(section.ModType); - - // attempt to re-select any already selected mods. - // this may be the first time we are receiving the ruleset, in which case they will still match. - selectedModsChanged(SelectedMods.Value); - - // write the mods back to the SelectedMods bindable in the case a change was not applicable. - // this generally isn't required as the previous line will perform deselection; just here for safety. - refreshSelectedMods(); - } - - private void selectedModsChanged(IEnumerable obj) - { - foreach (ModSection section in ModSectionsContainer.Children) - section.SelectTypes(obj.Select(m => m.GetType()).ToList()); - - updateMods(); - } - - private void updateMods() - { - double multiplier = 1.0; - bool ranked = true; - - foreach (Mod mod in SelectedMods.Value) - { - multiplier *= mod.ScoreMultiplier; - ranked &= mod.Ranked; - } - - MultiplierLabel.Text = $"{multiplier:N2}x"; - 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() - { - base.PopOut(); - - footerContainer.MoveToX(footerContainer.DrawSize.X, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - - foreach (ModSection section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.MoveToX(100f, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - section.ButtonsContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); - } - } - - protected override void PopIn() - { - base.PopIn(); - - footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - - foreach (ModSection section in ModSectionsContainer.Children) - { - section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), WaveContainer.APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - section.ButtonsContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - } - } - - public void DeselectAll() - { - foreach (ModSection section in ModSectionsContainer.Children) - section.DeselectAll(); - - refreshSelectedMods(); - } - - /// - /// Deselect one or more mods. - /// - /// The types of s which should be deselected. - /// Set to true to bypass animations and update selections immediately. - public void DeselectTypes(Type[] modTypes, bool immediate = false) - { - if (modTypes.Length == 0) return; - foreach (ModSection section in ModSectionsContainer.Children) - section.DeselectTypes(modTypes, immediate); - } + protected Color4 LowMultiplierColour; + protected Color4 HighMultiplierColour; + private const float content_width = 0.8f; + private readonly FillFlowContainer footerContainer; private SampleChannel sampleOn, sampleOff; - private void modButtonPressed(Mod selectedMod) - { - if (selectedMod != null) - { - if (State == Visibility.Visible) sampleOn?.Play(); - DeselectTypes(selectedMod.IncompatibleMods, true); - } - else - { - if (State == Visibility.Visible) sampleOff?.Play(); - } - - refreshSelectedMods(); - } - - private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); - public ModSelectOverlay() { Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2"); @@ -204,11 +62,7 @@ namespace osu.Game.Overlays.Mods Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e"); Height = 510; - Padding = new MarginPadding - { - Left = -overflow_padding, - Right = -overflow_padding - }; + Padding = new MarginPadding { Horizontal = -OsuScreen.HORIZONTAL_OVERFLOW_PADDING }; Children = new Drawable[] { @@ -268,18 +122,13 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Width = content_width, - Padding = new MarginPadding - { - Left = overflow_padding, - Right = overflow_padding - }, + Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { new OsuSpriteText { - Font = @"Exo2.0-Bold", Text = @"Gameplay Mods", - TextSize = 22, + Font = OsuFont.GetFont(size: 22, weight: FontWeight.Bold), Shadow = true, Margin = new MarginPadding { @@ -288,7 +137,7 @@ namespace osu.Game.Overlays.Mods }, new OsuTextFlowContainer(text => { - text.TextSize = 18; + text.Font = text.Font.With(size: 18); text.Shadow = true; }) { @@ -313,8 +162,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Vertical = 10, - Left = overflow_padding, - Right = overflow_padding + Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Child = ModSectionsContainer = new FillFlowContainer { @@ -324,6 +172,8 @@ namespace osu.Game.Overlays.Mods AutoSizeAxes = Axes.Y, Spacing = new Vector2(0f, 10f), Width = content_width, + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, Children = new ModSection[] { new DifficultyReductionSection { Action = modButtonPressed }, @@ -362,8 +212,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Vertical = 15, - Left = overflow_padding, - Right = overflow_padding + Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { @@ -377,10 +226,20 @@ namespace osu.Game.Overlays.Mods Right = 20 } }, + CloseButton = new TriangleButton + { + Width = 180, + Text = "Close", + Action = Hide, + Margin = new MarginPadding + { + Right = 20 + } + }, new OsuSpriteText { Text = @"Score Multiplier:", - TextSize = 30, + Font = OsuFont.GetFont(size: 30), Margin = new MarginPadding { Top = 5, @@ -389,8 +248,7 @@ namespace osu.Game.Overlays.Mods }, MultiplierLabel = new OsuSpriteText { - Font = @"Exo2.0-Bold", - TextSize = 30, + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), Margin = new MarginPadding { Top = 5 @@ -398,9 +256,8 @@ namespace osu.Game.Overlays.Mods }, UnrankedLabel = new OsuSpriteText { - Font = @"Exo2.0-Bold", Text = @"(Unranked)", - TextSize = 30, + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold), Margin = new MarginPadding { Top = 5, @@ -416,5 +273,171 @@ namespace osu.Game.Overlays.Mods }, }; } + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, IBindable ruleset, AudioManager audio, Bindable> mods) + { + LowMultiplierColour = colours.Red; + HighMultiplierColour = colours.Green; + UnrankedLabel.Colour = colours.Blue; + + Ruleset.BindTo(ruleset); + if (mods != null) SelectedMods.BindTo(mods); + + sampleOn = audio.Samples.Get(@"UI/check-on"); + sampleOff = audio.Samples.Get(@"UI/check-off"); + } + + public void DeselectAll() + { + foreach (var section in ModSectionsContainer.Children) + section.DeselectAll(); + + refreshSelectedMods(); + } + + /// + /// Deselect one or more mods. + /// + /// The types of s which should be deselected. + /// Set to true to bypass animations and update selections immediately. + public void DeselectTypes(Type[] modTypes, bool immediate = false) + { + if (modTypes.Length == 0) return; + + foreach (var section in ModSectionsContainer.Children) + section.DeselectTypes(modTypes, immediate); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Ruleset.BindValueChanged(rulesetChanged, true); + SelectedMods.BindValueChanged(selectedModsChanged, true); + } + + protected override void PopOut() + { + base.PopOut(); + + footerContainer.MoveToX(footerContainer.DrawSize.X, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + + foreach (var section in ModSectionsContainer.Children) + { + section.ButtonsContainer.TransformSpacingTo(new Vector2(100f, 0f), WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.MoveToX(100f, WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + section.ButtonsContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine); + } + } + + protected override void PopIn() + { + base.PopIn(); + + footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + + foreach (var section in ModSectionsContainer.Children) + { + section.ButtonsContainer.TransformSpacingTo(new Vector2(50f, 0f), WaveContainer.APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + section.ButtonsContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + } + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Number1: + DeselectAllButton.Click(); + return true; + + case Key.Number2: + CloseButton.Click(); + return true; + } + + return base.OnKeyDown(e); + } + + private void rulesetChanged(ValueChangedEvent e) + { + if (e.NewValue == null) return; + + var instance = e.NewValue.CreateInstance(); + + foreach (var section in ModSectionsContainer.Children) + section.Mods = instance.GetModsFor(section.ModType); + + // attempt to re-select any already selected mods. + // this may be the first time we are receiving the ruleset, in which case they will still match. + selectedModsChanged(new ValueChangedEvent>(SelectedMods.Value, SelectedMods.Value)); + + // write the mods back to the SelectedMods bindable in the case a change was not applicable. + // this generally isn't required as the previous line will perform deselection; just here for safety. + refreshSelectedMods(); + } + + private void selectedModsChanged(ValueChangedEvent> e) + { + foreach (var section in ModSectionsContainer.Children) + section.SelectTypes(e.NewValue.Select(m => m.GetType()).ToList()); + + updateMods(); + } + + private void updateMods() + { + var multiplier = 1.0; + var ranked = true; + + foreach (var mod in SelectedMods.Value) + { + multiplier *= mod.ScoreMultiplier; + ranked &= mod.Ranked; + } + + MultiplierLabel.Text = $"{multiplier:N2}x"; + 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); + } + + private void modButtonPressed(Mod selectedMod) + { + if (selectedMod != null) + { + if (State.Value == Visibility.Visible) sampleOn?.Play(); + DeselectTypes(selectedMod.IncompatibleMods, true); + } + else + { + if (State.Value == Visibility.Visible) sampleOff?.Play(); + } + + refreshSelectedMods(); + } + + private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray(); + + #region Disposal + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + Ruleset.UnbindAll(); + SelectedMods.UnbindAll(); + } + + #endregion } } diff --git a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs b/osu.Game/Overlays/Mods/Sections/AutomationSection.cs index c14ed270f7..a2d7fec15f 100644 --- a/osu.Game/Overlays/Mods/Sections/AutomationSection.cs +++ b/osu.Game/Overlays/Mods/Sections/AutomationSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; using osuTK.Input; diff --git a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs b/osu.Game/Overlays/Mods/Sections/ConversionSection.cs index 90ff879265..24fd8c30dd 100644 --- a/osu.Game/Overlays/Mods/Sections/ConversionSection.cs +++ b/osu.Game/Overlays/Mods/Sections/ConversionSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; using osuTK.Input; diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs index f1b7edf98b..0b7ccd1f25 100644 --- a/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs +++ b/osu.Game/Overlays/Mods/Sections/DifficultyIncreaseSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; using osuTK.Input; diff --git a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs b/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs index 8ab9251431..508e92508b 100644 --- a/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs +++ b/osu.Game/Overlays/Mods/Sections/DifficultyReductionSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; using osuTK.Input; diff --git a/osu.Game/Overlays/Mods/Sections/FunSection.cs b/osu.Game/Overlays/Mods/Sections/FunSection.cs index 29329f9c1d..af1f5836b1 100644 --- a/osu.Game/Overlays/Mods/Sections/FunSection.cs +++ b/osu.Game/Overlays/Mods/Sections/FunSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mods; using osuTK.Input; diff --git a/osu.Game/Overlays/Music/CollectionsDropdown.cs b/osu.Game/Overlays/Music/CollectionsDropdown.cs index 7b2f81b60c..4f59b053b6 100644 --- a/osu.Game/Overlays/Music/CollectionsDropdown.cs +++ b/osu.Game/Overlays/Music/CollectionsDropdown.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index 598b99b9a6..99017579a2 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; namespace osu.Game.Overlays.Music { @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Music Search.Current.ValueChanged += current_ValueChanged; } - private void current_ValueChanged(string newValue) => FilterChanged?.Invoke(newValue); + private void current_ValueChanged(ValueChangedEvent e) => FilterChanged?.Invoke(e.NewValue); public Action ExitRequested; @@ -53,22 +53,16 @@ namespace osu.Game.Overlays.Music public class FilterTextBox : SearchTextBox { - private Color4 backgroundColour; - - protected override Color4 BackgroundUnfocused => backgroundColour; - protected override Color4 BackgroundFocused => backgroundColour; protected override bool AllowCommit => true; - public FilterTextBox() + [BackgroundDependencyLoader] + private void load() { Masking = true; CornerRadius = 5; - } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - backgroundColour = colours.Gray2; + BackgroundUnfocused = OsuColour.Gray(0.06f); + BackgroundFocused = OsuColour.Gray(0.12f); } } } diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 5d89e53081..df37a1b2c7 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; +using System.Linq; using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.Music private SpriteIcon handle; private TextFlowContainer text; - private IEnumerable titleSprites; + private IEnumerable titleSprites; private ILocalisedBindableString titleBind; private ILocalisedBindableString artistBind; @@ -49,16 +50,18 @@ namespace osu.Game.Overlays.Music } private bool selected; + public bool Selected { - get { return selected; } + get => selected; set { if (value == selected) return; + selected = value; FinishTransforms(true); - foreach (SpriteText s in titleSprites) + foreach (Drawable s in titleSprites) s.FadeColour(Selected ? hoverColour : Color4.White, fade_duration); } } @@ -99,7 +102,7 @@ namespace osu.Game.Overlays.Music titleBind = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title))); artistBind = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist))); - artistBind.BindValueChanged(newText => recreateText(), true); + artistBind.BindValueChanged(_ => recreateText(), true); } private void recreateText() @@ -107,16 +110,11 @@ namespace osu.Game.Overlays.Music text.Clear(); //space after the title to put a space between the title and artist - titleSprites = text.AddText(titleBind.Value + @" ", sprite => - { - sprite.TextSize = 16; - sprite.Font = @"Exo2.0-Regular"; - }); + titleSprites = text.AddText(titleBind.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); text.AddText(artistBind.Value, sprite => { - sprite.TextSize = 14; - sprite.Font = @"Exo2.0-Bold"; + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); sprite.Colour = artistColour; sprite.Padding = new MarginPadding { Top = 1 }; }); @@ -146,7 +144,7 @@ namespace osu.Game.Overlays.Music public bool MatchingFilter { - get { return matching; } + get => matching; set { if (matching == value) return; @@ -157,6 +155,8 @@ namespace osu.Game.Overlays.Music } } + public bool FilteringActive { get; set; } + private class PlaylistItemHandle : SpriteIcon { public PlaylistItemHandle() @@ -164,10 +164,12 @@ namespace osu.Game.Overlays.Music Anchor = Anchor.TopLeft; Origin = Anchor.TopLeft; Size = new Vector2(12); - Icon = FontAwesome.fa_bars; + Icon = FontAwesome.Solid.Bars; Alpha = 0f; Margin = new MarginPadding { Left = 5, Top = 2 }; } + + public override bool HandlePositionalInput => IsPresent; } } diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index ef7f946859..07040f166d 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -34,8 +34,8 @@ namespace osu.Game.Overlays.Music public new MarginPadding Padding { - get { return base.Padding; } - set { base.Padding = value; } + get => base.Padding; + set => base.Padding = value; } public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet; @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(BeatmapManager beatmaps, IBindableBeatmap beatmap) + private void load(BeatmapManager beatmaps, IBindable beatmap) { beatmaps.GetAllUsableBeatmapSets().ForEach(addBeatmapSet); beatmaps.ItemAdded += addBeatmapSet; @@ -106,8 +106,8 @@ namespace osu.Game.Overlays.Music public string SearchTerm { - get { return search.SearchTerm; } - set { search.SearchTerm = value; } + get => search.SearchTerm; + set => search.SearchTerm = value; } public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo; @@ -127,6 +127,7 @@ namespace osu.Game.Overlays.Music nativeDragPosition = e.ScreenSpaceMousePosition; if (draggedItem == null) return base.OnDrag(e); + return true; } @@ -186,6 +187,7 @@ namespace osu.Game.Overlays.Music // the item positions as they are being transformed float heightAccumulator = 0; int dstIndex = 0; + for (; dstIndex < items.Count; dstIndex++) { // Using BoundingBox here takes care of scale, paddings, etc... @@ -227,6 +229,11 @@ namespace osu.Game.Overlays.Music } } + public bool FilteringActive + { + set { } + } + public IEnumerable FilterableChildren => Children; public ItemSearchContainer() diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 6c45c63145..ec3d708645 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -1,13 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -34,7 +35,7 @@ namespace osu.Game.Overlays.Music private PlaylistList list; [BackgroundDependencyLoader] - private void load(OsuColour colours, BindableBeatmap beatmap, BeatmapManager beatmaps) + private void load(OsuColour colours, Bindable beatmap, BeatmapManager beatmaps) { this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; @@ -70,7 +71,7 @@ namespace osu.Game.Overlays.Music { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - ExitRequested = () => State = Visibility.Hidden, + ExitRequested = Hide, FilterChanged = search => list.Filter(search), Padding = new MarginPadding(10), }, @@ -81,6 +82,7 @@ namespace osu.Game.Overlays.Music filter.Search.OnCommit = (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); + if (toSelect != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); @@ -92,7 +94,7 @@ namespace osu.Game.Overlays.Music protected override void PopIn() { filter.Search.HoldFocus = true; - Schedule(() => GetContainingInputManager().ChangeFocus(filter.Search)); + Schedule(() => filter.Search.TakeFocus()); this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); this.FadeIn(transition_duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 58e03bd0cd..29ae5983be 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -1,15 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -22,6 +23,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Music; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -49,26 +51,30 @@ namespace osu.Game.Overlays private BeatmapManager beatmaps; private List beatmapSets; - private BeatmapSetInfo currentSet; private Container dragContainer; private Container playerContainer; - private readonly Bindable beatmap = new Bindable(); + [Resolved] + private Bindable beatmap { get; set; } + + [Resolved] + private IBindable> mods { get; set; } + + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; public MusicController() { Width = 400; Margin = new MarginPadding(10); - - // required to let MusicController handle beatmap cycling. - AlwaysPresent = true; } [BackgroundDependencyLoader] - private void load(BindableBeatmap beatmap, BeatmapManager beatmaps, OsuColour colours) + private void load(Bindable beatmap, BeatmapManager beatmaps, OsuColour colours) { - this.beatmap.BindTo(beatmap); this.beatmaps = beatmaps; Children = new Drawable[] @@ -107,20 +113,18 @@ namespace osu.Game.Overlays Origin = Anchor.BottomCentre, Anchor = Anchor.TopCentre, Position = new Vector2(0, 40), - TextSize = 25, + Font = OsuFont.GetFont(size: 25, italics: true), Colour = Color4.White, Text = @"Nothing to play", - Font = @"Exo2.0-MediumItalic" }, artist = new OsuSpriteText { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Position = new Vector2(0, 45), - TextSize = 15, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold, italics: true), Colour = Color4.White, Text = @"Nothing to play", - Font = @"Exo2.0-BoldItalic" }, new Container { @@ -145,7 +149,7 @@ namespace osu.Game.Overlays Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = prev, - Icon = FontAwesome.fa_step_backward, + Icon = FontAwesome.Solid.StepBackward, }, playButton = new MusicIconButton { @@ -154,14 +158,14 @@ namespace osu.Game.Overlays Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), Action = play, - Icon = FontAwesome.fa_play_circle_o, + Icon = FontAwesome.Regular.PlayCircle, }, nextButton = new MusicIconButton { Anchor = Anchor.Centre, Origin = Anchor.Centre, Action = () => next(), - Icon = FontAwesome.fa_step_forward, + Icon = FontAwesome.Solid.StepForward, }, } }, @@ -170,7 +174,7 @@ namespace osu.Game.Overlays Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-bottom_black_area_height / 2, 0), - Icon = FontAwesome.fa_bars, + Icon = FontAwesome.Solid.Bars, Action = () => playlist.ToggleVisibility(), }, } @@ -193,7 +197,7 @@ namespace osu.Game.Overlays beatmaps.ItemAdded += handleBeatmapAdded; beatmaps.ItemRemoved += handleBeatmapRemoved; - playlist.StateChanged += s => playlistButton.FadeColour(s == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); + playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } private ScheduledDelegate seekDelegate; @@ -214,13 +218,15 @@ namespace osu.Game.Overlays beatmapSets.Insert(index, beatmapSetInfo); } - private void handleBeatmapAdded(BeatmapSetInfo obj) => Schedule(() => beatmapSets.Add(obj)); - private void handleBeatmapRemoved(BeatmapSetInfo obj) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == obj.ID)); + private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set)); + + private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID)); protected override void LoadComplete() { beatmap.BindValueChanged(beatmapChanged, true); beatmap.BindDisabledChanged(beatmapDisabledChanged, true); + mods.BindValueChanged(_ => updateAudioAdjustments(), true); base.LoadComplete(); } @@ -239,12 +245,20 @@ namespace osu.Game.Overlays { base.UpdateAfterChildren(); Height = dragContainer.Height; + + dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; } protected override void Update() { base.Update(); + if (pendingBeatmapSwitch != null) + { + pendingBeatmapSwitch(); + pendingBeatmapSwitch = null; + } + var track = current?.TrackLoaded ?? false ? current.Track : null; if (track?.IsDummyDevice == false) @@ -252,16 +266,13 @@ namespace osu.Game.Overlays progressBar.EndTime = track.Length; progressBar.CurrentTime = track.CurrentTime; - playButton.Icon = track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; - - if (track.HasCompleted && !track.Looping && !beatmap.Disabled && beatmapSets.Any()) - next(); + playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } else { progressBar.CurrentTime = 0; progressBar.EndTime = 1; - playButton.Icon = FontAwesome.fa_play_circle_o; + playButton.Icon = FontAwesome.Regular.PlayCircle; } } @@ -287,6 +298,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Prev; var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -300,6 +312,7 @@ namespace osu.Game.Overlays queuedDirection = TransformDirection.Next; var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault(); + if (playable != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value); @@ -310,13 +323,13 @@ namespace osu.Game.Overlays private WorkingBeatmap current; private TransformDirection? queuedDirection; - private void beatmapChanged(WorkingBeatmap beatmap) + private void beatmapChanged(ValueChangedEvent beatmap) { TransformDirection direction = TransformDirection.None; if (current != null) { - bool audioEquals = beatmap?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false; + bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false; if (audioEquals) direction = TransformDirection.None; @@ -329,30 +342,38 @@ namespace osu.Game.Overlays { //figure out the best direction based on order in playlist. var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count(); - var next = beatmap == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.BeatmapSetInfo?.ID).Count(); + var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count(); direction = last > next ? TransformDirection.Prev : TransformDirection.Next; } } - current = beatmap; - progressBar.CurrentTime = 0; - updateDisplay(current, direction); + updateDisplay(current = beatmap.NewValue, direction); + updateAudioAdjustments(); queuedDirection = null; } - private ScheduledDelegate pendingBeatmapSwitch; + private void updateAudioAdjustments() + { + var track = current?.Track; + if (track == null) + return; + + track.ResetSpeedAdjustments(); + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToClock(track); + } + + private Action pendingBeatmapSwitch; private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction) { - //we might be off-screen when this update comes in. - //rather than Scheduling, manually handle this to avoid possible memory contention. - pendingBeatmapSwitch?.Cancel(); - - pendingBeatmapSwitch = Schedule(delegate + // avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps. + pendingBeatmapSwitch = delegate { // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync() Task.Run(() => @@ -379,6 +400,7 @@ namespace osu.Game.Overlays newBackground.MoveToX(0, 500, Easing.OutCubic); background.MoveToX(-400, 500, Easing.OutCubic); break; + case TransformDirection.Prev: newBackground.Position = new Vector2(-400, 0); newBackground.MoveToX(0, 500, Easing.OutCubic); @@ -391,7 +413,7 @@ namespace osu.Game.Overlays playerContainer.Add(newBackground); }); - }); + }; } protected override void PopIn() @@ -406,10 +428,6 @@ namespace osu.Game.Overlays { base.PopOut(); - // This is here mostly as a performance fix. - // If the playlist is not hidden it will update children even when the music controller is hidden (due to AlwaysPresent). - playlist.State = Visibility.Hidden; - this.FadeOut(transition_length, Easing.OutQuint); dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint); } @@ -423,12 +441,26 @@ namespace osu.Game.Overlays private class MusicIconButton : IconButton { + public MusicIconButton() + { + AutoSizeAxes = Axes.Both; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { HoverColour = colours.YellowDark.Opacity(0.6f); FlashColour = colours.Yellow; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // works with AutoSizeAxes above to make buttons autosize with the scale animation. + Content.AutoSizeAxes = Axes.None; + Content.Size = new Vector2(DEFAULT_BUTTON_SIZE); + } } private class Background : BufferedContainer @@ -493,5 +525,10 @@ namespace osu.Game.Overlays return base.OnDragEnd(e); } } + + /// + /// Play the next random or playlist track. + /// + public void NextTrack() => next(); } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index b9c5151e26..41160d10ec 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; @@ -11,7 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Containers; using System; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Threading; namespace osu.Game.Overlays @@ -35,8 +35,6 @@ namespace osu.Game.Overlays Width = width; RelativeSizeAxes = Axes.Y; - AlwaysPresent = true; - Children = new Drawable[] { new Box @@ -58,16 +56,12 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.X, Children = new[] { - new NotificationSection + new NotificationSection(@"Notifications", @"Clear All") { - Title = @"Notifications", - ClearText = @"Clear All", AcceptTypes = new[] { typeof(SimpleNotification) } }, - new NotificationSection + new NotificationSection(@"Running Tasks", @"Cancel All") { - Title = @"Running Tasks", - ClearText = @"Cancel All", AcceptTypes = new[] { typeof(ProgressNotification) } } } @@ -78,15 +72,16 @@ namespace osu.Game.Overlays } private ScheduledDelegate notificationsEnabler; + private void updateProcessingMode() { - bool enabled = OverlayActivationMode == OverlayActivation.All || State == Visibility.Visible; + bool enabled = OverlayActivationMode.Value == OverlayActivation.All || State.Value == 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); + notificationsEnabler = Scheduler.AddDelayed(() => processingPosts = true, State.Value == Visibility.Visible ? 0 : 1000); else processingPosts = false; } @@ -95,13 +90,10 @@ namespace osu.Game.Overlays { base.LoadComplete(); - StateChanged += _ => updateProcessingMode(); + State.ValueChanged += _ => updateProcessingMode(); OverlayActivationMode.BindValueChanged(_ => updateProcessingMode(), true); } - private int totalCount => sections.Select(c => c.DisplayedCount).Sum(); - private int unreadCount => sections.Select(c => c.UnreadCount).Sum(); - public readonly BindableInt UnreadCount = new BindableInt(); private int runningDepth; @@ -110,6 +102,8 @@ namespace osu.Game.Overlays private readonly Scheduler postScheduler = new Scheduler(); + public override bool IsPresent => base.IsPresent || postScheduler.HasPendingTasks; + private bool processingPosts = true; public void Post(Notification notification) => postScheduler.Add(() => @@ -118,8 +112,7 @@ namespace osu.Game.Overlays notification.Closed += notificationClosed; - var hasCompletionTarget = notification as IHasCompletionTarget; - if (hasCompletionTarget != null) + if (notification is IHasCompletionTarget hasCompletionTarget) hasCompletionTarget.CompletionTarget = Post; var ourType = notification.GetType(); @@ -128,7 +121,7 @@ namespace osu.Game.Overlays section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth); if (notification.IsImportant) - State = Visibility.Visible; + Show(); updateCounts(); }); @@ -160,7 +153,7 @@ namespace osu.Game.Overlays private void updateCounts() { - UnreadCount.Value = unreadCount; + UnreadCount.Value = sections.Select(c => c.UnreadCount).Sum(); } private void markAllRead() diff --git a/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs b/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs index 6b91f50aa0..603f53cb53 100644 --- a/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs +++ b/osu.Game/Overlays/Notifications/IHasCompletionTarget.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; diff --git a/osu.Game/Overlays/Notifications/Notification.cs b/osu.Game/Overlays/Notifications/Notification.cs index ad20e6cd01..2dc6b39a92 100644 --- a/osu.Game/Overlays/Notifications/Notification.cs +++ b/osu.Game/Overlays/Notifications/Notification.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -7,10 +7,12 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; @@ -151,6 +153,7 @@ namespace osu.Game.Overlays.Notifications public virtual void Close() { if (WasClosed) return; + WasClosed = true; Closed?.Invoke(); @@ -173,7 +176,7 @@ namespace osu.Game.Overlays.Notifications { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_times_circle, + Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(20), } }; @@ -205,7 +208,7 @@ namespace osu.Game.Overlays.Notifications public bool Pulsate { - get { return pulsate; } + get => pulsate; set { if (pulsate == value) return; diff --git a/osu.Game/Overlays/Notifications/NotificationSection.cs b/osu.Game/Overlays/Notifications/NotificationSection.cs index 6958835f03..f9278bbbd2 100644 --- a/osu.Game/Overlays/Notifications/NotificationSection.cs +++ b/osu.Game/Overlays/Notifications/NotificationSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -17,10 +17,7 @@ namespace osu.Game.Overlays.Notifications { public class NotificationSection : AlwaysUpdateFillFlowContainer { - private OsuSpriteText titleText; - private OsuSpriteText countText; - - private ClearAllButton clearButton; + private OsuSpriteText countDrawable; private FlowContainer notifications; @@ -35,28 +32,14 @@ namespace osu.Game.Overlays.Notifications public IEnumerable AcceptTypes; - private string clearText; + private readonly string clearButtonText; - public string ClearText + private readonly string titleText; + + public NotificationSection(string title, string clearButtonText) { - get { return clearText; } - set - { - clearText = value; - if (clearButton != null) clearButton.Text = clearText; - } - } - - private string title; - - public string Title - { - get { return title; } - set - { - title = value; - if (titleText != null) titleText.Text = title.ToUpperInvariant(); - } + this.clearButtonText = clearButtonText; + titleText = title; } [BackgroundDependencyLoader] @@ -82,9 +65,9 @@ namespace osu.Game.Overlays.Notifications AutoSizeAxes = Axes.Y, Children = new Drawable[] { - clearButton = new ClearAllButton + new ClearAllButton { - Text = clearText, + Text = clearButtonText, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Action = clearAll @@ -99,16 +82,16 @@ namespace osu.Game.Overlays.Notifications AutoSizeAxes = Axes.Both, Children = new Drawable[] { - titleText = new OsuSpriteText + new OsuSpriteText { - Text = title.ToUpperInvariant(), - Font = @"Exo2.0-Black", + Text = titleText.ToUpperInvariant(), + Font = OsuFont.GetFont(weight: FontWeight.Black) }, - countText = new OsuSpriteText + countDrawable = new OsuSpriteText { Text = "3", Colour = colours.Yellow, - Font = @"Exo2.0-Black", + Font = OsuFont.GetFont(weight: FontWeight.Black) }, } }, @@ -134,7 +117,7 @@ namespace osu.Game.Overlays.Notifications { base.Update(); - countText.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString(); + countDrawable.Text = notifications.Children.Count(c => c.Alpha > 0.99f).ToString(); } private class ClearAllButton : OsuClickableContainer @@ -153,8 +136,8 @@ namespace osu.Game.Overlays.Notifications public string Text { - get { return text.Text; } - set { text.Text = value.ToUpperInvariant(); } + get => text.Text; + set => text.Text = value.ToUpperInvariant(); } } diff --git a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs index 0711e49608..feffb4fa66 100644 --- a/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressCompletionNotification.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Notifications { @@ -11,7 +12,7 @@ namespace osu.Game.Overlays.Notifications { public ProgressCompletionNotification() { - Icon = FontAwesome.fa_check; + Icon = FontAwesome.Solid.Check; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index 56342ea4c4..99836705c4 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,18 +18,21 @@ namespace osu.Game.Overlays.Notifications { public string Text { - set - { - Schedule(() => textDrawable.Text = value); - } + set => Schedule(() => textDrawable.Text = value); } public string CompletionText { get; set; } = "Task has completed!"; + private float progress; + public float Progress { - get { return progressBar.Progress; } - set { Schedule(() => progressBar.Progress = value); } + get => progress; + set + { + progress = value; + Scheduler.AddOnce(() => progressBar.Progress = progress); + } } protected override void LoadComplete() @@ -36,52 +40,55 @@ namespace osu.Game.Overlays.Notifications base.LoadComplete(); //we may have received changes before we were displayed. - State = state; + updateState(); } - public virtual ProgressNotificationState State + private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); + + public CancellationToken CancellationToken => cancellationTokenSource.Token; + + public ProgressNotificationState State { - get { return state; } + get => state; set { - Schedule(() => - { - bool stateChanged = state != value; - state = value; + if (state == value) return; - if (IsLoaded) - { - switch (state) - { - case ProgressNotificationState.Queued: - Light.Colour = colourQueued; - Light.Pulsate = false; - progressBar.Active = false; - break; - case ProgressNotificationState.Active: - Light.Colour = colourActive; - Light.Pulsate = true; - progressBar.Active = true; - break; - case ProgressNotificationState.Cancelled: - Light.Colour = colourCancelled; - Light.Pulsate = false; - progressBar.Active = false; - break; - } - } + state = value; - if (stateChanged) - { - switch (state) - { - case ProgressNotificationState.Completed: - NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); - this.FadeOut(200).Finally(d => Completed()); - break; - } - } - }); + if (IsLoaded) + Schedule(updateState); + } + } + + private void updateState() + { + switch (state) + { + case ProgressNotificationState.Queued: + Light.Colour = colourQueued; + Light.Pulsate = false; + progressBar.Active = false; + break; + + case ProgressNotificationState.Active: + Light.Colour = colourActive; + Light.Pulsate = true; + progressBar.Active = true; + break; + + case ProgressNotificationState.Cancelled: + cancellationTokenSource.Cancel(); + + Light.Colour = colourCancelled; + Light.Pulsate = false; + progressBar.Active = false; + break; + + case ProgressNotificationState.Completed: + NotificationContent.MoveToY(-DrawSize.Y / 2, 200, Easing.OutQuint); + this.FadeOut(200).Finally(d => Completed()); + break; } } @@ -115,10 +122,7 @@ namespace osu.Game.Overlays.Notifications RelativeSizeAxes = Axes.Both, }); - Content.Add(textDrawable = new OsuTextFlowContainer(t => - { - t.TextSize = 16; - }) + Content.Add(textDrawable = new OsuTextFlowContainer { Colour = OsuColour.Gray(128), AutoSizeAxes = Axes.Y, @@ -153,6 +157,7 @@ namespace osu.Game.Overlays.Notifications case ProgressNotificationState.Cancelled: base.Close(); break; + case ProgressNotificationState.Active: case ProgressNotificationState.Queued: if (CancelRequested?.Invoke() != false) @@ -181,9 +186,10 @@ namespace osu.Game.Overlays.Notifications private Color4 colourInactive; private float progress; + public float Progress { - get { return progress; } + get => progress; set { if (progress == value) return; @@ -197,7 +203,7 @@ namespace osu.Game.Overlays.Notifications public bool Active { - get { return active; } + get => active; set { active = value; diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index d0f49a74c0..3a3136b1ea 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -15,9 +16,10 @@ namespace osu.Game.Overlays.Notifications public class SimpleNotification : Notification { private string text = string.Empty; + public string Text { - get { return text; } + get => text; set { text = value; @@ -25,10 +27,11 @@ namespace osu.Game.Overlays.Notifications } } - private FontAwesome icon = FontAwesome.fa_info_circle; - public FontAwesome Icon + private IconUsage icon = FontAwesome.Solid.InfoCircle; + + public IconUsage Icon { - get { return icon; } + get => icon; set { icon = value; @@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Notifications } }); - Content.Add(textDrawable = new OsuTextFlowContainer(t => t.TextSize = 14) + Content.Add(textDrawable = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 14)) { Colour = OsuColour.Gray(128), AutoSizeAxes = Axes.Y, @@ -76,11 +79,7 @@ namespace osu.Game.Overlays.Notifications public override bool Read { - get - { - return base.Read; - } - + get => base.Read; set { if (value == base.Read) return; diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index 9cf42c3a29..88a1edddc5 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -14,6 +14,7 @@ using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Transforms; using osu.Framework.Threading; using osu.Game.Configuration; @@ -69,16 +70,14 @@ namespace osu.Game.Overlays textLine1 = new OsuSpriteText { Padding = new MarginPadding(10), - Font = @"Exo2.0-Black", + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black), Spacing = new Vector2(1, 0), - TextSize = 14, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, textLine2 = new OsuSpriteText { - TextSize = 24, - Font = @"Exo2.0-Light", + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light), Padding = new MarginPadding { Left = 10, Right = 10 }, Anchor = Anchor.Centre, Origin = Anchor.BottomCentre, @@ -105,8 +104,7 @@ namespace osu.Game.Overlays Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Margin = new MarginPadding { Bottom = 15 }, - Font = @"Exo2.0-Bold", - TextSize = 12, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), Alpha = 0.3f, }, } @@ -161,7 +159,7 @@ namespace osu.Game.Overlays if (configManager == null) throw new ArgumentNullException(nameof(configManager)); if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing)) - throw new InvalidOperationException($"{nameof(configManager)} is not registered."); + return; existing.Unload(); existing.SettingChanged -= display; @@ -191,6 +189,7 @@ namespace osu.Game.Overlays optionCount = 1; if (val) selectedOption = 0; break; + case Enum _: var values = Enum.GetValues(description.RawValue.GetType()); optionCount = values.Length; diff --git a/osu.Game/Overlays/OverlayActivation.cs b/osu.Game/Overlays/OverlayActivation.cs index da4e153ce9..68d7ee8ea9 100644 --- a/osu.Game/Overlays/OverlayActivation.cs +++ b/osu.Game/Overlays/OverlayActivation.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Overlays { diff --git a/osu.Game/Overlays/OverlayHeader.cs b/osu.Game/Overlays/OverlayHeader.cs new file mode 100644 index 0000000000..2e032db2ba --- /dev/null +++ b/osu.Game/Overlays/OverlayHeader.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays +{ + public abstract class OverlayHeader : Container + { + protected readonly OverlayHeaderTabControl TabControl; + + private const float cover_height = 150; + private const float cover_info_height = 75; + + protected OverlayHeader() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = cover_height, + Masking = true, + Child = CreateBackground() + }, + new Container + { + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Y = cover_height, + Height = cover_info_height, + RelativeSizeAxes = Axes.X, + Anchor = Anchor.TopLeft, + Origin = Anchor.BottomLeft, + Depth = -float.MaxValue, + Children = new Drawable[] + { + CreateTitle().With(t => t.X = -ScreenTitle.ICON_WIDTH), + TabControl = new OverlayHeaderTabControl + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = cover_info_height - 30, + Margin = new MarginPadding { Left = -UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN } + } + } + }, + new Container + { + Margin = new MarginPadding { Top = cover_height }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = CreateContent() + } + }; + } + + protected abstract Drawable CreateBackground(); + + protected abstract Drawable CreateContent(); + + protected abstract ScreenTitle CreateTitle(); + } +} diff --git a/osu.Game/Overlays/OverlayHeaderTabControl.cs b/osu.Game/Overlays/OverlayHeaderTabControl.cs new file mode 100644 index 0000000000..5b56771dc1 --- /dev/null +++ b/osu.Game/Overlays/OverlayHeaderTabControl.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.UserInterface; + +namespace osu.Game.Overlays +{ + public class OverlayHeaderTabControl : OverlayTabControl + { + protected override TabItem CreateTabItem(string value) => new OverlayHeaderTabItem(value) + { + AccentColour = AccentColour, + }; + + private class OverlayHeaderTabItem : OverlayTabItem + { + public OverlayHeaderTabItem(string value) + : base(value) + { + Text.Text = value; + } + } + } +} diff --git a/osu.Game/Overlays/OverlayTabControl.cs b/osu.Game/Overlays/OverlayTabControl.cs new file mode 100644 index 0000000000..20649c8a74 --- /dev/null +++ b/osu.Game/Overlays/OverlayTabControl.cs @@ -0,0 +1,163 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays +{ + public abstract class OverlayTabControl : TabControl + { + private readonly Box bar; + + private Color4 accentColour = Color4.White; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + foreach (TabItem tabItem in TabContainer) + { + ((OverlayTabItem)tabItem).AccentColour = value; + } + } + } + + public new MarginPadding Padding + { + get => TabContainer.Padding; + set => TabContainer.Padding = value; + } + + protected OverlayTabControl() + { + TabContainer.Masking = false; + TabContainer.Spacing = new Vector2(15, 0); + + AddInternal(bar = new Box + { + RelativeSizeAxes = Axes.X, + Height = 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft + }); + } + + protected override Dropdown CreateDropdown() => null; + + protected override TabItem CreateTabItem(T value) => new OverlayTabItem(value); + + protected class OverlayTabItem : TabItem + { + private readonly ExpandingBar bar; + + protected readonly OsuSpriteText Text; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + bar.Colour = value; + + updateState(); + } + } + + public OverlayTabItem(U value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + Text = new OsuSpriteText + { + Margin = new MarginPadding { Bottom = 10 }, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Font = OsuFont.GetFont(), + }, + bar = new ExpandingBar + { + Anchor = Anchor.BottomCentre, + ExpandedSize = 7.5f, + CollapsedSize = 0 + }, + new HoverClickSounds() + }; + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + + if (!Active.Value) + HoverAction(); + + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + + if (!Active.Value) + UnhoverAction(); + } + + protected override void OnActivated() + { + HoverAction(); + Text.Font = Text.Font.With(weight: FontWeight.Bold); + } + + protected override void OnDeactivated() + { + UnhoverAction(); + Text.Font = Text.Font.With(weight: FontWeight.Medium); + } + + private void updateState() + { + if (Active.Value) + OnActivated(); + else + OnDeactivated(); + } + + protected virtual void HoverAction() + { + bar.Expand(); + Text.FadeColour(Color4.White, 120, Easing.InQuad); + } + + protected virtual void UnhoverAction() + { + bar.Collapse(); + Text.FadeColour(AccentColour, 120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs b/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs deleted file mode 100644 index 11ee329f33..0000000000 --- a/osu.Game/Overlays/Profile/Components/DrawableJoinDate.cs +++ /dev/null @@ -1,20 +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 osu.Game.Graphics; - -namespace osu.Game.Overlays.Profile.Components -{ - public class DrawableJoinDate : DrawableDate - { - public DrawableJoinDate(DateTimeOffset date) - : base(date) - { - } - - protected override string Format() => Text = Date.ToUniversalTime().Year < 2008 ? "Here since the beginning" : $"{Date:MMMM yyyy}"; - - public override string TooltipText => $"{Date:MMMM d, yyyy}"; - } -} diff --git a/osu.Game/Overlays/Profile/Components/GradeBadge.cs b/osu.Game/Overlays/Profile/Components/GradeBadge.cs deleted file mode 100644 index 14a47e8d03..0000000000 --- a/osu.Game/Overlays/Profile/Components/GradeBadge.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Overlays.Profile.Components -{ - public class GradeBadge : Container - { - private const float width = 50; - private readonly string grade; - private readonly Sprite badge; - private readonly SpriteText numberText; - - public int DisplayCount - { - set => numberText.Text = value.ToString(@"#,0"); - } - - public GradeBadge(string grade) - { - this.grade = grade; - Width = width; - Height = 41; - Add(badge = new Sprite - { - Width = width, - Height = 26 - }); - Add(numberText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - TextSize = 14, - Font = @"Exo2.0-Bold" - }); - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - badge.Texture = textures.Get($"Grades/{grade}"); - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs b/osu.Game/Overlays/Profile/Header/BadgeContainer.cs deleted file mode 100644 index 06fef22309..0000000000 --- a/osu.Game/Overlays/Profile/Header/BadgeContainer.cs +++ /dev/null @@ -1,198 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -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.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class BadgeContainer : Container - { - private static readonly Vector2 badge_size = new Vector2(86, 40); - private static readonly MarginPadding outer_padding = new MarginPadding(3); - - private OsuSpriteText badgeCountText; - private FillFlowContainer badgeFlowContainer; - private FillFlowContainer outerBadgeContainer; - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Child = new Container - { - Masking = true, - CornerRadius = 4, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray3 - }, - outerBadgeContainer = new OuterBadgeContainer(onOuterHover, onOuterHoverLost) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - Padding = outer_padding, - Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - badgeCountText = new OsuSpriteText - { - Alpha = 0, - TextSize = 12, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = "Exo2.0-Regular" - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, - Child = badgeFlowContainer = new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - } - } - } - }, - } - }; - - Scheduler.AddDelayed(rotateBadges, 3000, true); - } - - private void rotateBadges() - { - if (outerBadgeContainer.IsHovered) return; - - visibleBadge = (visibleBadge + 1) % badgeCount; - - badgeFlowContainer.MoveToX(-DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge, 500, Easing.InOutQuad); - } - - private int visibleBadge; - private int badgeCount; - - public void ShowBadges(Badge[] badges) - { - if (badges == null || badges.Length == 0) - { - Hide(); - return; - } - - badgeCount = badges.Length; - - badgeCountText.FadeTo(badgeCount > 1 ? 1 : 0); - badgeCountText.Text = $"{badges.Length} badges"; - - Show(); - visibleBadge = 0; - - badgeFlowContainer.Clear(); - for (var index = 0; index < badges.Length; index++) - { - int displayIndex = index; - LoadComponentAsync(new DrawableBadge(badges[index]) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - }, asyncBadge => - { - badgeFlowContainer.Add(asyncBadge); - - // load in stable order regardless of async load order. - badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); - }); - } - } - - private void onOuterHover() - { - badgeFlowContainer.ClearTransforms(); - badgeFlowContainer.X = 0; - badgeFlowContainer.Direction = FillDirection.Full; - outerBadgeContainer.AutoSizeAxes = Axes.Both; - - badgeFlowContainer.MaximumSize = new Vector2(ChildSize.X, float.MaxValue); - } - - private void onOuterHoverLost() - { - badgeFlowContainer.X = -DrawableBadge.DRAWABLE_BADGE_SIZE.X * visibleBadge; - badgeFlowContainer.Direction = FillDirection.Horizontal; - outerBadgeContainer.AutoSizeAxes = Axes.Y; - outerBadgeContainer.Width = DrawableBadge.DRAWABLE_BADGE_SIZE.X + outer_padding.TotalHorizontal; - } - - private class OuterBadgeContainer : FillFlowContainer - { - private readonly Action hoverAction; - private readonly Action hoverLostAction; - - public OuterBadgeContainer(Action hoverAction, Action hoverLostAction) - { - this.hoverAction = hoverAction; - this.hoverLostAction = hoverLostAction; - } - - protected override bool OnHover(HoverEvent e) - { - hoverAction(); - return true; - } - - protected override void OnHoverLost(HoverLostEvent e) => hoverLostAction(); - } - - private class DrawableBadge : Container, IHasTooltip - { - public static readonly Vector2 DRAWABLE_BADGE_SIZE = badge_size + outer_padding.Total; - - private readonly Badge badge; - - public DrawableBadge(Badge badge) - { - this.badge = badge; - Padding = outer_padding; - Size = DRAWABLE_BADGE_SIZE; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - Child = new Sprite - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Texture = textures.Get(badge.ImageUrl), - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - Child.FadeInFromZero(200); - } - - public string TooltipText => badge.Description; - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs new file mode 100644 index 0000000000..e7f7c2f490 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -0,0 +1,159 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class BottomHeaderContainer : CompositeDrawable + { + public readonly Bindable User = new Bindable(); + + private LinkFlowContainer topLinkContainer; + private LinkFlowContainer bottomLinkContainer; + + private Color4 iconColour; + + public BottomHeaderContainer() + { + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + iconColour = colours.GreySeafoamLighter; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDark, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + topLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + bottomLinkContainer = new LinkFlowContainer(text => text.Font = text.Font.With(size: 12)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } + } + } + }; + + User.BindValueChanged(user => updateDisplay(user.NewValue)); + } + + private void updateDisplay(User user) + { + topLinkContainer.Clear(); + bottomLinkContainer.Clear(); + + if (user == null) return; + + if (user.JoinDate.ToUniversalTime().Year < 2008) + topLinkContainer.AddText("Here since the beginning"); + else + { + topLinkContainer.AddText("Joined "); + topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden); + } + + addSpacer(topLinkContainer); + + if (user.IsOnline) + { + topLinkContainer.AddText("Currently online"); + addSpacer(topLinkContainer); + } + else if (user.LastVisit.HasValue) + { + topLinkContainer.AddText("Last seen "); + topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden); + + addSpacer(topLinkContainer); + } + + if (user.PlayStyles?.Length > 0) + { + topLinkContainer.AddText("Plays with "); + topLinkContainer.AddText(string.Join(", ", user.PlayStyles.Select(style => style.GetDescription())), embolden); + + addSpacer(topLinkContainer); + } + + topLinkContainer.AddText("Contributed "); + topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden); + + string websiteWithoutProtcol = user.Website; + + if (!string.IsNullOrEmpty(websiteWithoutProtcol)) + { + if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri)) + { + websiteWithoutProtcol = uri.Host + uri.PathAndQuery + uri.Fragment; + websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/'); + } + } + + tryAddInfo(FontAwesome.Solid.MapMarker, user.Location); + tryAddInfo(OsuIcon.Heart, user.Interests); + tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation); + bottomLinkContainer.NewLine(); + if (!string.IsNullOrEmpty(user.Twitter)) + tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); + tryAddInfo(FontAwesome.Brands.Discord, user.Discord); + tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat"); + tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); + tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website); + } + + private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); + + private void tryAddInfo(IconUsage icon, string content, string link = null) + { + if (string.IsNullOrEmpty(content)) return; + + bottomLinkContainer.AddIcon(icon, text => + { + text.Font = text.Font.With(size: 10); + text.Colour = iconColour; + }); + + if (link != null) + bottomLinkContainer.AddLink(" " + content, link, creationParameters: embolden); + else + bottomLinkContainer.AddText(" " + content, embolden); + + addSpacer(bottomLinkContainer); + } + + private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold); + } +} diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs new file mode 100644 index 0000000000..68fd77dd84 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -0,0 +1,150 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class CentreHeaderContainer : CompositeDrawable + { + public readonly BindableBool DetailsVisible = new BindableBool(true); + public readonly Bindable User = new Bindable(); + + private OverlinedInfoContainer hiddenDetailGlobal; + private OverlinedInfoContainer hiddenDetailCountry; + + public CentreHeaderContainer() + { + Height = 60; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + Container hiddenDetailContainer; + Container expandedDetailContainer; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoam + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Vertical = 10 }, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new AddFriendButton + { + RelativeSizeAxes = Axes.Y, + User = { BindTarget = User } + }, + new MessageUserButton + { + User = { BindTarget = User } + }, + } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 10 }, + Width = UserProfileOverlay.CONTENT_X_MARGIN, + Child = new ExpandDetailsButton + { + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + DetailsVisible = { BindTarget = DetailsVisible } + }, + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Children = new Drawable[] + { + new LevelBadge + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(40), + User = { BindTarget = User } + }, + expandedDetailContainer = new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + Height = 6, + Margin = new MarginPadding { Right = 50 }, + Child = new LevelProgressBar + { + RelativeSizeAxes = Axes.Both, + User = { BindTarget = User } + } + }, + hiddenDetailContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 200, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Right = 50 }, + Children = new[] + { + hiddenDetailGlobal = new OverlinedInfoContainer + { + Title = "Global Ranking", + LineColour = colours.Yellow + }, + hiddenDetailCountry = new OverlinedInfoContainer + { + Title = "Country Ranking", + LineColour = colours.Yellow + }, + } + } + } + } + }; + + DetailsVisible.BindValueChanged(visible => + { + hiddenDetailContainer.FadeTo(visible.NewValue ? 0 : 1, 200, Easing.OutQuint); + expandedDetailContainer.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + }); + + User.BindValueChanged(user => updateDisplay(user.NewValue)); + } + + private void updateDisplay(User user) + { + hiddenDetailGlobal.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + hiddenDetailCountry.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs new file mode 100644 index 0000000000..2e4fd6fe3d --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/AddFriendButton.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class AddFriendButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + public override string TooltipText => "friends"; + + private OsuSpriteText followerText; + + [BackgroundDependencyLoader] + private void load() + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Right = 10 }, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.User, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }, + followerText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(weight: FontWeight.Bold) + } + } + }; + + User.BindValueChanged(user => updateFollowers(user.NewValue), true); + } + + private void updateFollowers(User user) => followerText.Text = user?.FollowerCount?.Length > 0 ? user.FollowerCount[0].ToString("#,##0") : "0"; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs new file mode 100644 index 0000000000..ea259fe49a --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/DrawableBadge.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class DrawableBadge : CompositeDrawable, IHasTooltip + { + public static readonly Vector2 DRAWABLE_BADGE_SIZE = new Vector2(86, 40); + + private readonly Badge badge; + + public DrawableBadge(Badge badge) + { + this.badge = badge; + Size = DRAWABLE_BADGE_SIZE; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + InternalChild = new Sprite + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(badge.ImageUrl), + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + InternalChild.FadeInFromZero(200); + } + + public string TooltipText => badge.Description; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs new file mode 100644 index 0000000000..46d24608ed --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -0,0 +1,45 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class ExpandDetailsButton : ProfileHeaderButton + { + public readonly BindableBool DetailsVisible = new BindableBool(); + + public override string TooltipText => DetailsVisible.Value ? "collapse" : "expand"; + + private SpriteIcon icon; + + public ExpandDetailsButton() + { + Action = () => DetailsVisible.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.GreySeafoamLight; + HoverColour = colours.GreySeafoamLight.Darken(0.2f); + + Child = icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(20, 12) + }; + + DetailsVisible.BindValueChanged(visible => updateState(visible.NewValue), true); + } + + private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs new file mode 100644 index 0000000000..8069937810 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class LevelBadge : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private OsuSpriteText levelText; + + public LevelBadge() + { + TooltipText = "Level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures) + { + InternalChildren = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get("Profile/levelbadge"), + Colour = colours.Yellow, + }, + levelText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 20) + } + }; + + User.BindValueChanged(user => updateLevel(user.NewValue)); + } + + private void updateLevel(User user) + { + levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0"; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs new file mode 100644 index 0000000000..6a6532764f --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class LevelProgressBar : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; } + + private Bar levelProgressBar; + private OsuSpriteText levelProgressText; + + public LevelProgressBar() + { + TooltipText = "Progress to next level"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = levelProgressBar = new Bar + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = Color4.Black, + Direction = BarDirection.LeftToRight, + AccentColour = colours.Yellow + } + }, + levelProgressText = new OsuSpriteText + { + Anchor = Anchor.BottomRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) + } + }; + + User.BindValueChanged(user => updateProgress(user.NewValue)); + } + + private void updateProgress(User user) + { + levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0; + levelProgressText.Text = user?.Statistics?.Level.Progress.ToString("0'%'"); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs new file mode 100644 index 0000000000..cc6edcdd6a --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.Chat; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class MessageUserButton : ProfileHeaderButton + { + public readonly Bindable User = new Bindable(); + + public override string TooltipText => "send message"; + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + [Resolved(CanBeNull = true)] + private UserProfileOverlay userOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private ChatOverlay chatOverlay { get; set; } + + [Resolved] + private IAPIProvider apiProvider { get; set; } + + public MessageUserButton() + { + Content.Alpha = 0; + RelativeSizeAxes = Axes.Y; + + Child = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Envelope, + FillMode = FillMode.Fit, + Size = new Vector2(50, 14) + }; + + Action = () => + { + if (!Content.IsPresent) return; + + channelManager?.OpenPrivateChannel(User.Value); + userOverlay?.Hide(); + chatOverlay?.Show(); + }; + + User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs new file mode 100644 index 0000000000..c40ddca688 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class OverlinedInfoContainer : CompositeDrawable + { + private readonly Circle line; + private readonly OsuSpriteText title; + private readonly OsuSpriteText content; + + public string Title + { + set => title.Text = value; + } + + public string Content + { + set => content.Text = value; + } + + public Color4 LineColour + { + set => line.Colour = value; + } + + public OverlinedInfoContainer(bool big = false, int minimumWidth = 60) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + line = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 4, + }, + title = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 14 : 12, weight: FontWeight.Bold) + }, + content = new OsuSpriteText + { + Font = OsuFont.GetFont(size: big ? 40 : 18, weight: FontWeight.Light) + }, + new Container //Add a minimum size to the FillFlowContainer + { + Width = minimumWidth, + } + } + }; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs new file mode 100644 index 0000000000..2c88a83680 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Users; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class OverlinedTotalPlayTime : CompositeDrawable, IHasTooltip + { + public readonly Bindable User = new Bindable(); + + public string TooltipText { get; set; } + + private OverlinedInfoContainer info; + + public OverlinedTotalPlayTime() + { + AutoSizeAxes = Axes.Both; + + TooltipText = "0 hours"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChild = info = new OverlinedInfoContainer + { + Title = "Total Play Time", + LineColour = colours.Yellow, + }; + + User.BindValueChanged(updateTime, true); + } + + private void updateTime(ValueChangedEvent user) + { + TooltipText = (user.NewValue?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; + info.Content = formatTime(user.NewValue?.Statistics?.PlayTime); + } + + private string formatTime(int? secondsNull) + { + if (secondsNull == null) return "0h 0m"; + + int seconds = secondsNull.Value; + string time = ""; + + int days = seconds / 86400; + seconds -= days * 86400; + if (days > 0) + time += days + "d "; + + int hours = seconds / 3600; + seconds -= hours * 3600; + time += hours + "h "; + + int minutes = seconds / 60; + time += minutes + "m"; + + return time; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs new file mode 100644 index 0000000000..ddcf011277 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public abstract class ProfileHeaderButton : OsuHoverContainer + { + private readonly Box background; + private readonly Container content; + + protected override Container Content => content; + + protected override IEnumerable EffectTargets => new[] { background }; + + protected ProfileHeaderButton() + { + AutoSizeAxes = Axes.X; + + IdleColour = Color4.Black; + HoverColour = OsuColour.Gray(0.1f); + + base.Content.Add(new CircularContainer + { + Masking = true, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10 }, + } + } + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs new file mode 100644 index 0000000000..b6112a6501 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class ProfileRulesetSelector : RulesetSelector + { + private Color4 accentColour = Color4.White; + + public ProfileRulesetSelector() + { + TabContainer.Masking = false; + TabContainer.Spacing = new Vector2(10, 0); + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + accentColour = colours.Seafoam; + + foreach (TabItem tabItem in TabContainer) + ((ProfileRulesetTabItem)tabItem).AccentColour = accentColour; + } + + public void SetDefaultRuleset(RulesetInfo ruleset) + { + // Todo: This method shouldn't exist, but bindables don't provide the concept of observing a change to the default value + foreach (TabItem tabItem in TabContainer) + ((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID; + } + + public void SelectDefaultRuleset() + { + // Todo: This method shouldn't exist, but bindables don't provide the concept of observing a change to the default value + foreach (TabItem tabItem in TabContainer) + { + if (((ProfileRulesetTabItem)tabItem).IsDefault) + { + Current.Value = ((ProfileRulesetTabItem)tabItem).Value; + return; + } + } + } + + protected override TabItem CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value) + { + AccentColour = accentColour + }; + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }; + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs new file mode 100644 index 0000000000..b5170ea3a2 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs @@ -0,0 +1,125 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class ProfileRulesetTabItem : TabItem, IHasAccentColour + { + private readonly OsuSpriteText text; + private readonly SpriteIcon icon; + + private Color4 accentColour; + + public Color4 AccentColour + { + get => accentColour; + set + { + if (accentColour == value) + return; + + accentColour = value; + + updateState(); + } + } + + private bool isDefault; + + public bool IsDefault + { + get => isDefault; + set + { + if (isDefault == value) + return; + + isDefault = value; + + icon.FadeTo(isDefault ? 1 : 0, 200, Easing.OutQuint); + } + } + + public ProfileRulesetTabItem(RulesetInfo value) + : base(value) + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(3, 0), + Children = new Drawable[] + { + text = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Text = value.Name, + }, + icon = new SpriteIcon + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Alpha = 0, + AlwaysPresent = true, + Icon = FontAwesome.Solid.Star, + Size = new Vector2(12), + }, + } + }, + new HoverClickSounds() + }; + } + + protected override bool OnHover(HoverEvent e) + { + base.OnHover(e); + updateState(); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + updateState(); + } + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + private void updateState() + { + text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium); + + if (IsHovered || Active.Value) + { + text.FadeColour(Color4.White, 120, Easing.InQuad); + icon.FadeColour(Color4.White, 120, Easing.InQuad); + } + else + { + text.FadeColour(AccentColour, 120, Easing.InQuad); + icon.FadeColour(AccentColour, 120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs new file mode 100644 index 0000000000..5f79386b76 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -0,0 +1,283 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class RankGraph : Container, IHasCustomTooltip + { + private const float secondary_textsize = 13; + private const float padding = 10; + private const float fade_duration = 150; + private const int ranked_days = 88; + + private readonly RankChartLineGraph graph; + private readonly OsuSpriteText placeholder; + + private KeyValuePair[] ranks; + private int dayIndex; + public Bindable User = new Bindable(); + + public RankGraph() + { + Padding = new MarginPadding { Vertical = padding }; + Children = new Drawable[] + { + placeholder = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "No recent plays", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) + }, + graph = new RankChartLineGraph + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Y = -secondary_textsize, + Alpha = 0, + } + }; + + graph.OnBallMove += i => dayIndex = i; + + User.ValueChanged += userChanged; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + graph.LineColour = colours.Yellow; + } + + private void userChanged(ValueChangedEvent e) + { + placeholder.FadeIn(fade_duration, Easing.Out); + + if (e.NewValue?.Statistics?.Ranks.Global == null) + { + graph.FadeOut(fade_duration, Easing.Out); + ranks = null; + return; + } + + int[] userRanks = e.NewValue.RankHistory?.Data ?? new[] { e.NewValue.Statistics.Ranks.Global.Value }; + ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); + + if (ranks.Length > 1) + { + placeholder.FadeOut(fade_duration, Easing.Out); + + graph.DefaultValueCount = ranks.Length; + graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); + } + + graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); + } + + protected override bool OnHover(HoverEvent e) + { + if (ranks?.Length > 1) + { + graph.UpdateBallPosition(e.MousePosition.X); + graph.ShowBar(); + } + + return base.OnHover(e); + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + if (ranks?.Length > 1) + graph.UpdateBallPosition(e.MousePosition.X); + + return base.OnMouseMove(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (ranks?.Length > 1) + { + graph.HideBar(); + } + + base.OnHoverLost(e); + } + + private class RankChartLineGraph : LineGraph + { + private readonly CircularContainer movingBall; + private readonly Container bar; + private readonly Box ballBg; + private readonly Box line; + + public Action OnBallMove; + + public RankChartLineGraph() + { + Add(bar = new Container + { + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Alpha = 0, + RelativePositionAxes = Axes.Both, + Children = new Drawable[] + { + line = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 1.5f, + }, + movingBall = new CircularContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.Centre, + Size = new Vector2(18), + Masking = true, + BorderThickness = 4, + RelativePositionAxes = Axes.Y, + Child = ballBg = new Box { RelativeSizeAxes = Axes.Both } + } + } + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ballBg.Colour = colours.GreySeafoamDarker; + movingBall.BorderColour = line.Colour = colours.Yellow; + } + + public void UpdateBallPosition(float mouseXPosition) + { + const int duration = 200; + int index = calculateIndex(mouseXPosition); + Vector2 position = calculateBallPosition(index); + movingBall.MoveToY(position.Y, duration, Easing.OutQuint); + bar.MoveToX(position.X, duration, Easing.OutQuint); + OnBallMove.Invoke(index); + } + + public void ShowBar() => bar.FadeIn(fade_duration); + + public void HideBar() => bar.FadeOut(fade_duration); + + private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); + + private Vector2 calculateBallPosition(int index) + { + float y = GetYPosition(Values.ElementAt(index)); + return new Vector2(index / (float)(DefaultValueCount - 1), y); + } + } + + public string TooltipText => User.Value?.Statistics?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}"; + + public ITooltip GetCustomTooltip() => new RankGraphTooltip(); + + public class RankGraphTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText globalRankingText, timeText; + private readonly Box background; + + public string TooltipText { get; set; } + + public RankGraphTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 10; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "Global Ranking " + }, + globalRankingText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + timeText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDark; + } + + public void Refresh() + { + var info = TooltipText.Split('|'); + globalRankingText.Text = info[0]; + timeText.Text = info[1] == "0" ? "now" : $"{info[1]} days ago"; + } + + private bool instantMove = true; + + public void Move(Vector2 pos) + { + if (instantMove) + { + Position = pos; + instantMove = false; + } + else + this.MoveTo(pos, 200, Easing.OutQuint); + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs new file mode 100644 index 0000000000..c5e61f68f4 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -0,0 +1,86 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header.Components +{ + public class SupporterIcon : CompositeDrawable, IHasTooltip + { + private readonly Box background; + private readonly FillFlowContainer iconContainer; + private readonly CircularContainer content; + + public string TooltipText => "osu!supporter"; + + public int SupportLevel + { + set + { + int count = MathHelper.Clamp(value, 0, 3); + + if (count == 0) + { + content.Hide(); + } + else + { + content.Show(); + iconContainer.Clear(); + + for (int i = 0; i < count; i++) + { + iconContainer.Add(new SpriteIcon + { + Width = 12, + RelativeSizeAxes = Axes.Y, + Icon = FontAwesome.Solid.Heart, + }); + } + + iconContainer.Padding = new MarginPadding { Horizontal = DrawHeight / 2 }; + } + } + } + + public SupporterIcon() + { + AutoSizeAxes = Axes.X; + + InternalChild = content = new CircularContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Masking = true, + Alpha = 0, + Children = new Drawable[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + iconContainer = new FillFlowContainer + { + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Height = 0.6f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Pink; + iconContainer.Colour = colours.GreySeafoam; + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs new file mode 100644 index 0000000000..0db1cb32d7 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -0,0 +1,220 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Scoring; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class DetailHeaderContainer : CompositeDrawable + { + private readonly Dictionary scoreRankInfos = new Dictionary(); + private OverlinedInfoContainer medalInfo; + private OverlinedInfoContainer ppInfo; + private OverlinedInfoContainer detailGlobalRank; + private OverlinedInfoContainer detailCountryRank; + private FillFlowContainer fillFlow; + private RankGraph rankGraph; + + public readonly Bindable User = new Bindable(); + + private bool expanded = true; + + public bool Expanded + { + set + { + if (expanded == value) return; + + expanded = value; + + if (fillFlow == null) return; + + fillFlow.ClearTransforms(); + + if (expanded) + fillFlow.AutoSizeAxes = Axes.Y; + else + { + fillFlow.AutoSizeAxes = Axes.None; + fillFlow.ResizeHeightTo(0, 200, Easing.OutQuint); + } + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Y; + + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDarker, + }, + fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = expanded ? Axes.Y : Axes.None, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.OutQuint, + Masking = true, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new OverlinedTotalPlayTime + { + User = { BindTarget = User } + }, + medalInfo = new OverlinedInfoContainer + { + Title = "Medals", + LineColour = colours.GreenLight, + }, + ppInfo = new OverlinedInfoContainer + { + Title = "pp", + LineColour = colours.Red, + }, + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Children = new[] + { + scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH), + scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X), + scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH), + scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S), + scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A), + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 130 }, + Children = new Drawable[] + { + rankGraph = new RankGraph + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 130, + Anchor = Anchor.TopRight, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Horizontal = 10 }, + Spacing = new Vector2(0, 20), + Children = new Drawable[] + { + detailGlobalRank = new OverlinedInfoContainer(true, 110) + { + Title = "Global Ranking", + LineColour = colours.Yellow, + }, + detailCountryRank = new OverlinedInfoContainer(false, 110) + { + Title = "Country Ranking", + LineColour = colours.Yellow, + }, + } + } + } + }, + } + }, + }; + } + + private void updateDisplay(User user) + { + medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; + ppInfo.Content = user?.Statistics?.PP?.ToString("#,##0") ?? "0"; + + foreach (var scoreRankInfo in scoreRankInfos) + scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; + + detailGlobalRank.Content = user?.Statistics?.Ranks.Global?.ToString("\\##,##0") ?? "-"; + detailCountryRank.Content = user?.Statistics?.Ranks.Country?.ToString("\\##,##0") ?? "-"; + + rankGraph.User.Value = user; + } + + private class ScoreRankInfo : CompositeDrawable + { + private readonly OsuSpriteText rankCount; + + public int RankCount + { + set => rankCount.Text = value.ToString("#,##0"); + } + + public ScoreRankInfo(ScoreRank rank) + { + AutoSizeAxes = Axes.Both; + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 56, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new DrawableRank(rank) + { + RelativeSizeAxes = Axes.X, + Height = 30, + }, + rankCount = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + } + } + }; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs new file mode 100644 index 0000000000..67229a80c0 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Profile.Header +{ + public class MedalHeaderContainer : CompositeDrawable + { + private FillFlowContainer badgeFlowContainer; + + public readonly Bindable User = new Bindable(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Alpha = 0; + AutoSizeAxes = Axes.Y; + User.ValueChanged += e => updateDisplay(e.NewValue); + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDarker, + }, + new Container //artificial shadow + { + RelativeSizeAxes = Axes.X, + Height = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = new ColourInfo + { + TopLeft = Color4.Black.Opacity(0.2f), + TopRight = Color4.Black.Opacity(0.2f), + BottomLeft = Color4.Black.Opacity(0), + BottomRight = Color4.Black.Opacity(0) + } + }, + }, + badgeFlowContainer = new FillFlowContainer + { + Direction = FillDirection.Full, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 5 }, + Spacing = new Vector2(10, 10), + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, + } + }; + } + + private void updateDisplay(User user) + { + var badges = user.Badges; + badgeFlowContainer.Clear(); + + if (badges?.Length > 0) + { + Show(); + + for (var index = 0; index < badges.Length; index++) + { + int displayIndex = index; + LoadComponentAsync(new DrawableBadge(badges[index]), asyncBadge => + { + badgeFlowContainer.Add(asyncBadge); + + // load in stable order regardless of async load order. + badgeFlowContainer.SetLayoutPosition(asyncBadge, displayIndex); + }); + } + } + else + { + Hide(); + } + } + } +} diff --git a/osu.Game/Overlays/Profile/Header/RankGraph.cs b/osu.Game/Overlays/Profile/Header/RankGraph.cs deleted file mode 100644 index f74c8b5069..0000000000 --- a/osu.Game/Overlays/Profile/Header/RankGraph.cs +++ /dev/null @@ -1,219 +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.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class RankGraph : Container - { - private const float primary_textsize = 25; - private const float secondary_textsize = 13; - private const float padding = 10; - private const float fade_duration = 150; - private const int ranked_days = 88; - - private readonly SpriteText rankText, performanceText, relativeText; - private readonly RankChartLineGraph graph; - private readonly OsuSpriteText placeholder; - - private KeyValuePair[] ranks; - public Bindable User = new Bindable(); - - public RankGraph() - { - Padding = new MarginPadding { Vertical = padding }; - Children = new Drawable[] - { - placeholder = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "No recent plays", - TextSize = 14, - Font = @"Exo2.0-RegularItalic", - }, - rankText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = @"Exo2.0-RegularItalic", - TextSize = primary_textsize - }, - relativeText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = @"Exo2.0-RegularItalic", - Y = 25, - TextSize = secondary_textsize - }, - performanceText = new OsuSpriteText - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Font = @"Exo2.0-RegularItalic", - TextSize = secondary_textsize - }, - graph = new RankChartLineGraph - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 60, - Y = -secondary_textsize, - Alpha = 0, - } - }; - - graph.OnBallMove += showHistoryRankTexts; - - User.ValueChanged += userChanged; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - graph.Colour = colours.Yellow; - } - - private void userChanged(User user) - { - placeholder.FadeIn(fade_duration, Easing.Out); - - if (user?.Statistics?.Ranks.Global == null) - { - rankText.Text = string.Empty; - performanceText.Text = string.Empty; - relativeText.Text = string.Empty; - graph.FadeOut(fade_duration, Easing.Out); - ranks = null; - return; - } - - int[] userRanks = user.RankHistory?.Data ?? new[] { user.Statistics.Ranks.Global.Value }; - ranks = userRanks.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); - - if (ranks.Length > 1) - { - placeholder.FadeOut(fade_duration, Easing.Out); - - graph.DefaultValueCount = ranks.Length; - graph.Values = ranks.Select(x => -(float)Math.Log(x.Value)); - graph.SetStaticBallPosition(); - } - - graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); - - updateRankTexts(); - } - - private void updateRankTexts() - { - var user = User.Value; - - performanceText.Text = user.Statistics.PP != null ? $"{user.Statistics.PP:#,0}pp" : string.Empty; - rankText.Text = user.Statistics.Ranks.Global > 0 ? $"#{user.Statistics.Ranks.Global:#,0}" : "no rank"; - relativeText.Text = user.Country != null && user.Statistics.Ranks.Country > 0 ? $"{user.Country.FullName} #{user.Statistics.Ranks.Country:#,0}" : "no rank"; - } - - private void showHistoryRankTexts(int dayIndex) - { - rankText.Text = $"#{ranks[dayIndex].Value:#,0}"; - relativeText.Text = dayIndex + 1 == ranks.Length ? "Now" : $"{ranked_days - ranks[dayIndex].Key} days ago"; - } - - protected override bool OnHover(HoverEvent e) - { - if (ranks?.Length > 1) - { - graph.UpdateBallPosition(e.MousePosition.X); - graph.ShowBall(); - } - return base.OnHover(e); - } - - protected override bool OnMouseMove(MouseMoveEvent e) - { - if (ranks?.Length > 1) - graph.UpdateBallPosition(e.MousePosition.X); - - return base.OnMouseMove(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (ranks?.Length > 1) - { - graph.HideBall(); - updateRankTexts(); - } - - base.OnHoverLost(e); - } - - private class RankChartLineGraph : LineGraph - { - private readonly CircularContainer staticBall; - private readonly CircularContainer movingBall; - - public Action OnBallMove; - - public RankChartLineGraph() - { - Add(staticBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - Add(movingBall = new CircularContainer - { - Origin = Anchor.Centre, - Size = new Vector2(8), - Alpha = 0, - Masking = true, - RelativePositionAxes = Axes.Both, - Child = new Box { RelativeSizeAxes = Axes.Both } - }); - } - - public void SetStaticBallPosition() => staticBall.Position = new Vector2(1, GetYPosition(Values.Last())); - - public void UpdateBallPosition(float mouseXPosition) - { - int index = calculateIndex(mouseXPosition); - movingBall.Position = calculateBallPosition(index); - OnBallMove.Invoke(index); - } - - public void ShowBall() => movingBall.FadeIn(fade_duration); - - public void HideBall() => movingBall.FadeOut(fade_duration); - - private int calculateIndex(float mouseXPosition) => (int)Math.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)); - - private Vector2 calculateBallPosition(int index) - { - float y = GetYPosition(Values.ElementAt(index)); - return new Vector2(index / (float)(DefaultValueCount - 1), y); - } - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/SupporterIcon.cs deleted file mode 100644 index 1325ea4e9a..0000000000 --- a/osu.Game/Overlays/Profile/Header/SupporterIcon.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Backgrounds; -using osuTK; - -namespace osu.Game.Overlays.Profile.Header -{ - public class SupporterIcon : CircularContainer, IHasTooltip - { - private readonly Box background; - - public string TooltipText => "osu!supporter"; - - public SupporterIcon() - { - Masking = true; - Children = new Drawable[] - { - new Box { RelativeSizeAxes = Axes.Both }, - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.8f), - Masking = true, - Children = new Drawable[] - { - background = new Box { RelativeSizeAxes = Axes.Both }, - new Triangles - { - TriangleScale = 0.2f, - ColourLight = OsuColour.FromHex(@"ff7db7"), - ColourDark = OsuColour.FromHex(@"de5b95"), - RelativeSizeAxes = Axes.Both, - Velocity = 0.3f, - }, - } - }, - new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Icon = FontAwesome.fa_heart, - Scale = new Vector2(0.45f), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.Pink; - } - } -} diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs new file mode 100644 index 0000000000..dc6c3f56f8 --- /dev/null +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -0,0 +1,205 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osuTK; + +namespace osu.Game.Overlays.Profile.Header +{ + public class TopHeaderContainer : CompositeDrawable + { + private const float avatar_size = 110; + + public readonly Bindable User = new Bindable(); + + private SupporterIcon supporterTag; + private UpdateableAvatar avatar; + private OsuSpriteText usernameText; + private ExternalLinkButton openUserExternally; + private OsuSpriteText titleText; + private UpdateableFlag userFlag; + private OsuSpriteText userCountryText; + private FillFlowContainer userStats; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Height = 150; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.GreySeafoamDark, + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN }, + Height = avatar_size, + AutoSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + avatar = new UpdateableAvatar + { + Size = new Vector2(avatar_size), + Masking = true, + CornerRadius = avatar_size * 0.25f, + OpenOnClick = { Value = false }, + ShowGuestOnNull = false, + }, + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Padding = new MarginPadding { Left = 10 }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + usernameText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 24, weight: FontWeight.Regular) + }, + openUserExternally = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + new FillFlowContainer + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Regular) + }, + supporterTag = new SupporterIcon + { + Height = 20, + Margin = new MarginPadding { Top = 5 } + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Margin = new MarginPadding { Top = 10 }, + Colour = colours.GreySeafoamLighter, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 5 }, + Children = new Drawable[] + { + userFlag = new UpdateableFlag + { + Size = new Vector2(30, 20), + ShowPlaceholderOnNull = false, + }, + userCountryText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 17.5f, weight: FontWeight.Regular), + Margin = new MarginPadding { Left = 40 }, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Colour = colours.GreySeafoamLighter, + } + } + }, + } + } + } + } + } + }, + userStats = new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Y, + Width = 300, + Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN }, + Padding = new MarginPadding { Vertical = 15 }, + Spacing = new Vector2(0, 2) + } + }; + + User.BindValueChanged(user => updateUser(user.NewValue)); + } + + private void updateUser(User user) + { + avatar.User = user; + usernameText.Text = user?.Username ?? string.Empty; + openUserExternally.Link = $@"https://osu.ppy.sh/users/{user?.Id ?? 0}"; + userFlag.Country = user?.Country; + userCountryText.Text = user?.Country?.FullName ?? "Alien"; + supporterTag.SupportLevel = user?.SupportLevel ?? 0; + titleText.Text = user?.Title ?? string.Empty; + titleText.Colour = OsuColour.FromHex(user?.Colour ?? "fff"); + + userStats.Clear(); + + if (user?.Statistics != null) + { + userStats.Add(new UserStatsLine("Ranked Score", user.Statistics.RankedScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Hit Accuracy", Math.Round(user.Statistics.Accuracy, 2).ToString("#0.00'%'"))); + userStats.Add(new UserStatsLine("Play Count", user.Statistics.PlayCount.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Score", user.Statistics.TotalScore.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Total Hits", user.Statistics.TotalHits.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Maximum Combo", user.Statistics.MaxCombo.ToString("#,##0"))); + userStats.Add(new UserStatsLine("Replays Watched by Others", user.Statistics.ReplaysWatched.ToString("#,##0"))); + } + } + + private class UserStatsLine : Container + { + public UserStatsLine(string left, string right) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = left, + }, + new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold), + Text = right, + }, + }; + } + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index a8075ec295..76613c156d 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -1,480 +1,115 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.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.Components; using osu.Game.Overlays.Profile.Header; using osu.Game.Users; namespace osu.Game.Overlays.Profile { - public class ProfileHeader : Container + public class ProfileHeader : OverlayHeader { - private readonly LinkFlowContainer infoTextLeft; - private readonly LinkFlowContainer infoTextRight; - private readonly FillFlowContainer scoreText, scoreNumberText; - private readonly RankGraph rankGraph; + private UserCoverBackground coverContainer; - public readonly SupporterIcon SupporterTag; - private readonly Container coverContainer; - private readonly Sprite levelBadge; - private readonly SpriteText levelText; - private readonly GradeBadge gradeSSPlus, gradeSS, gradeSPlus, gradeS, gradeA; - private readonly Box colourBar; - private readonly DrawableFlag countryFlag; - private readonly BadgeContainer badgeContainer; + public Bindable User = new Bindable(); - private const float cover_height = 350; - private const float info_height = 150; - private const float info_width = 220; - private const float avatar_size = 110; - private const float level_position = 30; - private const float level_height = 60; - private const float stats_width = 280; + private CentreHeaderContainer centreHeaderContainer; + private DetailHeaderContainer detailHeaderContainer; - public ProfileHeader(User user) + public ProfileHeader() { - RelativeSizeAxes = Axes.X; - Height = cover_height + info_height; + User.ValueChanged += e => updateDisplay(e.NewValue); - Children = new Drawable[] - { - coverContainer = new Container - { - RelativeSizeAxes = Axes.X, - Height = cover_height, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN, Bottom = 20, Right = stats_width + UserProfileOverlay.CONTENT_X_MARGIN }, - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] - { - new UpdateableAvatar - { - User = user, - Size = new Vector2(avatar_size), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Masking = true, - CornerRadius = 5, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.25f), - Radius = 4, - }, - }, - new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = avatar_size + 10, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - SupporterTag = new SupporterIcon - { - Alpha = 0, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Y = -75, - Size = new Vector2(25, 25) - }, - 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) - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Width = 30, - Height = 20 - } - } - }, - badgeContainer = new BadgeContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Bottom = 5 }, - Alpha = 0, - }, - } - }, - colourBar = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - X = UserProfileOverlay.CONTENT_X_MARGIN, - Height = 5, - Width = info_width, - Alpha = 0 - } - } - }, - new Box // this is a temporary workaround for incorrect masking behaviour of FillMode.Fill used in UserCoverBackground (see https://github.com/ppy/osu-framework/issues/1675) - { - RelativeSizeAxes = Axes.X, - Height = 1, - Y = cover_height, - Colour = OsuColour.Gray(34), - }, - infoTextLeft = new LinkFlowContainer(t => t.TextSize = 14) - { - X = UserProfileOverlay.CONTENT_X_MARGIN, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - infoTextRight = new LinkFlowContainer(t => - { - t.TextSize = 14; - t.Font = @"Exo2.0-RegularItalic"; - }) - { - X = UserProfileOverlay.CONTENT_X_MARGIN + info_width + 20, - Y = cover_height + 20, - Width = info_width, - AutoSizeAxes = Axes.Y, - ParagraphSpacing = 0.8f, - LineSpacing = 0.2f - }, - new Container - { - X = -UserProfileOverlay.CONTENT_X_MARGIN, - RelativeSizeAxes = Axes.Y, - Width = stats_width, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Y = level_position, - Height = level_height, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - levelBadge = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Height = 50, - Width = 50, - Alpha = 0 - }, - levelText = new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Y = 11, - TextSize = 20 - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Y = cover_height, - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - Height = cover_height - level_height - level_position - 5, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.5f), - RelativeSizeAxes = Axes.Both - }, - scoreText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - scoreNumberText = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Horizontal = 20, Vertical = 18 }, - Spacing = new Vector2(0, 2) - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -64, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSSPlus = new GradeBadge("SSPlus") { Alpha = 0 }, - gradeSS = new GradeBadge("SS") { Alpha = 0 }, - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Y = -18, - Spacing = new Vector2(20, 0), - Children = new[] - { - gradeSPlus = new GradeBadge("SPlus") { Alpha = 0 }, - gradeS = new GradeBadge("S") { Alpha = 0 }, - gradeA = new GradeBadge("A") { Alpha = 0 }, - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = info_height - 15, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Black.Opacity(0.25f), - RelativeSizeAxes = Axes.Both - }, - rankGraph = new RankGraph - { - RelativeSizeAxes = Axes.Both - } - } - } - } - } - }; + TabControl.AddItem("Info"); + TabControl.AddItem("Modding"); + + centreHeaderContainer.DetailsVisible.BindValueChanged(visible => detailHeaderContainer.Expanded = visible.NewValue, true); } [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(OsuColour colours) { - levelBadge.Texture = textures.Get(@"Profile/levelbadge"); + TabControl.AccentColour = colours.Seafoam; } - private User user; - - public User User - { - get { return user; } - set - { - user = value; - loadUser(); - } - } - - private void loadUser() - { - LoadComponentAsync(new UserCoverBackground(user) + protected override Drawable CreateBackground() => + new Container { RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(200), - Depth = float.MaxValue, - }, coverContainer.Add); - - if (user.IsSupporter) - SupporterTag.Show(); - - if (!string.IsNullOrEmpty(user.Colour)) - { - colourBar.Colour = OsuColour.FromHex(user.Colour); - colourBar.Show(); - } - - void boldItalic(SpriteText t) => t.Font = @"Exo2.0-BoldItalic"; - void lightText(SpriteText t) => t.Alpha = 0.8f; - - OsuSpriteText createScoreText(string text) => new OsuSpriteText - { - TextSize = 14, - Text = text + Children = new Drawable[] + { + coverContainer = new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(OsuColour.FromHex("222").Opacity(0.8f), OsuColour.FromHex("222").Opacity(0.2f)) + }, + } }; - OsuSpriteText createScoreNumberText(string text) => new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Text = text - }; - - if (user.Country != null) - { - infoTextLeft.AddText("From ", lightText); - infoTextLeft.AddText(user.Country.FullName, boldItalic); - countryFlag.Country = user.Country; - } - - infoTextLeft.NewParagraph(); - - if (user.JoinDate.ToUniversalTime().Year < 2008) - { - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), lightText); - } - else - { - infoTextLeft.AddText("Joined ", lightText); - infoTextLeft.AddText(new DrawableJoinDate(user.JoinDate), boldItalic); - } - - if (user.LastVisit.HasValue) - { - infoTextLeft.NewLine(); - infoTextLeft.AddText("Last seen ", lightText); - infoTextLeft.AddText(new DrawableDate(user.LastVisit.Value), boldItalic); - infoTextLeft.NewParagraph(); - } - - if (user.PlayStyle?.Length > 0) - { - infoTextLeft.AddText("Plays with ", lightText); - infoTextLeft.AddText(string.Join(", ", user.PlayStyle), boldItalic); - } - - infoTextLeft.NewLine(); - infoTextLeft.AddText("Contributed ", lightText); - infoTextLeft.AddLink($@"{user.PostCount} forum posts", url: $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: boldItalic); - - string websiteWithoutProtcol = user.Website; - if (!string.IsNullOrEmpty(websiteWithoutProtcol)) - { - int protocolIndex = websiteWithoutProtcol.IndexOf("//", StringComparison.Ordinal); - if (protocolIndex >= 0) - websiteWithoutProtcol = websiteWithoutProtcol.Substring(protocolIndex + 2); - } - - tryAddInfoRightLine(FontAwesome.fa_map_marker, user.Location); - tryAddInfoRightLine(FontAwesome.fa_heart_o, user.Interests); - tryAddInfoRightLine(FontAwesome.fa_suitcase, user.Occupation); - infoTextRight.NewParagraph(); - if (!string.IsNullOrEmpty(user.Twitter)) - tryAddInfoRightLine(FontAwesome.fa_twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); - tryAddInfoRightLine(FontAwesome.fa_gamepad, user.Discord); - tryAddInfoRightLine(FontAwesome.fa_skype, user.Skype, @"skype:" + user.Skype + @"?chat"); - tryAddInfoRightLine(FontAwesome.fa_lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}"); - tryAddInfoRightLine(FontAwesome.fa_globe, websiteWithoutProtcol, user.Website); - - if (user.Statistics != null) - { - levelBadge.Show(); - levelText.Text = user.Statistics.Level.Current.ToString(); - - scoreText.Add(createScoreText("Ranked Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.RankedScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Accuracy")); - scoreNumberText.Add(createScoreNumberText($"{user.Statistics.Accuracy:0.##}%")); - scoreText.Add(createScoreText("Play Count")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.PlayCount.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Score")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalScore.ToString(@"#,0"))); - scoreText.Add(createScoreText("Total Hits")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.TotalHits.ToString(@"#,0"))); - scoreText.Add(createScoreText("Max Combo")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.MaxCombo.ToString(@"#,0"))); - scoreText.Add(createScoreText("Replays Watched by Others")); - scoreNumberText.Add(createScoreNumberText(user.Statistics.ReplaysWatched.ToString(@"#,0"))); - - gradeSSPlus.DisplayCount = user.Statistics.GradesCount.SSPlus; - gradeSSPlus.Show(); - gradeSS.DisplayCount = user.Statistics.GradesCount.SS; - gradeSS.Show(); - gradeSPlus.DisplayCount = user.Statistics.GradesCount.SPlus; - gradeSPlus.Show(); - gradeS.DisplayCount = user.Statistics.GradesCount.S; - gradeS.Show(); - gradeA.DisplayCount = user.Statistics.GradesCount.A; - gradeA.Show(); - - rankGraph.User.Value = user; - } - - badgeContainer.ShowBadges(user.Badges); - } - - private void tryAddInfoRightLine(FontAwesome icon, string str, string url = null) + protected override Drawable CreateContent() => new FillFlowContainer { - if (string.IsNullOrEmpty(str)) return; - - infoTextRight.AddIcon(icon); - if (url != null) + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - infoTextRight.AddLink(" " + str, url); + new TopHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + centreHeaderContainer = new CentreHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + detailHeaderContainer = new DetailHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + new MedalHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, + new BottomHeaderContainer + { + RelativeSizeAxes = Axes.X, + User = { BindTarget = User }, + }, } - else + }; + + protected override ScreenTitle CreateTitle() => new ProfileHeaderTitle(); + + private void updateDisplay(User user) => coverContainer.User = user; + + private class ProfileHeaderTitle : ScreenTitle + { + public ProfileHeaderTitle() { - infoTextRight.AddText(" " + str); + Title = "Player"; + Section = "Info"; } - infoTextRight.NewLine(); + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Seafoam; + } } } } diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 1aa43feb8c..f3590d4bb7 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -1,25 +1,29 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using osuTK.Graphics; -using osu.Framework.Configuration; namespace osu.Game.Overlays.Profile { - public abstract class ProfileSection : FillFlowContainer + public abstract class ProfileSection : Container { public abstract string Title { get; } public abstract string Identifier { get; } private readonly FillFlowContainer content; + private readonly Box background; + private readonly Box underscore; protected override Container Content => content; @@ -27,53 +31,109 @@ namespace osu.Game.Overlays.Profile protected ProfileSection() { - Direction = FillDirection.Vertical; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; + InternalChildren = new Drawable[] { - new OsuSpriteText + background = new Box { - Text = Title, - TextSize = 20, - Font = @"Exo2.0-RegularItalic", - Margin = new MarginPadding - { - Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, - Vertical = 10 - } + RelativeSizeAxes = Axes.Both, }, - content = new FillFlowContainer + new SectionTriangles + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, + new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Padding = new MarginPadding + Children = new Drawable[] { - Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, - Bottom = 20 - } - }, - new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(34), - EdgeSmoothness = new Vector2(1) + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, + Top = 15, + Bottom = 10, + }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = Title, + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), + }, + underscore = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding { Top = 4 }, + RelativeSizeAxes = Axes.X, + Height = 2, + } + } + }, + content = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding + { + Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, + Bottom = 20 + } + }, + }, } }; + } - // placeholder - Add(new OsuSpriteText + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; + underscore.Colour = colours.Seafoam; + } + + private class SectionTriangles : Container + { + private readonly Triangles triangles; + private readonly Box foreground; + + public SectionTriangles() { - Text = @"coming soon!", - TextSize = 16, - Font = @"Exo2.0-Medium", - Colour = Color4.Gray, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Margin = new MarginPadding { Top = 100, Bottom = 100 } - }); + RelativeSizeAxes = Axes.X; + Height = 100; + Masking = true; + Children = new Drawable[] + { + triangles = new Triangles + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + TriangleScale = 3, + }, + foreground = new Box + { + RelativeSizeAxes = Axes.Both, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + triangles.ColourLight = colours.GreySeafoamDark; + triangles.ColourDark = colours.GreySeafoamDarker.Darken(0.2f); + foreground.Colour = ColourInfo.GradientVertical(colours.GreySeafoamDarker, colours.GreySeafoamDarker.Opacity(0)); + } } } } diff --git a/osu.Game/Overlays/Profile/Sections/AboutSection.cs b/osu.Game/Overlays/Profile/Sections/AboutSection.cs index ba3ab80a0c..1bc01cfd9e 100644 --- a/osu.Game/Overlays/Profile/Sections/AboutSection.cs +++ b/osu.Game/Overlays/Profile/Sections/AboutSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Overlays.Profile.Sections { diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 4278598bf5..16326900f1 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections /// /// Display artist/title/mapper information, commonly used as the left portion of a profile or score display row (see ). /// - public class BeatmapMetadataContainer : OsuHoverContainer, IHasTooltip + public class BeatmapMetadataContainer : OsuHoverContainer { private readonly BeatmapInfo beatmap; @@ -26,8 +26,6 @@ namespace osu.Game.Overlays.Profile.Sections TooltipText = $"{beatmap.Metadata.Artist} - {beatmap.Metadata.Title}"; } - public string TooltipText { get; } - [BackgroundDependencyLoader(true)] private void load(BeatmapSetOverlay beatmapSetOverlay) { @@ -46,17 +44,16 @@ namespace osu.Game.Overlays.Profile.Sections { new OsuSpriteText { - Text = new LocalisedString(($"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), - TextSize = 15, - Font = "Exo2.0-SemiBoldItalic", + Text = new LocalisedString(( + $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", + $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] ")), + Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold, italics: true) }, new OsuSpriteText { Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), - TextSize = 12, Padding = new MarginPadding { Top = 3 }, - Font = "Exo2.0-RegularItalic", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular, italics: true) }, }, }; diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 195269d427..b6b0e605d7 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Direct; @@ -29,13 +29,11 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override void ShowMore() { - base.ShowMore(); - request = new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); request.Success += sets => Schedule(() => { - ShowMoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); + MoreButton.FadeTo(sets.Count == ItemsPerPage ? 1 : 0); + MoreButton.IsLoading = false; if (!sets.Any() && VisiblePages == 1) { diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 367d096c16..ae91837d29 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Sections.Beatmaps; diff --git a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs index 3aefce39dc..23fe6e9cd5 100644 --- a/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs +++ b/osu.Game/Overlays/Profile/Sections/DrawableProfileRow.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs index bc0f22f4d0..1b286f92d3 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedRow.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; @@ -16,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { private readonly BeatmapInfo beatmap; private readonly int playCount; - private OsuHoverContainer mapperContainer; public DrawableMostPlayedRow(BeatmapInfo beatmap, int playCount) { @@ -33,37 +33,20 @@ namespace osu.Game.Overlays.Profile.Sections.Historical CoverType = BeatmapSetCoverType.List, }; - [BackgroundDependencyLoader(true)] - private void load(UserProfileOverlay profileOverlay) + [BackgroundDependencyLoader] + private void load() { LeftFlowContainer.Add(new BeatmapMetadataContainer(beatmap)); - LeftFlowContainer.Add(new FillFlowContainer + LeftFlowContainer.Add(new LinkFlowContainer(t => t.Font = OsuFont.GetFont(size: 12)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = @"mapped by ", - TextSize = 12, - }, - mapperContainer = new OsuHoverContainer - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Text = beatmap.Metadata.AuthorString, - TextSize = 12, - Font = @"Exo2.0-MediumItalic" - } - } - }, - } - }); + }.With(d => + { + d.AddText("mapped by "); + d.AddUserLink(beatmap.Metadata.Author); + })); RightFlowContainer.Add(new FillFlowContainer { @@ -78,22 +61,17 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Text = playCount.ToString(), - TextSize = 18, - Font = @"Exo2.0-SemiBoldItalic" + Font = OsuFont.GetFont(size: 18, weight: FontWeight.SemiBold, italics: true) }, new OsuSpriteText { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Text = @"times played ", - TextSize = 12, - Font = @"Exo2.0-RegularItalic" + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular, italics: true) }, } }); - - if (profileOverlay != null) - mapperContainer.Action = () => profileOverlay.ShowUser(beatmap.BeatmapSet.Metadata.Author); } } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index ad886c363b..6085b0bc05 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests; @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private GetUserMostPlayedBeatmapsRequest request; public PaginatedMostPlayedBeatmapContainer(Bindable user) - :base(user, "Most Played Beatmaps", "No records. :(") + : base(user, "Most Played Beatmaps", "No records. :(") { ItemsPerPage = 5; @@ -24,13 +24,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override void ShowMore() { - base.ShowMore(); - request = new GetUserMostPlayedBeatmapsRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); request.Success += beatmaps => Schedule(() => { - ShowMoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); + MoreButton.FadeTo(beatmaps.Count == ItemsPerPage ? 1 : 0); + MoreButton.IsLoading = false; if (!beatmaps.Any() && VisiblePages == 1) { @@ -49,7 +47,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Api.Queue(request); } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 924b27a86d..4bdd25ee66 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Online.API.Requests; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 5207aa6528..aabfa56ee6 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -1,10 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osuTK; -using osuTK.Graphics; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -13,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Profile.Sections.Kudosu { @@ -23,70 +22,73 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu public KudosuInfo(Bindable user) { this.user.BindTo(user); - CountSection total; CountSection avaliable; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Masking = true; CornerRadius = 3; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Offset = new Vector2(0f, 1f), - Radius = 3f, - Colour = Color4.Black.Opacity(0.2f), - }; Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) - }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), Children = new[] { - total = new CountSection( - "Total Kudosu Earned", - "Based on how much of a contribution the user has made to beatmap moderation. See this link for more information." - ), - avaliable = new CountSection( - "Kudosu Avaliable", - "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet." - ), + total = new CountTotal(), + avaliable = new CountAvailable() } } }; - - this.user.ValueChanged += newUser => + this.user.ValueChanged += u => { - total.Count = newUser?.Kudosu.Total ?? 0; - avaliable.Count = newUser?.Kudosu.Available ?? 0; + total.Count = u.NewValue?.Kudosu.Total ?? 0; + avaliable.Count = u.NewValue?.Kudosu.Available ?? 0; }; } protected override bool OnClick(ClickEvent e) => true; + private class CountAvailable : CountSection + { + public CountAvailable() + : base("Kudosu Avaliable") + { + DescriptionText.Text = "Kudosu can be traded for kudosu stars, which will help your beatmap get more attention. This is the number of kudosu you haven't traded in yet."; + } + } + + private class CountTotal : CountSection + { + public CountTotal() + : base("Total Kudosu Earned") + { + DescriptionText.AddText("Based on how much of a contribution the user has made to beatmap moderation. See "); + DescriptionText.AddLink("this link", "https://osu.ppy.sh/wiki/Kudosu"); + DescriptionText.AddText(" for more information."); + } + } + private class CountSection : Container { private readonly OsuSpriteText valueText; + protected readonly LinkFlowContainer DescriptionText; + private readonly Box lineBackground; public new int Count { - set { valueText.Text = value.ToString(); } + set => valueText.Text = value.ToString(); } - public CountSection(string header, string description) + public CountSection(string header) { RelativeSizeAxes = Axes.X; Width = 0.5f; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Horizontal = 10, Top = 10, Bottom = 20 }; + Padding = new MarginPadding { Top = 10, Bottom = 20 }; Child = new FillFlowContainer { AutoSizeAxes = Axes.Y, @@ -95,41 +97,42 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu Spacing = new Vector2(0, 5), Children = new Drawable[] { - new FillFlowContainer + new CircularContainer { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] + Masking = true, + RelativeSizeAxes = Axes.X, + Height = 5, + Child = lineBackground = new Box { - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = header + ":", - TextSize = 20, - Font = @"Exo2.0-RegularItalic", - }, - valueText = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Text = "0", - TextSize = 40, - UseFullGlyphHeight = false, - Font = @"Exo2.0-RegularItalic" - } + RelativeSizeAxes = Axes.Both, } }, - new OsuTextFlowContainer(t => { t.TextSize = 19; }) + new OsuSpriteText + { + Text = header, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold) + }, + valueText = new OsuSpriteText + { + Text = "0", + Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light), + UseFullGlyphHeight = false, + }, + DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = description } } }; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + lineBackground.Colour = colours.Yellow; + DescriptionText.Colour = colours.GreySeafoamLighter; + } } } } diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index 1490a4bda7..a17b68933c 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Overlays.Profile.Sections.Kudosu; diff --git a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs index 45be08b292..575a2f2c19 100644 --- a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Overlays.Profile.Sections { diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs index 40da95c30b..b459afcb49 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedContainer.cs @@ -1,25 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Users; namespace osu.Game.Overlays.Profile.Sections { - public class PaginatedContainer : FillFlowContainer + public abstract class PaginatedContainer : FillFlowContainer { protected readonly FillFlowContainer ItemsContainer; - protected readonly OsuHoverContainer ShowMoreButton; - protected readonly LoadingAnimation ShowMoreLoading; + protected readonly ShowMoreButton MoreButton; protected readonly OsuSpriteText MissingText; protected int VisiblePages; @@ -27,11 +25,11 @@ namespace osu.Game.Overlays.Profile.Sections protected readonly Bindable User = new Bindable(); - protected APIAccess Api; + protected IAPIProvider Api; protected APIRequest RetrievalRequest; protected RulesetStore Rulesets; - public PaginatedContainer(Bindable user, string header, string missing) + protected PaginatedContainer(Bindable user, string header, string missing) { User.BindTo(user); @@ -43,40 +41,27 @@ namespace osu.Game.Overlays.Profile.Sections { new OsuSpriteText { - TextSize = 15, Text = header, - Font = "Exo2.0-RegularItalic", + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), Margin = new MarginPadding { Top = 10, Bottom = 10 }, }, ItemsContainer = new FillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Margin = new MarginPadding { Bottom = 10 } + Spacing = new Vector2(0, 2), }, - ShowMoreButton = new OsuHoverContainer + MoreButton = new ShowMoreButton { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Alpha = 0, + Margin = new MarginPadding { Top = 10 }, Action = ShowMore, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Child = new OsuSpriteText - { - TextSize = 14, - Text = "show more", - Padding = new MarginPadding {Vertical = 10, Horizontal = 15 }, - } - }, - ShowMoreLoading = new LoadingAnimation - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(14), }, MissingText = new OsuSpriteText { - TextSize = 14, + Font = OsuFont.GetFont(size: 15), Text = missing, Alpha = 0, }, @@ -84,7 +69,7 @@ namespace osu.Game.Overlays.Profile.Sections } [BackgroundDependencyLoader] - private void load(APIAccess api, RulesetStore rulesets) + private void load(IAPIProvider api, RulesetStore rulesets) { Api = api; Rulesets = rulesets; @@ -93,20 +78,15 @@ namespace osu.Game.Overlays.Profile.Sections User.TriggerChange(); } - private void onUserChanged(User newUser) + private void onUserChanged(ValueChangedEvent e) { VisiblePages = 0; ItemsContainer.Clear(); - ShowMoreButton.Hide(); - if (newUser != null) + if (e.NewValue != null) ShowMore(); } - protected virtual void ShowMore() - { - ShowMoreLoading.Show(); - ShowMoreButton.Hide(); - } + protected abstract void ShowMore(); } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs index ddbe10c05c..843f9b7ef2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawablePerformanceScore.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double? weight; - public DrawablePerformanceScore(Score score, double? weight = null) + public DrawablePerformanceScore(ScoreInfo score, double? weight = null) : base(score) { this.weight = weight; @@ -28,8 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Text = $"{pp:0}pp", Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - TextSize = 18, - Font = "Exo2.0-BoldItalic", + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }); if (weight.HasValue) @@ -40,8 +39,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, - TextSize = 11, - Font = "Exo2.0-RegularItalic", + Font = OsuFont.GetFont(size: 11, weight: FontWeight.Regular, italics: true) }); } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index a20054351d..0a90c9b135 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -1,24 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Select.Leaderboards; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { public abstract class DrawableProfileScore : DrawableProfileRow { private readonly ScoreModsContainer modsContainer; - protected readonly Score Score; + protected readonly ScoreInfo Score; - protected DrawableProfileScore(Score score) + protected DrawableProfileScore(ScoreInfo score) { Score = score; @@ -46,8 +46,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Colour = colour.GrayA, - TextSize = 11, - Font = "Exo2.0-RegularItalic" + Font = OsuFont.GetFont(size: 11, weight: FontWeight.Regular, italics: true) }; RightFlowContainer.Add(text); @@ -60,7 +59,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks modsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.5f) }); } - protected override Drawable CreateLeftVisual() => new DrawableRank(Score.Rank) + protected override Drawable CreateLeftVisual() => new UpdateableRank(Score.Rank) { RelativeSizeAxes = Axes.Y, Width = 60, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs index 3a39ef5d61..8bfca08fe7 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableTotalScore.cs @@ -1,16 +1,17 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Overlays.Profile.Sections.Ranks { public class DrawableTotalScore : DrawableProfileScore { - public DrawableTotalScore(Score score) + public DrawableTotalScore(ScoreInfo score) : base(score) { } @@ -23,8 +24,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Text = Score.TotalScore.ToString("#,###"), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - TextSize = 18, - Font = "Exo2.0-BoldItalic", + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }); } } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index ed82c62e5c..a149cfa12e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -1,14 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Users; using System; +using System.Collections.Generic; using System.Linq; -using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Bindables; +using osu.Framework.Graphics; namespace osu.Game.Overlays.Profile.Sections.Ranks { @@ -31,41 +31,41 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected override void ShowMore() { - base.ShowMore(); - request = new GetUserScoresRequest(User.Value.Id, type, VisiblePages++ * ItemsPerPage); request.Success += scores => Schedule(() => { foreach (var s in scores) - s.ApplyRuleset(Rulesets.GetRuleset(s.OnlineRulesetID)); - - ShowMoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); + s.Ruleset = Rulesets.GetRuleset(s.RulesetID); if (!scores.Any() && VisiblePages == 1) { + MoreButton.Hide(); + MoreButton.IsLoading = false; MissingText.Show(); return; } - MissingText.Hide(); + IEnumerable drawableScores; - foreach (APIScore score in scores) + switch (type) { - DrawableProfileScore drawableScore; + default: + drawableScores = scores.Select(score => new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null)); + break; - switch (type) - { - default: - drawableScore = new DrawablePerformanceScore(score, includeWeight ? Math.Pow(0.95, ItemsContainer.Count) : (double?)null); - break; - case ScoreType.Recent: - drawableScore = new DrawableTotalScore(score); - break; - } - - ItemsContainer.Add(drawableScore); + case ScoreType.Recent: + drawableScores = scores.Select(score => new DrawableTotalScore(score)); + break; } + + LoadComponentsAsync(drawableScores, s => + { + MissingText.Hide(); + MoreButton.FadeTo(scores.Count == ItemsPerPage ? 1 : 0); + MoreButton.IsLoading = false; + + ItemsContainer.AddRange(s); + }); }); Api.Queue(request); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs index f67d333a0f..1ce04effa8 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/ScoreModsContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index 965f668a68..c4b933593e 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Overlays.Profile.Sections.Ranks; using osu.Game.Online.API.Requests; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index fefb289d17..b5a508bff7 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,13 +10,13 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; -using osu.Game.Screens.Select.Leaderboards; +using osu.Game.Online.Leaderboards; namespace osu.Game.Overlays.Profile.Sections.Recent { public class DrawableRecentActivity : DrawableProfileRow { - private APIAccess api; + private IAPIProvider api; private readonly APIRecentActivity activity; @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent RightFlowContainer.Add(new DrawableDate(activity.CreatedAt) { - TextSize = 13, + Font = OsuFont.GetFont(size: 13), Colour = OsuColour.Gray(0xAA), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent switch (activity.Type) { case RecentActivityType.Rank: - return new DrawableRank(activity.ScoreRank) + return new UpdateableRank(activity.ScoreRank) { RelativeSizeAxes = Axes.Y, Width = 60, diff --git a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs index 45569271df..56ff4d4dec 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index fd5eda4e44..b72aec7a44 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Users; using System.Linq; +using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.Profile.Sections.Recent @@ -22,13 +22,11 @@ namespace osu.Game.Overlays.Profile.Sections.Recent protected override void ShowMore() { - base.ShowMore(); - request = new GetUserRecentActivitiesRequest(User.Value.Id, VisiblePages++ * ItemsPerPage); request.Success += activities => Schedule(() => { - ShowMoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); - ShowMoreLoading.Hide(); + MoreButton.FadeTo(activities.Count == ItemsPerPage ? 1 : 0); + MoreButton.IsLoading = false; if (!activities.Any() && VisiblePages == 1) { diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index fdccd00bac..8fcc5cc7c0 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Overlays.Profile.Sections.Recent; diff --git a/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs b/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs new file mode 100644 index 0000000000..5ed546c62b --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/ShowMoreButton.cs @@ -0,0 +1,146 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using System.Collections.Generic; + +namespace osu.Game.Overlays.Profile.Sections +{ + public class ShowMoreButton : OsuHoverContainer + { + private const float fade_duration = 200; + + private readonly Box background; + private readonly LoadingAnimation loading; + private readonly FillFlowContainer content; + + protected override IEnumerable EffectTargets => new[] { background }; + + private bool isLoading; + + public bool IsLoading + { + get => isLoading; + set + { + if (isLoading == value) + return; + + isLoading = value; + + Enabled.Value = !isLoading; + + if (value) + { + loading.FadeIn(fade_duration, Easing.OutQuint); + content.FadeOut(fade_duration, Easing.OutQuint); + } + else + { + loading.FadeOut(fade_duration, Easing.OutQuint); + content.FadeIn(fade_duration, Easing.OutQuint); + } + } + } + + public ShowMoreButton() + { + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { + new CircularContainer + { + Masking = true, + Size = new Vector2(140, 30), + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + content = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + new ChevronIcon(), + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "show more".ToUpper(), + }, + new ChevronIcon(), + } + }, + loading = new LoadingAnimation + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(12) + }, + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colors) + { + IdleColour = colors.GreySeafoamDark; + HoverColour = colors.GreySeafoam; + } + + protected override bool OnClick(ClickEvent e) + { + if (!Enabled.Value) + return false; + + try + { + return base.OnClick(e); + } + finally + { + // run afterwards as this will disable this button. + IsLoading = true; + } + } + + private class ChevronIcon : SpriteIcon + { + private const int bottom_margin = 2; + private const int icon_size = 8; + + public ChevronIcon() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Margin = new MarginPadding { Bottom = bottom_margin }; + Size = new Vector2(icon_size); + Icon = FontAwesome.Solid.ChevronDown; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colors) + { + Colour = colors.Yellow; + } + } + } +} diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index 35888bee73..0808cc8fcc 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osuTK; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList @@ -37,8 +37,8 @@ namespace osu.Game.Overlays.SearchableList Direction = FillDirection.Horizontal, Children = new[] { - new DisplayStyleToggleButton(FontAwesome.fa_th_large, PanelDisplayStyle.Grid, DisplayStyle), - new DisplayStyleToggleButton(FontAwesome.fa_list_ul, PanelDisplayStyle.List, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.Solid.ThLarge, PanelDisplayStyle.Grid, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.Solid.ListUl, PanelDisplayStyle.List, DisplayStyle), }, }, Dropdown = new SlimEnumDropdown @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.SearchableList private readonly PanelDisplayStyle style; private readonly Bindable bindable; - public DisplayStyleToggleButton(FontAwesome icon, PanelDisplayStyle style, Bindable bindable) + public DisplayStyleToggleButton(IconUsage icon, PanelDisplayStyle style, Bindable bindable) { this.bindable = bindable; this.style = style; @@ -78,13 +78,13 @@ namespace osu.Game.Overlays.SearchableList }; bindable.ValueChanged += Bindable_ValueChanged; - Bindable_ValueChanged(bindable.Value); + Bindable_ValueChanged(new ValueChangedEvent(bindable.Value, bindable.Value)); Action = () => bindable.Value = this.style; } - private void Bindable_ValueChanged(PanelDisplayStyle style) + private void Bindable_ValueChanged(ValueChangedEvent e) { - icon.FadeTo(style == this.style ? 1.0f : 0.5f, 100); + icon.FadeTo(e.NewValue == style ? 1.0f : 0.5f, 100); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/SearchableList/HeaderTabControl.cs b/osu.Game/Overlays/SearchableList/HeaderTabControl.cs index 07f1714e2c..2087a72c54 100644 --- a/osu.Game/Overlays/SearchableList/HeaderTabControl.cs +++ b/osu.Game/Overlays/SearchableList/HeaderTabControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Graphics.UserInterface; @@ -19,9 +19,10 @@ namespace osu.Game.Overlays.SearchableList private class HeaderTabItem : OsuTabItem { - public HeaderTabItem(T value) : base(value) + public HeaderTabItem(T value) + : base(value) { - Text.TextSize = 16; + Text.Font = Text.Font.With(size: 16); } } } diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index bd03ff9734..b0a8a0e77d 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK.Graphics; @@ -28,6 +28,11 @@ namespace osu.Game.Overlays.SearchableList protected abstract T DefaultTab { get; } protected virtual Drawable CreateSupplementaryControls() => null; + /// + /// The amount of padding added to content (does not affect background or tab control strip). + /// + protected virtual float ContentHorizontalPadding => SearchableListOverlay.WIDTH_PADDING; + protected SearchableListFilterControl() { if (!typeof(T).IsEnum) @@ -62,7 +67,11 @@ namespace osu.Game.Overlays.SearchableList { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Top = padding, Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Padding = new MarginPadding + { + Top = padding, + Horizontal = ContentHorizontalPadding + }, Children = new Drawable[] { Search = new FilterSearchTextBox @@ -118,16 +127,13 @@ namespace osu.Game.Overlays.SearchableList private class FilterSearchTextBox : SearchTextBox { - protected override Color4 BackgroundUnfocused => backgroundColour; - protected override Color4 BackgroundFocused => backgroundColour; protected override bool AllowCommit => true; - private Color4 backgroundColour; - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - backgroundColour = colours.Gray2.Opacity(0.9f); + BackgroundUnfocused = OsuColour.Gray(0.06f); + BackgroundFocused = OsuColour.Gray(0.12f); } } } diff --git a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs index 312197c7ae..73dca956d1 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListHeader.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListHeader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.SearchableList { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.SearchableList protected abstract Color4 BackgroundColour { get; } protected abstract T DefaultTab { get; } protected abstract Drawable CreateHeaderText(); - protected abstract FontAwesome Icon { get; } + protected abstract IconUsage Icon { get; } protected SearchableListHeader() { diff --git a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs index 2c97cbeb40..293ee4bcda 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Graphics; @@ -11,7 +11,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList { - public abstract class SearchableListOverlay : WaveOverlayContainer + public abstract class SearchableListOverlay : FullscreenOverlay { public static readonly float WIDTH_PADDING = 80; } @@ -32,8 +32,6 @@ namespace osu.Game.Overlays.SearchableList protected SearchableListOverlay() { - RelativeSizeAxes = Axes.Both; - Children = new Drawable[] { new Box @@ -103,7 +101,7 @@ namespace osu.Game.Overlays.SearchableList protected override void OnFocus(FocusEvent e) { - GetContainingInputManager().ChangeFocus(Filter.Search); + Filter.Search.TakeFocus(); } protected override void PopIn() diff --git a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs index 4c7f3e1c31..f320ef1344 100644 --- a/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs +++ b/osu.Game/Overlays/SearchableList/SlimEnumDropdown.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs index 9eb68b416a..c02db40eca 100644 --- a/osu.Game/Overlays/Settings/DangerousSettingsButton.cs +++ b/osu.Game/Overlays/Settings/DangerousSettingsButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; diff --git a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs index 60a1c7c125..93b07fbac7 100644 --- a/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/RulesetSettingsSubsection.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; @@ -9,7 +10,7 @@ namespace osu.Game.Overlays.Settings { /// /// A which provides subclasses with the - /// from the 's . + /// from the 's . /// public abstract class RulesetSettingsSubsection : SettingsSubsection { diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index ad79024f62..2c25808170 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio private class AudioDeviceSettingsDropdown : SettingsDropdown { - protected override OsuDropdown CreateDropdown() => new AudioDeviceDropdownControl { Items = Items }; + protected override OsuDropdown CreateDropdown() => new AudioDeviceDropdownControl(); private class AudioDeviceDropdownControl : DropdownControl { diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index 35f0a19796..4e43caff23 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Configuration; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index da96c6ef30..aaa4302553 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index fa4a714ba3..0124f7090e 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index d9326b686f..772f5c2039 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Audio; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class AudioSection : SettingsSection { public override string Header => "Audio"; - public override FontAwesome Icon => FontAwesome.fa_volume_up; + public override IconUsage Icon => FontAwesome.Solid.VolumeUp; public AudioSection() { diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs index b14a4b8773..6897b42f4f 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GCSettings.cs @@ -1,8 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using System.Runtime; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; @@ -13,37 +12,17 @@ namespace osu.Game.Overlays.Settings.Sections.Debug { protected override string Header => "Garbage Collector"; - private readonly Bindable latencyMode = new Bindable(); - private Bindable configLatencyMode; - [BackgroundDependencyLoader] private void load(FrameworkDebugConfigManager config) { Children = new Drawable[] { - new SettingsEnumDropdown - { - LabelText = "Active mode", - Bindable = latencyMode - }, new SettingsButton { Text = "Force garbage collection", Action = GC.Collect }, }; - - configLatencyMode = config.GetBindable(DebugSetting.ActiveGCMode); - configLatencyMode.BindValueChanged(v => latencyMode.Value = (LatencyMode)v, true); - latencyMode.BindValueChanged(v => configLatencyMode.Value = (GCLatencyMode)v); - } - - private enum LatencyMode - { - Batch = GCLatencyMode.Batch, - Interactive = GCLatencyMode.Interactive, - LowLatency = GCLatencyMode.LowLatency, - SustainedLowLatency = GCLatencyMode.SustainedLowLatency } } } diff --git a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs index 8b09a2a26a..f063898a9f 100644 --- a/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Debug/GeneralSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Configuration; @@ -31,6 +31,11 @@ namespace osu.Game.Overlays.Settings.Sections.Debug LabelText = "Bypass caching (slow)", Bindable = config.GetBindable(DebugSetting.BypassCaching) }, + new SettingsCheckbox + { + LabelText = "Bypass front-to-back render pass", + Bindable = config.GetBindable(DebugSetting.BypassFrontToBackPass) + } }; } } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 0502e98192..0149cab802 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Debug; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class DebugSection : SettingsSection { public override string Header => "Debug"; - public override FontAwesome Icon => FontAwesome.fa_bug; + public override IconUsage Icon => FontAwesome.Solid.Bug; public DebugSection() { diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 21d5d452bf..997d1354b3 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index a9cefa81da..2c6b2663c6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Configuration; @@ -17,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { new SettingsCheckbox { - LabelText = "Increase visibility of first object with \"Hidden\" mod", + LabelText = "Increase visibility of first object when visual impairment mods are enabled", Bindable = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs index 235ff0f319..3e2272dba6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/SongSelectSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index f565ff8556..97d9d3c697 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -1,19 +1,19 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Graphics; using osu.Game.Overlays.Settings.Sections.Gameplay; using osu.Game.Rulesets; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Settings.Sections { public class GameplaySection : SettingsSection { public override string Header => "Gameplay"; - public override FontAwesome Icon => FontAwesome.fa_circle_o; + public override IconUsage Icon => FontAwesome.Regular.Circle; public GameplaySection() { diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index 977d75079b..236bfbecc3 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Configuration; diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 6623fbc73b..66fec1ecf9 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -16,7 +16,10 @@ using System.ComponentModel; using osu.Game.Graphics; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; using Container = osu.Framework.Graphics.Containers.Container; @@ -40,7 +43,7 @@ namespace osu.Game.Overlays.Settings.Sections.General public bool Bounding { - get { return bounding; } + get => bounding; set { bounding = value; @@ -57,14 +60,14 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, APIAccess api) + private void load(OsuColour colours, IAPIProvider api) { this.colours = colours; api?.Register(this); } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { form = null; @@ -77,32 +80,41 @@ namespace osu.Game.Overlays.Settings.Sections.General { Text = "ACCOUNT", Margin = new MarginPadding { Bottom = 5 }, - Font = @"Exo2.0-Black", + Font = OsuFont.GetFont(weight: FontWeight.Black), }, - form = new LoginForm() + form = new LoginForm + { + RequestHide = RequestHide + } }; break; + case APIState.Failing: - Children = new Drawable[] - { - new OsuSpriteText - { - Text = "Connection failing :(", - }, - }; - break; case APIState.Connecting: + LinkFlowContainer linkFlow; + Children = new Drawable[] { - new OsuSpriteText + new LoadingAnimation { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Connecting...", + State = { Value = Visibility.Visible }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + linkFlow = new LinkFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + TextAnchor = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Text = state == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ", Margin = new MarginPadding { Top = 10, Bottom = 10 }, }, }; + + linkFlow.AddLink("cancel", api.Logout, string.Empty); break; + case APIState.Online: Children = new Drawable[] { @@ -126,8 +138,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = "Signed in", - TextSize = 18, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), Margin = new MarginPadding { Top = 5, Bottom = 5 }, }, }, @@ -143,30 +154,32 @@ namespace osu.Game.Overlays.Settings.Sections.General }; panel.Status.BindTo(api.LocalUser.Value.Status); + panel.Activity.BindTo(api.LocalUser.Value.Activity); - dropdown.Current.ValueChanged += newValue => + dropdown.Current.BindValueChanged(action => { - switch (newValue) + switch (action.NewValue) { case UserAction.Online: api.LocalUser.Value.Status.Value = new UserStatusOnline(); dropdown.StatusColour = colours.Green; break; + case UserAction.DoNotDisturb: api.LocalUser.Value.Status.Value = new UserStatusDoNotDisturb(); dropdown.StatusColour = colours.Red; break; + case UserAction.AppearOffline: api.LocalUser.Value.Status.Value = new UserStatusOffline(); dropdown.StatusColour = colours.Gray7; break; + case UserAction.SignOut: api.Logout(); break; } - }; - dropdown.Current.TriggerChange(); - + }, true); break; } @@ -187,7 +200,9 @@ namespace osu.Game.Overlays.Settings.Sections.General { private TextBox username; private TextBox password; - private APIAccess api; + private IAPIProvider api; + + public Action RequestHide; private void performLogin() { @@ -196,7 +211,7 @@ namespace osu.Game.Overlays.Settings.Sections.General } [BackgroundDependencyLoader(permitNulls: true)] - private void load(APIAccess api, OsuConfigManager config) + private void load(IAPIProvider api, OsuConfigManager config, AccountCreationOverlay accountCreation) { this.api = api; Direction = FillDirection.Vertical; @@ -207,14 +222,14 @@ namespace osu.Game.Overlays.Settings.Sections.General { username = new OsuTextBox { - PlaceholderText = "Email address", + PlaceholderText = "email address", RelativeSizeAxes = Axes.X, Text = api?.ProvidedUsername ?? string.Empty, TabbableContentContainer = this }, password = new OsuPasswordTextBox { - PlaceholderText = "Password", + PlaceholderText = "password", RelativeSizeAxes = Axes.X, TabbableContentContainer = this, OnCommit = (sender, newText) => performLogin() @@ -237,7 +252,11 @@ namespace osu.Game.Overlays.Settings.Sections.General new SettingsButton { Text = "Register", - //Action = registerLink + Action = () => + { + RequestHide(); + accountCreation.Show(); + } } }; } @@ -264,6 +283,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { var h = Header as UserDropdownHeader; if (h == null) return; + h.StatusColour = value; } } @@ -299,7 +319,7 @@ namespace osu.Game.Overlays.Settings.Sections.General BackgroundColour = colours.Gray3; } - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item); + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item); private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem { @@ -322,12 +342,10 @@ namespace osu.Game.Overlays.Settings.Sections.General public const float LABEL_LEFT_MARGIN = 20; private readonly SpriteIcon statusIcon; + public Color4 StatusColour { - set - { - statusIcon.FadeColour(value, 500, Easing.OutQuint); - } + set => statusIcon.FadeColour(value, 500, Easing.OutQuint); } public UserDropdownHeader() @@ -350,7 +368,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.Regular.Circle, Size = new Vector2(14), }); @@ -368,10 +386,13 @@ namespace osu.Game.Overlays.Settings.Sections.General private enum UserAction { Online, + [Description(@"Do not disturb")] DoNotDisturb, + [Description(@"Appear offline")] AppearOffline, + [Description(@"Sign out")] SignOut, } diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 34d13b1462..188c9c05ef 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Configuration; @@ -15,19 +15,20 @@ namespace osu.Game.Overlays.Settings.Sections.General [BackgroundDependencyLoader] private void load(Storage storage, OsuConfigManager config) { - Children = new Drawable[] + Add(new SettingsEnumDropdown { - new SettingsEnumDropdown - { - LabelText = "Release stream", - Bindable = config.GetBindable(OsuSetting.ReleaseStream), - }, - new SettingsButton + LabelText = "Release stream", + Bindable = config.GetBindable(OsuSetting.ReleaseStream), + }); + + if (RuntimeInfo.IsDesktop) + { + Add(new SettingsButton { Text = "Open osu! folder", Action = storage.OpenInNativeExplorer, - } - }; + }); + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index f75f01c034..d9947f16cc 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.General; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GeneralSection : SettingsSection { public override string Header => "General"; - public override FontAwesome Icon => FontAwesome.fa_gear; + public override IconUsage Icon => FontAwesome.Solid.Cog; public GeneralSection() { diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 54049bfb1f..01cdc9aa32 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 685244e06b..f4de4c0c41 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -1,14 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Drawing; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Configuration; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings.Sections.Graphics { @@ -16,45 +23,66 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { protected override string Header => "Layout"; - private FillFlowContainer letterboxSettings; + private FillFlowContainer> scalingSettings; - private Bindable letterboxing; + private Bindable scalingMode; private Bindable sizeFullscreen; + private readonly IBindableList windowModes = new BindableList(); private OsuGameBase game; private SettingsDropdown resolutionDropdown; - private SettingsEnumDropdown windowModeDropdown; + private SettingsDropdown windowModeDropdown; + + private Bindable scalingPositionX; + private Bindable scalingPositionY; + private Bindable scalingSizeX; + private Bindable scalingSizeY; private const int transition_duration = 400; [BackgroundDependencyLoader] - private void load(FrameworkConfigManager config, OsuGameBase game) + private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, OsuGameBase game, GameHost host) { this.game = game; - letterboxing = config.GetBindable(FrameworkSetting.Letterboxing); + scalingMode = osuConfig.GetBindable(OsuSetting.Scaling); sizeFullscreen = config.GetBindable(FrameworkSetting.SizeFullscreen); + scalingSizeX = osuConfig.GetBindable(OsuSetting.ScalingSizeX); + scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); + scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); + scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); + + if (host.Window != null) + windowModes.BindTo(host.Window.SupportedWindowModes); Container resolutionSettingsContainer; Children = new Drawable[] { - windowModeDropdown = new SettingsEnumDropdown + windowModeDropdown = new SettingsDropdown { LabelText = "Screen mode", Bindable = config.GetBindable(FrameworkSetting.WindowMode), + ItemSource = windowModes, }, resolutionSettingsContainer = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, - new SettingsCheckbox + new SettingsSlider { - LabelText = "Letterboxing", - Bindable = letterboxing, + LabelText = "UI Scaling", + TransferValueOnCommit = true, + Bindable = osuConfig.GetBindable(OsuSetting.UIScale), + KeyboardStep = 0.01f }, - letterboxSettings = new FillFlowContainer + new SettingsEnumDropdown + { + LabelText = "Screen Scaling", + Bindable = osuConfig.GetBindable(OsuSetting.Scaling), + }, + scalingSettings = new FillFlowContainer> { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, @@ -62,25 +90,38 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics AutoSizeDuration = transition_duration, AutoSizeEasing = Easing.OutQuint, Masking = true, - - Children = new Drawable[] + Children = new[] { - new SettingsSlider + new SettingsSlider { LabelText = "Horizontal position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX), + Bindable = scalingPositionX, KeyboardStep = 0.01f }, - new SettingsSlider + new SettingsSlider { LabelText = "Vertical position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY), + Bindable = scalingPositionY, + KeyboardStep = 0.01f + }, + new SettingsSlider + { + LabelText = "Horizontal scale", + Bindable = scalingSizeX, + KeyboardStep = 0.01f + }, + new SettingsSlider + { + LabelText = "Vertical scale", + Bindable = scalingSizeY, KeyboardStep = 0.01f }, } }, }; + scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable)); + var resolutions = getResolutions(); if (resolutions.Count > 1) @@ -93,9 +134,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics Bindable = sizeFullscreen }; - windowModeDropdown.Bindable.BindValueChanged(windowMode => + windowModeDropdown.Bindable.BindValueChanged(mode => { - if (windowMode == WindowMode.Fullscreen) + if (mode.NewValue == WindowMode.Fullscreen) { resolutionDropdown.Show(); sizeFullscreen.TriggerChange(); @@ -105,14 +146,58 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, true); } - letterboxing.BindValueChanged(isVisible => + scalingMode.BindValueChanged(mode => { - letterboxSettings.ClearTransforms(); - letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + scalingSettings.ClearTransforms(); + scalingSettings.AutoSizeAxes = mode.NewValue != ScalingMode.Off ? Axes.Y : Axes.None; - if (!isVisible) - letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + if (mode.NewValue == ScalingMode.Off) + scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + + scalingSettings.ForEach(s => s.TransferValueOnCommit = mode.NewValue == ScalingMode.Everything); }, true); + + windowModes.ItemsAdded += _ => windowModesChanged(); + windowModes.ItemsRemoved += _ => windowModesChanged(); + + windowModesChanged(); + } + + private void windowModesChanged() + { + if (windowModes.Count > 1) + windowModeDropdown.Show(); + else + windowModeDropdown.Hide(); + } + + /// + /// Create a delayed bindable which only updates when a condition is met. + /// + /// The config bindable. + /// A bindable which will propagate updates with a delay. + private void bindPreviewEvent(Bindable bindable) + { + bindable.ValueChanged += _ => + { + switch (scalingMode.Value) + { + case ScalingMode.Gameplay: + showPreview(); + break; + } + }; + } + + private Drawable preview; + + private void showPreview() + { + if (preview?.IsAlive != true) + game.Add(preview = new ScalingPreview()); + + preview.FadeOutFromOne(1500); + preview.Expire(); } private IReadOnlyList getResolutions() @@ -132,9 +217,27 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics return resolutions; } + private class ScalingPreview : ScalingContainer + { + public ScalingPreview() + { + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }; + } + } + + private class UIScaleSlider : OsuSliderBar + { + public override string TooltipText => base.TooltipText + "x"; + } + private class ResolutionSettingsDropdown : SettingsDropdown { - protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl { Items = Items }; + protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl(); private class ResolutionDropdownControl : DropdownControl { @@ -142,6 +245,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { if (item == new Size(9999, 9999)) return "Default"; + return $"{item.Width}x{item.Height}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs index 71d2b31946..92f64d0e14 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/MainMenuSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Configuration; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs index 5f3c7aa7e9..7317076c54 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/RendererSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Configuration; diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 1ce25bf517..3d6086d3ea 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Graphics; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class GraphicsSection : SettingsSection { public override string Header => "Graphics"; - public override FontAwesome Icon => FontAwesome.fa_laptop; + public override IconUsage Icon => FontAwesome.Solid.Laptop; public GraphicsSection() { diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs index 51b0296ca4..55c7210d6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyboardSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; @@ -9,13 +9,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input { protected override string Header => "Keyboard"; - public KeyboardSettings(KeyBindingOverlay keyConfig) + public KeyboardSettings(KeyBindingPanel keyConfig) { Children = new Drawable[] { new SettingsButton { Text = "Key configuration", + TooltipText = "Change global shortcut keys and gameplay bindings", Action = keyConfig.ToggleVisibility }, }; diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index c4d180790c..4f2f3dfd1a 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Input; -using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; @@ -62,13 +62,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input const string raw_mouse_handler = @"OsuTKRawMouseHandler"; const string standard_mouse_handler = @"OsuTKMouseHandler"; - ignoredInputHandler.Value = enabled ? standard_mouse_handler : raw_mouse_handler; + ignoredInputHandler.Value = enabled.NewValue ? standard_mouse_handler : raw_mouse_handler; }; ignoredInputHandler = config.GetBindable(FrameworkSetting.IgnoredInputHandlers); ignoredInputHandler.ValueChanged += handler => { - bool raw = !handler.Contains("Raw"); + bool raw = !handler.NewValue.Contains("Raw"); rawInputToggle.Value = raw; sensitivity.Bindable.Disabled = !raw; }; @@ -78,67 +78,16 @@ namespace osu.Game.Overlays.Settings.Sections.Input private class SensitivitySetting : SettingsSlider { - public override Bindable Bindable - { - get { return ((SensitivitySlider)Control).Sensitivity; } - - set - { - BindableDouble doubleValue = (BindableDouble)value; - - // create a second layer of bindable so we can only handle state changes when not being dragged. - ((SensitivitySlider)Control).Sensitivity = doubleValue; - - // this bindable will still act as the "interactive" bindable displayed during a drag. - base.Bindable = new BindableDouble(doubleValue.Value) - { - Default = doubleValue.Default, - MinValue = doubleValue.MinValue, - MaxValue = doubleValue.MaxValue - }; - - // one-way binding to update the sliderbar with changes from external actions. - doubleValue.DisabledChanged += disabled => base.Bindable.Disabled = disabled; - doubleValue.ValueChanged += newValue => base.Bindable.Value = newValue; - } - } - public SensitivitySetting() { KeyboardStep = 0.01f; + TransferValueOnCommit = true; } } private class SensitivitySlider : OsuSliderBar { - public Bindable Sensitivity; - - public SensitivitySlider() - { - Current.ValueChanged += newValue => - { - if (!isDragging && Sensitivity != null) - Sensitivity.Value = newValue; - }; - } - - private bool isDragging; - - protected override bool OnDragStart(DragStartEvent e) - { - isDragging = true; - return base.OnDragStart(e); - } - - protected override bool OnDragEnd(DragEndEvent e) - { - isDragging = false; - Current.TriggerChange(); - - return base.OnDragEnd(e); - } - - public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : Current.Value.ToString(@"0.##x"); + public override string TooltipText => Current.Disabled ? "Enable raw input to adjust sensitivity" : $"{base.TooltipText}x"; } } } diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index df94c1ad08..2a348b4e03 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Game.Overlays.Settings.Sections @@ -10,9 +10,9 @@ namespace osu.Game.Overlays.Settings.Sections public class InputSection : SettingsSection { public override string Header => "Input"; - public override FontAwesome Icon => FontAwesome.fa_keyboard_o; + public override IconUsage Icon => FontAwesome.Regular.Keyboard; - public InputSection(KeyBindingOverlay keyConfig) + public InputSection(KeyBindingPanel keyConfig) { Children = new Drawable[] { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs index ebabdd7a5d..a124501454 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DeleteAllBeatmapsDialog.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Overlays.Settings.Sections.Maintenance @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance { BodyText = "Everything?"; - Icon = FontAwesome.fa_trash_o; + Icon = FontAwesome.Regular.TrashAlt; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 835ec808bc..398a091486 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using System.Threading.Tasks; @@ -25,9 +25,9 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [BackgroundDependencyLoader] private void load(BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay) { - Children = new Drawable[] + if (beatmaps.SupportsImportFromStable) { - importBeatmapsButton = new SettingsButton + Add(importBeatmapsButton = new SettingsButton { Text = "Import beatmaps from stable", Action = () => @@ -35,20 +35,25 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance importBeatmapsButton.Enabled.Value = false; beatmaps.ImportFromStableAsync().ContinueWith(t => Schedule(() => importBeatmapsButton.Enabled.Value = true)); } - }, - deleteBeatmapsButton = new DangerousSettingsButton + }); + } + + Add(deleteBeatmapsButton = new DangerousSettingsButton + { + Text = "Delete ALL beatmaps", + Action = () => { - Text = "Delete ALL beatmaps", - Action = () => + dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => { - dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => - { - deleteBeatmapsButton.Enabled.Value = false; - Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); - })); - } - }, - importSkinsButton = new SettingsButton + deleteBeatmapsButton.Enabled.Value = false; + Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true)); + })); + } + }); + + if (skins.SupportsImportFromStable) + { + Add(importSkinsButton = new SettingsButton { Text = "Import skins from stable", Action = () => @@ -56,7 +61,11 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance importSkinsButton.Enabled.Value = false; skins.ImportFromStableAsync().ContinueWith(t => Schedule(() => importSkinsButton.Enabled.Value = true)); } - }, + }); + } + + AddRange(new Drawable[] + { deleteSkinsButton = new DangerousSettingsButton { Text = "Delete ALL skins", @@ -91,7 +100,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); } }, - }; + }); } } } diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index 673a49ea8f..0f3acd5b7f 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Maintenance; using osuTK; @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections public class MaintenanceSection : SettingsSection { public override string Header => "Maintenance"; - public override FontAwesome Icon => FontAwesome.fa_wrench; + public override IconUsage Icon => FontAwesome.Solid.Wrench; public MaintenanceSection() { diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index 62e307f323..a8b3e45a83 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index 3a2e52d2eb..80295690c0 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Settings.Sections.Online; namespace osu.Game.Overlays.Settings.Sections @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections public class OnlineSection : SettingsSection { public override string Header => "Online"; - public override FontAwesome Icon => FontAwesome.fa_globe; + public override IconUsage Icon => FontAwesome.Solid.GlobeAsia; public OnlineSection() { diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 938e2ca2c3..5d7542ca2b 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; -using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; using osuTK; @@ -19,9 +19,9 @@ namespace osu.Game.Overlays.Settings.Sections public override string Header => "Skin"; - public override FontAwesome Icon => FontAwesome.fa_paint_brush; + public override IconUsage Icon => FontAwesome.Solid.PaintBrush; - private readonly Bindable dropdownBindable = new Bindable(); + private readonly Bindable dropdownBindable = new Bindable { Default = SkinInfo.Default }; private readonly Bindable configBindable = new Bindable(); private SkinManager skins; @@ -52,6 +52,16 @@ namespace osu.Game.Overlays.Settings.Sections LabelText = "Adjust gameplay cursor size based on current beatmap", Bindable = config.GetBindable(OsuSetting.AutoCursorSize) }, + new SettingsCheckbox + { + LabelText = "Beatmap skins", + Bindable = config.GetBindable(OsuSetting.BeatmapSkins) + }, + new SettingsCheckbox + { + LabelText = "Beatmap hitsounds", + Bindable = config.GetBindable(OsuSetting.BeatmapHitsounds) + }, }; skins.ItemAdded += itemAdded; @@ -66,11 +76,12 @@ namespace osu.Game.Overlays.Settings.Sections if (skinDropdown.Items.All(s => s.ID != configBindable.Value)) configBindable.Value = 0; - configBindable.BindValueChanged(v => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == v), true); - dropdownBindable.BindValueChanged(v => configBindable.Value = v.ID); + configBindable.BindValueChanged(id => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == id.NewValue), true); + dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID); } private void itemRemoved(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => i.ID != s.ID).ToArray()); + private void itemAdded(SkinInfo s) => Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(s).ToArray()); protected override void Dispose(bool isDisposing) @@ -91,7 +102,7 @@ namespace osu.Game.Overlays.Settings.Sections private class SkinSettingsDropdown : SettingsDropdown { - protected override OsuDropdown CreateDropdown() => new SkinDropdownControl { Items = Items }; + protected override OsuDropdown CreateDropdown() => new SkinDropdownControl(); private class SkinDropdownControl : DropdownControl { diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 6689664988..088d69c031 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -1,17 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SettingsButton : TriangleButton + public class SettingsButton : TriangleButton, IHasTooltip { public SettingsButton() { RelativeSizeAxes = Axes.X; - Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }; + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }; + } + + public string TooltipText { get; set; } + + public override IEnumerable FilterTerms + { + get + { + if (TooltipText != null) + return base.FilterTerms.Append(TooltipText); + + return base.FilterTerms; + } } } } diff --git a/osu.Game/Overlays/Settings/SettingsCheckbox.cs b/osu.Game/Overlays/Settings/SettingsCheckbox.cs index 2c0f510cb8..a554159fd7 100644 --- a/osu.Game/Overlays/Settings/SettingsCheckbox.cs +++ b/osu.Game/Overlays/Settings/SettingsCheckbox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -10,12 +10,14 @@ namespace osu.Game.Overlays.Settings { private OsuCheckbox checkbox; + private string labelText; + protected override Drawable CreateControl() => checkbox = new OsuCheckbox(); public override string LabelText { - get { return checkbox.LabelText; } - set { checkbox.LabelText = value; } + get => labelText; + set => checkbox.LabelText = labelText = value; } } } diff --git a/osu.Game/Overlays/Settings/SettingsDropdown.cs b/osu.Game/Overlays/Settings/SettingsDropdown.cs index 1c3eb6c245..167061f485 100644 --- a/osu.Game/Overlays/Settings/SettingsDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsDropdown.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -12,23 +13,23 @@ namespace osu.Game.Overlays.Settings { protected new OsuDropdown Control => (OsuDropdown)base.Control; - private IEnumerable items = Enumerable.Empty(); - public IEnumerable Items { - get => items; - set - { - items = value; - - if (Control != null) - Control.Items = value; - } + get => Control.Items; + set => Control.Items = value; } + public IBindableList ItemSource + { + get => Control.ItemSource; + set => Control.ItemSource = value; + } + + public override IEnumerable FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.ToString())); + protected sealed override Drawable CreateControl() => CreateDropdown(); - protected virtual OsuDropdown CreateDropdown() => new DropdownControl { Items = Items }; + protected virtual OsuDropdown CreateDropdown() => new DropdownControl(); protected class DropdownControl : OsuDropdown { diff --git a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs index 2a98b80816..9f09f251c2 100644 --- a/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs +++ b/osu.Game/Overlays/Settings/SettingsEnumDropdown.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings { protected override OsuDropdown CreateDropdown() => new DropdownControl(); - protected class DropdownControl : OsuEnumDropdown + protected new class DropdownControl : OsuEnumDropdown { public DropdownControl() { diff --git a/osu.Game/Overlays/Settings/SettingsFooter.cs b/osu.Game/Overlays/Settings/SettingsFooter.cs index ac1548f00d..a815480094 100644 --- a/osu.Game/Overlays/Settings/SettingsFooter.cs +++ b/osu.Game/Overlays/Settings/SettingsFooter.cs @@ -1,17 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; -using DebugUtils = osu.Game.Utils.DebugUtils; namespace osu.Game.Overlays.Settings { @@ -56,18 +57,51 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = game.Name, - TextSize = 18, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold), }, - new OsuSpriteText + new BuildDisplay(game.Version, DebugUtils.IsDebugBuild) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - TextSize = 14, - Text = game.Version, - Colour = DebugUtils.IsDebug ? colours.Red : Color4.White, - }, + } }; } + + private class BuildDisplay : OsuAnimatedButton + { + private readonly string version; + private readonly bool isDebug; + + [Resolved] + private OsuColour colours { get; set; } + + public BuildDisplay(string version, bool isDebug) + { + this.version = version; + this.isDebug = isDebug; + + Content.RelativeSizeAxes = Axes.Y; + Content.AutoSizeAxes = AutoSizeAxes = Axes.X; + Height = 20; + } + + [BackgroundDependencyLoader(true)] + private void load(ChangelogOverlay changelog) + { + if (!isDebug) + Action = () => changelog?.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version); + + Add(new OsuSpriteText + { + Font = OsuFont.GetFont(size: 16), + + Text = version, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding(5), + Colour = isDebug ? colours.Red : Color4.White, + }); + } + } } } diff --git a/osu.Game/Overlays/Settings/SettingsHeader.cs b/osu.Game/Overlays/Settings/SettingsHeader.cs index fc86a38689..d8ec00bd99 100644 --- a/osu.Game/Overlays/Settings/SettingsHeader.cs +++ b/osu.Game/Overlays/Settings/SettingsHeader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -38,10 +38,10 @@ namespace osu.Game.Overlays.Settings new OsuSpriteText { Text = heading, - TextSize = 40, + Font = OsuFont.GetFont(size: 40), Margin = new MarginPadding { - Left = SettingsOverlay.CONTENT_MARGINS, + Left = SettingsPanel.CONTENT_MARGINS, Top = Toolbar.Toolbar.TOOLTIP_HEIGHT }, }, @@ -49,10 +49,10 @@ namespace osu.Game.Overlays.Settings { Colour = colours.Pink, Text = subheading, - TextSize = 18, + Font = OsuFont.GetFont(size: 18), Margin = new MarginPadding { - Left = SettingsOverlay.CONTENT_MARGINS, + Left = SettingsPanel.CONTENT_MARGINS, Bottom = 30 }, }, diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index 1d7e6350ae..ae840c8c00 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -1,14 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osuTK.Graphics; -using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -39,7 +40,7 @@ namespace osu.Game.Overlays.Settings public virtual string LabelText { - get { return text?.Text ?? string.Empty; } + get => text?.Text ?? string.Empty; set { if (text == null) @@ -58,12 +59,16 @@ namespace osu.Game.Overlays.Settings public virtual Bindable Bindable { - get { return bindable; } + get => bindable; set { + if (bindable != null) + controlWithCurrent?.Current.UnbindFrom(bindable); + bindable = value; controlWithCurrent?.Current.BindTo(bindable); + if (ShowsDefaultIndicator) { restoreDefaultButton.Bindable = bindable.GetBoundCopy(); @@ -72,22 +77,20 @@ namespace osu.Game.Overlays.Settings } } - public IEnumerable FilterTerms => new[] { LabelText }; + public virtual IEnumerable FilterTerms => new[] { LabelText }; public bool MatchingFilter { - set - { - // probably needs a better transition. - this.FadeTo(value ? 1 : 0); - } + set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsItem() { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Right = SettingsOverlay.CONTENT_MARGINS }; + Padding = new MarginPadding { Right = SettingsPanel.CONTENT_MARGINS }; InternalChildren = new Drawable[] { @@ -96,7 +99,7 @@ namespace osu.Game.Overlays.Settings { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS }, + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS }, Child = Control = CreateControl() }, }; @@ -115,12 +118,12 @@ namespace osu.Game.Overlays.Settings public Bindable Bindable { - get { return bindable; } + get => bindable; set { bindable = value; - bindable.ValueChanged += newValue => UpdateState(); - bindable.DisabledChanged += disabled => UpdateState(); + bindable.ValueChanged += _ => UpdateState(); + bindable.DisabledChanged += _ => UpdateState(); } } @@ -131,7 +134,7 @@ namespace osu.Game.Overlays.Settings public RestoreDefaultValueButton() { RelativeSizeAxes = Axes.Y; - Width = SettingsOverlay.CONTENT_MARGINS; + Width = SettingsPanel.CONTENT_MARGINS; Alpha = 0f; } diff --git a/osu.Game/Overlays/Settings/SettingsLabel.cs b/osu.Game/Overlays/Settings/SettingsLabel.cs deleted file mode 100644 index 2df4073191..0000000000 --- a/osu.Game/Overlays/Settings/SettingsLabel.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsLabel : SettingsItem - { - protected override Drawable CreateControl() => null; - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - Colour = colour.Gray6; - } - } -} diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs new file mode 100644 index 0000000000..cb7e63ae6f --- /dev/null +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Overlays.Settings +{ + public class SettingsNumberBox : SettingsItem + { + protected override Drawable CreateControl() => new OsuNumberBox + { + Margin = new MarginPadding { Top = 5 }, + RelativeSizeAxes = Axes.X, + }; + } +} diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index b7fe748bad..c878a9fc65 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Settings { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Settings protected FillFlowContainer FlowContent; protected override Container Content => FlowContent; - public abstract FontAwesome Icon { get; } + public abstract IconUsage Icon { get; } public abstract string Header { get; } public IEnumerable FilterableChildren => Children.OfType(); @@ -31,9 +32,11 @@ namespace osu.Game.Overlays.Settings public bool MatchingFilter { - set { this.FadeTo(value ? 1 : 0); } + set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsSection() { Margin = new MarginPadding { Top = 20 }; @@ -77,10 +80,10 @@ namespace osu.Game.Overlays.Settings { new OsuSpriteText { - TextSize = header_size, + Font = OsuFont.GetFont(size: header_size), Text = Header, Colour = colours.Yellow, - Margin = new MarginPadding { Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS } + Margin = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS } }, FlowContent } diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index a3698c36e6..fd96ea972a 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -1,8 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -23,14 +22,16 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.X }; - public float KeyboardStep; - - [BackgroundDependencyLoader] - private void load() + public bool TransferValueOnCommit { - var slider = Control as U; - if (slider != null) - slider.KeyboardStep = KeyboardStep; + get => ((U)Control).TransferValueOnCommit; + set => ((U)Control).TransferValueOnCommit = value; + } + + public float KeyboardStep + { + get => ((U)Control).KeyboardStep; + set => ((U)Control).KeyboardStep = value; } } } diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 94a660d1c8..c9c763e8d4 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -8,6 +8,7 @@ using osu.Game.Graphics.Sprites; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Game.Graphics; namespace osu.Game.Overlays.Settings { @@ -21,14 +22,14 @@ namespace osu.Game.Overlays.Settings public IEnumerable FilterableChildren => Children.OfType(); public IEnumerable FilterTerms => new[] { Header }; + public bool MatchingFilter { - set - { - this.FadeTo(value ? 1 : 0); - } + set => this.FadeTo(value ? 1 : 0); } + public bool FilteringActive { get; set; } + protected SettingsSubsection() { RelativeSizeAxes = Axes.X; @@ -52,8 +53,8 @@ namespace osu.Game.Overlays.Settings new OsuSpriteText { Text = Header.ToUpperInvariant(), - Margin = new MarginPadding { Bottom = 10, Left = SettingsOverlay.CONTENT_MARGINS, Right = SettingsOverlay.CONTENT_MARGINS }, - Font = @"Exo2.0-Black", + Margin = new MarginPadding { Bottom = 10, Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, + Font = OsuFont.GetFont(weight: FontWeight.Black), }, FlowContent }); diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index fbfb5481c5..0f257c2bfb 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Overlays/Settings/Sidebar.cs b/osu.Game/Overlays/Settings/Sidebar.cs index e43d4682b9..a80b7c1108 100644 --- a/osu.Game/Overlays/Settings/Sidebar.cs +++ b/osu.Game/Overlays/Settings/Sidebar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.Toolbar; namespace osu.Game.Overlays.Settings @@ -76,7 +77,7 @@ namespace osu.Game.Overlays.Settings return base.OnMouseMove(e); } - private class SidebarScrollContainer : ScrollContainer + private class SidebarScrollContainer : OsuScrollContainer { public SidebarScrollContainer() { @@ -88,7 +89,7 @@ namespace osu.Game.Overlays.Settings public ExpandedState State { - get { return state; } + get => state; set { expandEvent?.Cancel(); @@ -102,6 +103,7 @@ namespace osu.Game.Overlays.Settings default: this.ResizeTo(new Vector2(DEFAULT_WIDTH, Height), 500, Easing.OutQuint); break; + case ExpandedState.Expanded: this.ResizeTo(new Vector2(EXPANDED_WIDTH, Height), 500, Easing.OutQuint); break; diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index 1b23273c3c..a94f76e7af 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -26,12 +26,10 @@ namespace osu.Game.Overlays.Settings public new Action Action; private SettingsSection section; + public SettingsSection Section { - get - { - return section; - } + get => section; set { section = value; @@ -41,12 +39,14 @@ namespace osu.Game.Overlays.Settings } private bool selected; + public bool Selected { - get { return selected; } + get => selected; set { selected = value; + if (selected) { selectionIndicator.FadeIn(50); diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index df81421ea7..bb84de5d3a 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -1,224 +1,86 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using System.Linq; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; +using osuTK.Graphics; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; namespace osu.Game.Overlays { - public abstract class SettingsOverlay : OsuFocusedOverlayContainer + public class SettingsOverlay : SettingsPanel { - public const float CONTENT_MARGINS = 15; - - public const float TRANSITION_LENGTH = 600; - - private const float sidebar_width = Sidebar.DEFAULT_WIDTH; - - protected const float WIDTH = 400; - - private const float sidebar_padding = 10; - - protected Container ContentContainer; - - protected override Container Content => ContentContainer; - - protected Sidebar Sidebar; - private SidebarButton selectedSidebarButton; - - protected SettingsSectionsContainer SectionsContainer; - - private SearchTextBox searchTextBox; - - /// - /// Provide a source for the toolbar height. - /// - public Func GetToolbarHeight; - - private readonly bool showSidebar; - - protected Box Background; - - protected SettingsOverlay(bool showSidebar) + protected override IEnumerable CreateSections() => new SettingsSection[] + { + new GeneralSection(), + new GraphicsSection(), + new GameplaySection(), + new AudioSection(), + new SkinSection(), + new InputSection(createSubPanel(new KeyBindingPanel())), + new OnlineSection(), + new MaintenanceSection(), + new DebugSection(), + }; + + private readonly List subPanels = new List(); + + protected override Drawable CreateHeader() => new SettingsHeader("settings", "Change the way osu! behaves"); + protected override Drawable CreateFooter() => new SettingsFooter(); + + public SettingsOverlay() + : base(true) { - this.showSidebar = showSidebar; - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; } - protected virtual IEnumerable CreateSections() => null; + public override bool AcceptsFocus => subPanels.All(s => s.State.Value != Visibility.Visible); + + private T createSubPanel(T subPanel) + where T : SettingsSubPanel + { + subPanel.Depth = 1; + subPanel.Anchor = Anchor.TopRight; + subPanel.State.ValueChanged += subPanelStateChanged; + + subPanels.Add(subPanel); + + return subPanel; + } + + private void subPanelStateChanged(ValueChangedEvent state) + { + switch (state.NewValue) + { + case Visibility.Visible: + Background.FadeTo(0.9f, 300, Easing.OutQuint); + Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); + + SectionsContainer.FadeOut(300, Easing.OutQuint); + ContentContainer.MoveToX(-WIDTH, 500, Easing.OutQuint); + break; + + case Visibility.Hidden: + Background.FadeTo(0.6f, 500, Easing.OutQuint); + Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); + + SectionsContainer.FadeIn(500, Easing.OutQuint); + ContentContainer.MoveToX(0, 500, Easing.OutQuint); + break; + } + } + + protected override float ExpandedPosition => subPanels.Any(s => s.State.Value == Visibility.Visible) ? -WIDTH : base.ExpandedPosition; [BackgroundDependencyLoader] private void load() { - InternalChild = ContentContainer = new Container - { - Width = WIDTH, - RelativeSizeAxes = Axes.Y, - Children = new Drawable[] - { - Background = new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Scale = new Vector2(2, 1), // over-extend to the left for transitions - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, - SectionsContainer = new SettingsSectionsContainer - { - Masking = true, - RelativeSizeAxes = Axes.Both, - ExpandableHeader = CreateHeader(), - FixedHeader = searchTextBox = new SearchTextBox - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Width = 0.95f, - Margin = new MarginPadding - { - Top = 20, - Bottom = 20 - }, - Exit = Hide, - }, - Footer = CreateFooter() - }, - } - }; - - if (showSidebar) - { - AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); - - SectionsContainer.SelectedSection.ValueChanged += section => - { - selectedSidebarButton.Selected = false; - selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section); - selectedSidebarButton.Selected = true; - }; - } - - searchTextBox.Current.ValueChanged += newValue => SectionsContainer.SearchContainer.SearchTerm = newValue; - - CreateSections()?.ForEach(AddSection); - } - - protected void AddSection(SettingsSection section) - { - SectionsContainer.Add(section); - - if (Sidebar != null) - { - var button = new SidebarButton - { - Section = section, - Action = s => - { - SectionsContainer.ScrollTo(s); - Sidebar.State = ExpandedState.Contracted; - }, - }; - - Sidebar.Add(button); - - if (selectedSidebarButton == null) - { - selectedSidebarButton = Sidebar.Children.First(); - selectedSidebarButton.Selected = true; - } - } - } - - protected virtual Drawable CreateHeader() => new Container(); - - protected virtual Drawable CreateFooter() => new Container(); - - protected override void PopIn() - { - base.PopIn(); - - ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); - - Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); - - searchTextBox.HoldFocus = true; - } - - protected virtual float ExpandedPosition => 0; - - protected override void PopOut() - { - base.PopOut(); - - ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - - Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); - - searchTextBox.HoldFocus = false; - if (searchTextBox.HasFocus) - GetContainingInputManager().ChangeFocus(null); - } - - public override bool AcceptsFocus => true; - - protected override void OnFocus(FocusEvent e) - { - GetContainingInputManager().ChangeFocus(searchTextBox); - base.OnFocus(e); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; - ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; - } - - protected class SettingsSectionsContainer : SectionsContainer - { - public SearchContainer SearchContainer; - - protected override FlowContainer CreateScrollContentContainer() - => SearchContainer = new SearchContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - }; - - public SettingsSectionsContainer() - { - HeaderBackground = new Box - { - Colour = Color4.Black, - RelativeSizeAxes = Axes.Both - }; - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - // no null check because the usage of this class is strict - HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f; - } + foreach (var s in subPanels) + ContentContainer.Add(s); } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs new file mode 100644 index 0000000000..474f529bb1 --- /dev/null +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -0,0 +1,222 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osuTK; +using osuTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Settings; + +namespace osu.Game.Overlays +{ + public abstract class SettingsPanel : OsuFocusedOverlayContainer + { + public const float CONTENT_MARGINS = 15; + + public const float TRANSITION_LENGTH = 600; + + private const float sidebar_width = Sidebar.DEFAULT_WIDTH; + + protected const float WIDTH = 400; + + protected Container ContentContainer; + + protected override Container Content => ContentContainer; + + protected Sidebar Sidebar; + private SidebarButton selectedSidebarButton; + + protected SettingsSectionsContainer SectionsContainer; + + private SearchTextBox searchTextBox; + + /// + /// Provide a source for the toolbar height. + /// + public Func GetToolbarHeight; + + private readonly bool showSidebar; + + protected Box Background; + + protected SettingsPanel(bool showSidebar) + { + this.showSidebar = showSidebar; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + } + + protected virtual IEnumerable CreateSections() => null; + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = ContentContainer = new Container + { + Width = WIDTH, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + Background = new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Scale = new Vector2(2, 1), // over-extend to the left for transitions + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, + }, + SectionsContainer = new SettingsSectionsContainer + { + Masking = true, + RelativeSizeAxes = Axes.Both, + ExpandableHeader = CreateHeader(), + FixedHeader = searchTextBox = new SearchTextBox + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Width = 0.95f, + Margin = new MarginPadding + { + Top = 20, + Bottom = 20 + }, + Exit = Hide, + }, + Footer = CreateFooter() + }, + } + }; + + if (showSidebar) + { + AddInternal(Sidebar = new Sidebar { Width = sidebar_width }); + + SectionsContainer.SelectedSection.ValueChanged += section => + { + selectedSidebarButton.Selected = false; + selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section.NewValue); + selectedSidebarButton.Selected = true; + }; + } + + searchTextBox.Current.ValueChanged += term => SectionsContainer.SearchContainer.SearchTerm = term.NewValue; + + CreateSections()?.ForEach(AddSection); + } + + protected void AddSection(SettingsSection section) + { + SectionsContainer.Add(section); + + if (Sidebar != null) + { + var button = new SidebarButton + { + Section = section, + Action = s => + { + SectionsContainer.ScrollTo(s); + Sidebar.State = ExpandedState.Contracted; + }, + }; + + Sidebar.Add(button); + + if (selectedSidebarButton == null) + { + selectedSidebarButton = Sidebar.Children.First(); + selectedSidebarButton.Selected = true; + } + } + } + + protected virtual Drawable CreateHeader() => new Container(); + + protected virtual Drawable CreateFooter() => new Container(); + + protected override void PopIn() + { + base.PopIn(); + + ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + + searchTextBox.HoldFocus = true; + } + + protected virtual float ExpandedPosition => 0; + + protected override void PopOut() + { + base.PopOut(); + + ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint); + + Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + + searchTextBox.HoldFocus = false; + if (searchTextBox.HasFocus) + GetContainingInputManager().ChangeFocus(null); + } + + public override bool AcceptsFocus => true; + + protected override void OnFocus(FocusEvent e) + { + searchTextBox.TakeFocus(); + base.OnFocus(e); + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 }; + ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 }; + } + + protected class SettingsSectionsContainer : SectionsContainer + { + public SearchContainer SearchContainer; + + protected override FlowContainer CreateScrollContentContainer() + => SearchContainer = new SearchContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + }; + + public SettingsSectionsContainer() + { + HeaderBackground = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both + }; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + // no null check because the usage of this class is strict + HeaderBackground.Alpha = -ExpandableHeader.Y / ExpandableHeader.LayoutSize.Y * 0.5f; + } + } + } +} diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs new file mode 100644 index 0000000000..7f794e2927 --- /dev/null +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; +using osu.Game.Overlays.Settings; +using osu.Game.Screens.Ranking; +using osuTK; + +namespace osu.Game.Overlays +{ + public abstract class SettingsSubPanel : SettingsPanel + { + protected SettingsSubPanel() + : base(true) + { + } + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Action = Hide + }); + } + + protected override bool DimMainContent => false; // dimming is handled by main overlay + + private class BackButton : OsuClickableContainer, IKeyBindingHandler + { + private AspectContainer aspect; + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(Sidebar.DEFAULT_WIDTH); + Children = new Drawable[] + { + aspect = new AspectContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = -15, + Size = new Vector2(15), + Shadow = true, + Icon = FontAwesome.Solid.ChevronLeft + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Y = 15, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = @"back", + }, + } + } + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + aspect.ScaleTo(0.75f, 2000, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + aspect.ScaleTo(1, 1000, Easing.OutElastic); + return base.OnMouseUp(e); + } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + Click(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => false; + } + } +} diff --git a/osu.Game/Overlays/Social/FilterControl.cs b/osu.Game/Overlays/Social/FilterControl.cs index 72c3791f6f..6abf6ec98d 100644 --- a/osu.Game/Overlays/Social/FilterControl.cs +++ b/osu.Game/Overlays/Social/FilterControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Social/Header.cs b/osu.Game/Overlays/Social/Header.cs index a23a96cdf4..22bca9b421 100644 --- a/osu.Game/Overlays/Social/Header.cs +++ b/osu.Game/Overlays/Social/Header.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Overlays.SearchableList; using osuTK.Graphics; @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Framework.Allocation; using System.ComponentModel; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Social { @@ -19,7 +20,7 @@ namespace osu.Game.Overlays.Social protected override Color4 BackgroundColour => OsuColour.FromHex(@"38202e"); protected override SocialTab DefaultTab => SocialTab.AllPlayers; - protected override FontAwesome Icon => FontAwesome.fa_users; + protected override IconUsage Icon => FontAwesome.Solid.Users; protected override Drawable CreateHeaderText() { @@ -32,13 +33,12 @@ namespace osu.Game.Overlays.Social new OsuSpriteText { Text = "social ", - TextSize = 25, + Font = OsuFont.GetFont(size: 25), }, browser = new OsuSpriteText { Text = "browser", - TextSize = 25, - Font = @"Exo2.0-Light", + Font = OsuFont.GetFont(size: 25, weight: FontWeight.Light), }, }, }; @@ -55,6 +55,7 @@ namespace osu.Game.Overlays.Social { [Description("All Players")] AllPlayers, + [Description("Friends")] Friends, //[Description("Team Members")] diff --git a/osu.Game/Overlays/Social/SocialGridPanel.cs b/osu.Game/Overlays/Social/SocialGridPanel.cs index 0051e1e0e5..6f707d640b 100644 --- a/osu.Game/Overlays/Social/SocialGridPanel.cs +++ b/osu.Game/Overlays/Social/SocialGridPanel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Users; @@ -7,7 +7,8 @@ namespace osu.Game.Overlays.Social { public class SocialGridPanel : SocialPanel { - public SocialGridPanel(User user) : base(user) + public SocialGridPanel(User user) + : base(user) { Width = 300; } diff --git a/osu.Game/Overlays/Social/SocialListPanel.cs b/osu.Game/Overlays/Social/SocialListPanel.cs index ff39e3b236..1ba91e9204 100644 --- a/osu.Game/Overlays/Social/SocialListPanel.cs +++ b/osu.Game/Overlays/Social/SocialListPanel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Users; @@ -8,7 +8,8 @@ namespace osu.Game.Overlays.Social { public class SocialListPanel : SocialPanel { - public SocialListPanel(User user) : base(user) + public SocialListPanel(User user) + : base(user) { RelativeSizeAxes = Axes.X; } diff --git a/osu.Game/Overlays/Social/SocialPanel.cs b/osu.Game/Overlays/Social/SocialPanel.cs index 4dfeda9041..555527670a 100644 --- a/osu.Game/Overlays/Social/SocialPanel.cs +++ b/osu.Game/Overlays/Social/SocialPanel.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Game.Users; @@ -15,7 +15,8 @@ namespace osu.Game.Overlays.Social { private const double hover_transition_time = 400; - public SocialPanel(User user) : base(user) + public SocialPanel(User user) + : base(user) { } diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index bd6cd3cba6..780a80b4fc 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; +using osu.Framework.Bindables; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics; @@ -15,14 +15,12 @@ using osu.Game.Online.API.Requests; using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.Social; using osu.Game.Users; -using osu.Framework.Configuration; using osu.Framework.Threading; namespace osu.Game.Overlays { - public class SocialOverlay : SearchableListOverlay, IOnlineComponent + public class SocialOverlay : SearchableListOverlay { - private APIAccess api; private readonly LoadingAnimation loading; private FillFlowContainer panels; @@ -34,9 +32,10 @@ namespace osu.Game.Overlays protected override SearchableListFilterControl CreateFilterControl() => new FilterControl(); private IEnumerable users; + public IEnumerable Users { - get { return users; } + get => users; set { if (users?.Equals(value) ?? false) @@ -57,7 +56,7 @@ namespace osu.Game.Overlays Filter.Search.Current.ValueChanged += text => { - if (!string.IsNullOrEmpty(text)) + if (!string.IsNullOrEmpty(text.NewValue)) { // force searching in players until searching for friends is supported Header.Tabs.Current.Value = SocialTab.AllPlayers; @@ -67,18 +66,18 @@ namespace osu.Game.Overlays } }; - Header.Tabs.Current.ValueChanged += tab => Scheduler.AddOnce(updateSearch); + Header.Tabs.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); - Filter.Tabs.Current.ValueChanged += sortCriteria => Scheduler.AddOnce(updateSearch); + Filter.Tabs.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); - Filter.DisplayStyleControl.DisplayStyle.ValueChanged += recreatePanels; - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += sortOrder => Scheduler.AddOnce(updateSearch); + Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue); + Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => Scheduler.AddOnce(updateSearch); currentQuery.ValueChanged += query => { queryChangedDebounce?.Cancel(); - if (string.IsNullOrEmpty(query)) + if (string.IsNullOrEmpty(query.NewValue)) Scheduler.AddOnce(updateSearch); else queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500); @@ -87,13 +86,6 @@ namespace osu.Game.Overlays currentQuery.BindTo(Filter.Search.Current); } - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - api.Register(this); - } - private void recreatePanels(PanelDisplayStyle displayStyle) { clearPanels(); @@ -110,6 +102,7 @@ namespace osu.Game.Overlays ChildrenEnumerable = Users.Select(u => { SocialPanel panel; + switch (displayStyle) { case PanelDisplayStyle.Grid: @@ -119,18 +112,21 @@ namespace osu.Game.Overlays Origin = Anchor.TopCentre }; break; + default: panel = new SocialListPanel(u); break; } + panel.Status.BindTo(u.Status); + panel.Activity.BindTo(u.Activity); return panel; }) }; LoadComponentAsync(newPanels, f => { - if(panels != null) + if (panels != null) ScrollFlow.Remove(panels); ScrollFlow.Add(panels = newPanels); @@ -155,7 +151,7 @@ namespace osu.Game.Overlays loading.Hide(); getUsersRequest?.Cancel(); - if (api?.IsLoggedIn != true) + if (API?.IsLoggedIn != true) return; switch (Header.Tabs.Current.Value) @@ -163,14 +159,16 @@ namespace osu.Game.Overlays case SocialTab.Friends: var friendRequest = new GetFriendsRequest(); // TODO filter arguments? friendRequest.Success += updateUsers; - api.Queue(getUsersRequest = friendRequest); + API.Queue(getUsersRequest = friendRequest); break; + default: var userRequest = new GetUsersRequest(); // TODO filter arguments! userRequest.Success += response => updateUsers(response.Select(r => r.User)); - api.Queue(getUsersRequest = userRequest); + API.Queue(getUsersRequest = userRequest); break; } + loading.Show(); } @@ -190,13 +188,14 @@ namespace osu.Game.Overlays } } - public void APIStateChanged(APIAccess api, APIState state) + public override void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { case APIState.Online: Scheduler.AddOnce(updateSearch); break; + default: Users = null; clearPanels(); diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 341f5c3fd1..19038c3981 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Extensions.Color4Extensions; @@ -10,8 +10,9 @@ using osu.Game.Graphics; using osuTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Input.Events; +using osu.Game.Rulesets; namespace osu.Game.Overlays.Toolbar { @@ -22,7 +23,8 @@ namespace osu.Game.Overlays.Toolbar public Action OnHome; - private readonly ToolbarUserArea userArea; + private ToolbarUserButton userButton; + private ToolbarRulesetSelector rulesetSelector; protected override bool BlockPositionalInput => false; @@ -34,6 +36,13 @@ namespace osu.Game.Overlays.Toolbar private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); public Toolbar() + { + RelativeSizeAxes = Axes.X; + Size = new Vector2(1, HEIGHT); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuGame osuGame, Bindable parentRuleset) { Children = new Drawable[] { @@ -50,7 +59,7 @@ namespace osu.Game.Overlays.Toolbar { Action = () => OnHome?.Invoke() }, - new ToolbarRulesetSelector() + rulesetSelector = new ToolbarRulesetSelector() } }, new FillFlowContainer @@ -62,31 +71,28 @@ namespace osu.Game.Overlays.Toolbar AutoSizeAxes = Axes.X, Children = new Drawable[] { + new ToolbarChangelogButton(), new ToolbarDirectButton(), new ToolbarChatButton(), new ToolbarSocialButton(), new ToolbarMusicButton(), //new ToolbarButton //{ - // Icon = FontAwesome.fa_search + // Icon = FontAwesome.Solid.search //}, - userArea = new ToolbarUserArea(), + userButton = new ToolbarUserButton(), new ToolbarNotificationButton(), } } }; - RelativeSizeAxes = Axes.X; - Size = new Vector2(1, HEIGHT); - } + // Bound after the selector is added to the hierarchy to give it a chance to load the available rulesets + rulesetSelector.Current.BindTo(parentRuleset); - [BackgroundDependencyLoader(true)] - private void load(OsuGame osuGame) - { - StateChanged += visibility => + State.ValueChanged += visibility => { - if (overlayActivationMode == OverlayActivation.Disabled) - State = Visibility.Hidden; + if (overlayActivationMode.Value == OverlayActivation.Disabled) + Hide(); }; if (osuGame != null) @@ -143,7 +149,7 @@ namespace osu.Game.Overlays.Toolbar protected override void PopOut() { - userArea?.LoginOverlay.Hide(); + userButton?.StateContainer.Hide(); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.FadeOut(transition_time); diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index a9d8be6ff2..2b2b19b73a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; @@ -27,42 +28,33 @@ namespace osu.Game.Overlays.Toolbar IconContainer.Show(); } - public void SetIcon(FontAwesome icon) => SetIcon(new SpriteIcon + public void SetIcon(IconUsage icon) => SetIcon(new SpriteIcon { Size = new Vector2(20), Icon = icon }); - public FontAwesome Icon + public IconUsage Icon { - set { SetIcon(value); } + set => SetIcon(value); } public string Text { - get { return DrawableText.Text; } - set - { - DrawableText.Text = value; - } + get => DrawableText.Text; + set => DrawableText.Text = value; } public string TooltipMain { - get { return tooltip1.Text; } - set - { - tooltip1.Text = value; - } + get => tooltip1.Text; + set => tooltip1.Text = value; } public string TooltipSub { - get { return tooltip2.Text; } - set - { - tooltip2.Text = value; - } + get => tooltip2.Text; + set => tooltip2.Text = value; } protected virtual Anchor TooltipAnchor => Anchor.TopLeft; @@ -75,7 +67,8 @@ namespace osu.Game.Overlays.Toolbar private readonly SpriteText tooltip2; protected FillFlowContainer Flow; - public ToolbarButton() : base(HoverSampleSet.Loud) + public ToolbarButton() + : base(HoverSampleSet.Loud) { Width = WIDTH; RelativeSizeAxes = Axes.Y; @@ -129,15 +122,13 @@ namespace osu.Game.Overlays.Toolbar Anchor = TooltipAnchor, Origin = TooltipAnchor, Shadow = true, - TextSize = 22, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 22, weight: FontWeight.Bold), }, tooltip2 = new OsuSpriteText { Anchor = TooltipAnchor, Origin = TooltipAnchor, Shadow = true, - TextSize = 16 } } } @@ -149,6 +140,7 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnClick(ClickEvent e) { HoverBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); + tooltipContainer.FadeOut(100); return base.OnClick(e); } diff --git a/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs new file mode 100644 index 0000000000..84210e27a4 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarChangelogButton.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarChangelogButton : ToolbarOverlayToggleButton + { + public ToolbarChangelogButton() + { + SetIcon(FontAwesome.Solid.Bullhorn); + } + + [BackgroundDependencyLoader(true)] + private void load(ChangelogOverlay changelog) + { + StateContainer = changelog; + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs index 3d9b943d16..ad0e5be551 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarChatButton.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarChatButton() { - SetIcon(FontAwesome.fa_comments); + SetIcon(FontAwesome.Solid.Comments); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs index ffc5ecd682..1d07a3ae70 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarDirectButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarDirectButton() { - SetIcon(FontAwesome.fa_osu_chevron_down_o); + SetIcon(OsuIcon.ChevronDownCircle); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index e79b73e725..6f5e703a66 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -9,7 +9,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarHomeButton() { - Icon = FontAwesome.fa_home; + Icon = FontAwesome.Solid.Home; TooltipMain = "Home"; TooltipSub = "Return to the main menu"; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs index 94a7e69e3f..f03df2ed93 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarMusicButton.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarMusicButton() { - Icon = FontAwesome.fa_music; + Icon = FontAwesome.Solid.Music; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs index 42d9527432..dbd6c557d3 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarNotificationButton.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -23,7 +24,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarNotificationButton() { - Icon = FontAwesome.fa_bars; + Icon = FontAwesome.Solid.Bars; TooltipMain = "Notifications"; TooltipSub = "Waiting for 'ya"; @@ -47,11 +48,11 @@ namespace osu.Game.Overlays.Toolbar NotificationCount.ValueChanged += count => { - if (count == 0) + if (count.NewValue == 0) countDisplay.FadeOut(200, Easing.OutQuint); else { - countDisplay.Count = count; + countDisplay.Count = count.NewValue; countDisplay.FadeIn(200, Easing.OutQuint); } }; @@ -66,7 +67,7 @@ namespace osu.Game.Overlays.Toolbar public int Count { - get { return count; } + get => count; set { if (count == value) @@ -99,11 +100,10 @@ namespace osu.Game.Overlays.Toolbar Anchor = Anchor.Centre, Origin = Anchor.Centre, Y = -1, - TextSize = 14, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), Padding = new MarginPadding(5), Colour = Color4.White, UseFullGlyphHeight = true, - Font = "Exo2.0-Bold", } }; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 0c8483dec3..b286cbfb1d 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,16 +16,21 @@ namespace osu.Game.Overlays.Toolbar private OverlayContainer stateContainer; + private readonly Bindable overlayState = new Bindable(); + public OverlayContainer StateContainer { - get { return stateContainer; } + get => stateContainer; set { stateContainer = value; + + overlayState.UnbindBindings(); + if (stateContainer != null) { Action = stateContainer.ToggleVisibility; - stateContainer.StateChanged += stateChanged; + overlayState.BindTo(stateContainer.State); } } } @@ -39,22 +45,18 @@ namespace osu.Game.Overlays.Toolbar Depth = 2, Alpha = 0, }); + + overlayState.ValueChanged += stateChanged; } - protected override void Dispose(bool isDisposing) + private void stateChanged(ValueChangedEvent state) { - base.Dispose(isDisposing); - if (stateContainer != null) - stateContainer.StateChanged -= stateChanged; - } - - private void stateChanged(Visibility state) - { - switch (state) + switch (state.NewValue) { case Visibility.Hidden: stateBackground.FadeOut(200); break; + case Visibility.Visible: stateBackground.FadeIn(200); break; diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs deleted file mode 100644 index b0801d95b0..0000000000 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetButton.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets; -using osuTK.Graphics; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarRulesetButton : ToolbarButton - { - private RulesetInfo ruleset; - public RulesetInfo Ruleset - { - get { return ruleset; } - set - { - ruleset = value; - - var rInstance = ruleset.CreateInstance(); - - TooltipMain = rInstance.Description; - TooltipSub = $"Play some {rInstance.Description}"; - SetIcon(rInstance.CreateIcon()); - } - } - - public bool Active - { - set - { - if (value) - { - IconContainer.Colour = Color4.White; - IconContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }; - } - else - { - IconContainer.Colour = new Color4(255, 194, 224, 255); - IconContainer.EdgeEffect = new EdgeEffectParameters(); - } - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - IconContainer.Scale *= 1.4f; - } - } -} diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 19777bfbeb..f4272ab15c 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -1,49 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osuTK; -using osuTK.Input; using osuTK.Graphics; -using osu.Framework.Configuration; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Rulesets; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osuTK.Input; +using System.Linq; +using osu.Framework.Allocation; namespace osu.Game.Overlays.Toolbar { - public class ToolbarRulesetSelector : Container + public class ToolbarRulesetSelector : RulesetSelector { private const float padding = 10; - private readonly FillFlowContainer modeButtons; - private readonly Drawable modeButtonLine; - private ToolbarRulesetButton activeButton; - - private RulesetStore rulesets; - private readonly Bindable ruleset = new Bindable(); + protected Drawable ModeButtonLine { get; private set; } public ToolbarRulesetSelector() { RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + } - Children = new[] + [BackgroundDependencyLoader] + private void load() + { + AddRangeInternal(new[] { - new OpaqueBackground(), - modeButtons = new FillFlowContainer + new OpaqueBackground { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Direction = FillDirection.Horizontal, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Padding = new MarginPadding { Left = padding, Right = padding }, + Depth = 1, }, - modeButtonLine = new Container + ModeButtonLine = new Container { Size = new Vector2(padding * 2 + ToolbarButton.WIDTH, 3), Anchor = Anchor.BottomLeft, @@ -56,34 +50,54 @@ namespace osu.Game.Overlays.Toolbar Radius = 15, Roundness = 15, }, - Children = new[] + Child = new Box { - new Box - { - RelativeSizeAxes = Axes.Both, - } + RelativeSizeAxes = Axes.Both, } } - }; + }); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets, Bindable parentRuleset) + protected override void LoadComplete() { - this.rulesets = rulesets; - foreach (var r in rulesets.AvailableRulesets) + base.LoadComplete(); + + Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true); + Current.BindValueChanged(_ => moveLineToCurrent(), true); + } + + private bool hasInitialPosition; + + // Scheduled to allow the flow layout to be computed before the line position is updated + private void moveLineToCurrent() => ScheduleAfterChildren(() => + { + foreach (var tabItem in TabContainer) { - modeButtons.Add(new ToolbarRulesetButton + if (tabItem.Value == Current.Value) { - Ruleset = r, - Action = delegate { ruleset.Value = r; } - }); + ModeButtonLine.MoveToX(tabItem.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); + break; + } } - ruleset.ValueChanged += rulesetChanged; - ruleset.DisabledChanged += disabledChanged; - ruleset.BindTo(parentRuleset); - } + hasInitialPosition = true; + }); + + public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput; + + public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput; + + public override bool PropagatePositionalInputSubTree => !Current.Disabled && base.PropagatePositionalInputSubTree; + + protected override TabItem CreateTabItem(RulesetInfo value) => new ToolbarRulesetTabButton(value); + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding { Left = padding, Right = padding }, + }; protected override bool OnKeyDown(KeyDownEvent e) { @@ -93,52 +107,13 @@ namespace osu.Game.Overlays.Toolbar { int requested = e.Key - Key.Number1; - RulesetInfo found = rulesets.AvailableRulesets.Skip(requested).FirstOrDefault(); + RulesetInfo found = Rulesets.AvailableRulesets.Skip(requested).FirstOrDefault(); if (found != null) - ruleset.Value = found; + Current.Value = found; return true; } return false; } - - public override bool HandleNonPositionalInput => !ruleset.Disabled && base.HandleNonPositionalInput; - public override bool HandlePositionalInput => !ruleset.Disabled && base.HandlePositionalInput; - - public override bool PropagatePositionalInputSubTree => !ruleset.Disabled && base.PropagatePositionalInputSubTree; - - private void disabledChanged(bool isDisabled) => this.FadeColour(isDisabled ? Color4.Gray : Color4.White, 300); - - protected override void Update() - { - base.Update(); - Size = new Vector2(modeButtons.DrawSize.X, 1); - } - - private void rulesetChanged(RulesetInfo ruleset) - { - foreach (ToolbarRulesetButton m in modeButtons.Children.Cast()) - { - bool isActive = m.Ruleset.ID == ruleset.ID; - m.Active = isActive; - if (isActive) - activeButton = m; - } - - activeMode.Invalidate(); - } - - private Cached activeMode = new Cached(); - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (!activeMode.IsValid) - { - modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, Easing.OutQuint); - activeMode.Validate(); - } - } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs new file mode 100644 index 0000000000..a5194ea752 --- /dev/null +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; + +namespace osu.Game.Overlays.Toolbar +{ + public class ToolbarRulesetTabButton : TabItem + { + private readonly RulesetButton ruleset; + + public ToolbarRulesetTabButton(RulesetInfo value) + : base(value) + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + Child = ruleset = new RulesetButton + { + Active = false, + }; + + var rInstance = value.CreateInstance(); + + ruleset.TooltipMain = rInstance.Description; + ruleset.TooltipSub = $"Play some {rInstance.Description}"; + ruleset.SetIcon(rInstance.CreateIcon()); + } + + protected override void OnActivated() => ruleset.Active = true; + + protected override void OnDeactivated() => ruleset.Active = false; + + private class RulesetButton : ToolbarButton + { + public bool Active + { + set + { + if (value) + { + IconContainer.Colour = Color4.White; + IconContainer.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = new Color4(255, 194, 224, 100), + Radius = 15, + Roundness = 15, + }; + } + else + { + IconContainer.Colour = new Color4(255, 194, 224, 255); + IconContainer.EdgeEffect = new EdgeEffectParameters(); + } + } + } + + protected override bool OnClick(ClickEvent e) + { + Parent.Click(); + return base.OnClick(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + IconContainer.Scale *= 1.4f; + } + } + } +} diff --git a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs index 5c3eaf7840..79942012f9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSettingsButton.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,13 +10,13 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSettingsButton() { - Icon = FontAwesome.fa_gear; + Icon = FontAwesome.Solid.Cog; TooltipMain = "Settings"; TooltipSub = "Change your settings"; } [BackgroundDependencyLoader(true)] - private void load(MainSettings settings) + private void load(SettingsOverlay settings) { StateContainer = settings; } diff --git a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs index f45950cb4c..5e353d3319 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarSocialButton.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Toolbar { @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Toolbar { public ToolbarSocialButton() { - Icon = FontAwesome.fa_users; + Icon = FontAwesome.Solid.Users; } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs deleted file mode 100644 index d0cb3c0ff2..0000000000 --- a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs +++ /dev/null @@ -1,41 +0,0 @@ -// 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 osuTK; -using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; - -namespace osu.Game.Overlays.Toolbar -{ - public class ToolbarUserArea : Container - { - public LoginOverlay LoginOverlay; - private ToolbarUserButton button; - - public override RectangleF BoundingBox => button.BoundingBox; - - [BackgroundDependencyLoader] - private void load() - { - RelativeSizeAxes = Axes.Y; - AutoSizeAxes = Axes.X; - - Children = new Drawable[] { - button = new ToolbarUserButton - { - Action = () => LoginOverlay.ToggleVisibility(), - }, - LoginOverlay = new LoginOverlay - { - BypassAutoSizeAxes = Axes.Both, - Position = new Vector2(0, 1), - RelativePositionAxes = Axes.Y, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - } - }; - } - } -} diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 017d748600..bccef3d9fe 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -1,18 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Game.Graphics; using osu.Game.Online.API; using osu.Game.Users; +using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { - public class ToolbarUserButton : ToolbarButton, IOnlineComponent + public class ToolbarUserButton : ToolbarOverlayToggleButton, IOnlineComponent { private readonly UpdateableAvatar avatar; @@ -20,7 +22,7 @@ namespace osu.Game.Overlays.Toolbar { AutoSizeAxes = Axes.X; - DrawableText.Font = @"Exo2.0-MediumItalic"; + DrawableText.Font = OsuFont.GetFont(italics: true); Add(new OpaqueBackground { Depth = 1 }); @@ -31,6 +33,7 @@ namespace osu.Game.Overlays.Toolbar Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, CornerRadius = 4, + OpenOnClick = { Value = false }, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -40,13 +43,15 @@ namespace osu.Game.Overlays.Toolbar }); } - [BackgroundDependencyLoader] - private void load(APIAccess api) + [BackgroundDependencyLoader(true)] + private void load(IAPIProvider api, LoginOverlay login) { api.Register(this); + + StateContainer = login; } - public void APIStateChanged(APIAccess api, APIState state) + public void APIStateChanged(IAPIProvider api, APIState state) { switch (state) { @@ -54,9 +59,10 @@ namespace osu.Game.Overlays.Toolbar Text = @"Guest"; avatar.User = new User(); break; + case APIState.Online: Text = api.LocalUser.Value.Username; - avatar.User = api.LocalUser; + avatar.User = api.LocalUser.Value; break; } } diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index c15f464c7c..b924b3302f 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -1,87 +1,43 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile; using osu.Game.Overlays.Profile.Sections; using osu.Game.Users; using osuTK; -using osuTK.Graphics; namespace osu.Game.Overlays { - public class UserProfileOverlay : WaveOverlayContainer + public class UserProfileOverlay : FullscreenOverlay { private ProfileSection lastSection; private ProfileSection[] sections; private GetUserRequest userReq; - private APIAccess api; protected ProfileHeader Header; - private SectionsContainer sectionsContainer; + private ProfileSectionsContainer sectionsContainer; private ProfileTabControl tabs; - public const float CONTENT_X_MARGIN = 50; - - public UserProfileOverlay() - { - Waves.FirstWaveColour = OsuColour.Gray(0.4f); - Waves.SecondWaveColour = OsuColour.Gray(0.3f); - Waves.ThirdWaveColour = OsuColour.Gray(0.2f); - Waves.FourthWaveColour = OsuColour.Gray(0.1f); - - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; - Width = 0.85f; - Anchor = Anchor.TopCentre; - Origin = Anchor.TopCentre; - - Masking = true; - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0), - Type = EdgeEffectType.Shadow, - Radius = 10 - }; - } - - [BackgroundDependencyLoader] - private void load(APIAccess api) - { - this.api = api; - } - - protected override void PopIn() - { - base.PopIn(); - FadeEdgeEffectTo(0.5f, WaveContainer.APPEAR_DURATION, Easing.In); - } - - protected override void PopOut() - { - base.PopOut(); - FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.Out); - } + public const float CONTENT_X_MARGIN = 70; public void ShowUser(long userId) => ShowUser(new User { Id = userId }); public void ShowUser(User user, bool fetchOnline = true) { - if (user == User.SYSTEM_USER) return; + if (user == User.SYSTEM_USER) + return; Show(); - if (user.Id == Header?.User?.Id) + if (user.Id == Header?.User.Value?.Id) return; userReq?.Cancel(); @@ -110,32 +66,29 @@ namespace osu.Game.Overlays Add(new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(0.2f) + Colour = OsuColour.Gray(0.1f) }); - Header = new ProfileHeader(user); - - Add(sectionsContainer = new SectionsContainer + Add(sectionsContainer = new ProfileSectionsContainer { - RelativeSizeAxes = Axes.Both, - ExpandableHeader = Header, + ExpandableHeader = Header = new ProfileHeader(), FixedHeader = tabs, HeaderBackground = new Box { Colour = OsuColour.Gray(34), RelativeSizeAxes = Axes.Both - } + }, }); - sectionsContainer.SelectedSection.ValueChanged += s => + sectionsContainer.SelectedSection.ValueChanged += section => { - if (lastSection != s) + if (lastSection != section.NewValue) { - lastSection = s; + lastSection = section.NewValue; tabs.Current.Value = lastSection; } }; - tabs.Current.ValueChanged += s => + tabs.Current.ValueChanged += section => { if (lastSection == null) { @@ -144,9 +97,10 @@ namespace osu.Game.Overlays tabs.Current.Value = lastSection; return; } - if (lastSection != s) + + if (lastSection != section.NewValue) { - lastSection = s; + lastSection = section.NewValue; sectionsContainer.ScrollTo(lastSection); } }; @@ -155,7 +109,7 @@ namespace osu.Game.Overlays { userReq = new GetUserRequest(user.Id); userReq.Success += userLoadComplete; - api.Queue(userReq); + API.Queue(userReq); } else { @@ -168,13 +122,14 @@ namespace osu.Game.Overlays private void userLoadComplete(User user) { - Header.User = user; + Header.User.Value = user; if (user.ProfileOrder != null) { foreach (string id in user.ProfileOrder) { var sec = sections.FirstOrDefault(s => s.Identifier == id); + if (sec != null) { sec.User.Value = user; @@ -186,43 +141,51 @@ namespace osu.Game.Overlays } } - private class ProfileTabControl : PageTabControl + private class ProfileTabControl : OverlayTabControl { - private readonly Box bottom; - public ProfileTabControl() { TabContainer.RelativeSizeAxes &= ~Axes.X; TabContainer.AutoSizeAxes |= Axes.X; TabContainer.Anchor |= Anchor.x1; TabContainer.Origin |= Anchor.x1; - AddInternal(bottom = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - EdgeSmoothness = new Vector2(1) - }); } - protected override TabItem CreateTabItem(ProfileSection value) => new ProfileTabItem(value); - - protected override Dropdown CreateDropdown() => null; - - private class ProfileTabItem : PageTabItem + protected override TabItem CreateTabItem(ProfileSection value) => new ProfileTabItem(value) { - public ProfileTabItem(ProfileSection value) : base(value) - { - Text.Text = value.Title; - } - } + AccentColour = AccentColour + }; [BackgroundDependencyLoader] private void load(OsuColour colours) { - bottom.Colour = colours.Yellow; + AccentColour = colours.Seafoam; } + + private class ProfileTabItem : OverlayTabItem + { + public ProfileTabItem(ProfileSection value) + : base(value) + { + Text.Text = value.Title; + } + } + } + + private class ProfileSectionsContainer : SectionsContainer + { + public ProfileSectionsContainer() + { + RelativeSizeAxes = Axes.Both; + } + + protected override FlowContainer CreateScrollContentContainer() => new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(0, 20), + }; } } } diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index e31b349827..a4884dc2c1 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -1,83 +1,91 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Volume { - public class MuteButton : Container, IHasCurrentValue + public class MuteButton : OsuButton, IHasCurrentValue { - public Bindable Current { get; } = new Bindable(); + private readonly Bindable current = new Bindable(); + + public Bindable Current + { + get => current; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + current.UnbindBindings(); + current.BindTo(value); + } + } private Color4 hoveredColour, unhoveredColour; + private const float width = 100; public const float HEIGHT = 35; public MuteButton() { - Masking = true; - BorderThickness = 3; - CornerRadius = HEIGHT / 2; + Content.BorderThickness = 3; + Content.CornerRadius = HEIGHT / 2; + Size = new Vector2(width, HEIGHT); + + Action = () => Current.Value = !Current.Value; } [BackgroundDependencyLoader] private void load(OsuColour colours) { hoveredColour = colours.YellowDark; - BorderColour = unhoveredColour = colours.Gray1.Opacity(0.9f); + + Content.BorderColour = unhoveredColour = colours.Gray1; + BackgroundColour = colours.Gray1; SpriteIcon icon; + AddRange(new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray1, - Alpha = 0.9f, - }, icon = new SpriteIcon { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(20), } }); - Current.ValueChanged += newValue => + Current.ValueChanged += muted => { - icon.Icon = newValue ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up; - icon.Margin = new MarginPadding { Left = newValue ? width / 2 - 15 : width / 2 - 10 }; //Magic numbers to line up both icons because they're different widths + icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp; }; + Current.TriggerChange(); } protected override bool OnHover(HoverEvent e) { - this.TransformTo("BorderColour", hoveredColour, 500, Easing.OutQuint); + Content.TransformTo, SRGBColour>("BorderColour", hoveredColour, 500, Easing.OutQuint); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.TransformTo("BorderColour", unhoveredColour, 500, Easing.OutQuint); - } - - protected override bool OnClick(ClickEvent e) - { - Current.Value = !Current.Value; - return true; + Content.TransformTo, SRGBColour>("BorderColour", unhoveredColour, 500, Easing.OutQuint); } } } diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index 3a64e12b27..26235fa280 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index c67ed5b845..da696e0fdd 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Globalization; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Volume private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; - public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1 }; + public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 }; private readonly float circleSize; private readonly Color4 meterColour; private readonly string name; @@ -140,8 +140,7 @@ namespace osu.Game.Overlays.Volume { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = "Venera", - TextSize = 0.16f * circleSize + Font = OsuFont.Numeric.With(size: 0.16f * circleSize) }).WithEffect(new GlowEffect { Colour = Color4.Transparent, @@ -169,16 +168,16 @@ namespace osu.Game.Overlays.Volume { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = "Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), Text = name } } } }; - Bindable.ValueChanged += newVolume => + Bindable.ValueChanged += volume => { this.TransformTo("DisplayVolume", - newVolume, + volume.NewValue, 400, Easing.OutQuint); }; @@ -218,11 +217,11 @@ namespace osu.Game.Overlays.Volume public double Volume { - get => Bindable; + get => Bindable.Value; private set => Bindable.Value = value; } - private const float adjust_step = 0.05f; + private const double adjust_step = 0.05; public void Increase(double amount = 1, bool isPrecise = false) => adjust(amount, isPrecise); public void Decrease(double amount = 1, bool isPrecise = false) => adjust(-amount, isPrecise); @@ -236,7 +235,7 @@ namespace osu.Game.Overlays.Volume var precision = Bindable.Precision; - while (Math.Abs(scrollAccumulation) > precision) + while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision)) { Volume += Math.Sign(scrollAccumulation) * precision; scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index cbe61b5896..e6204a3179 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -74,9 +74,9 @@ namespace osu.Game.Overlays volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); - muteButton.Current.ValueChanged += mute => + muteButton.Current.ValueChanged += muted => { - if (mute) + if (muted.NewValue) audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); else audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); @@ -100,20 +100,30 @@ namespace osu.Game.Overlays switch (action) { case GlobalAction.DecreaseVolume: - if (State == Visibility.Hidden) + if (State.Value == Visibility.Hidden) Show(); + else if (volumeMeterMusic.IsHovered) + volumeMeterMusic.Decrease(amount, isPrecise); + else if (volumeMeterEffect.IsHovered) + volumeMeterEffect.Decrease(amount, isPrecise); else volumeMeterMaster.Decrease(amount, isPrecise); return true; + case GlobalAction.IncreaseVolume: - if (State == Visibility.Hidden) + if (State.Value == Visibility.Hidden) Show(); + else if (volumeMeterMusic.IsHovered) + volumeMeterMusic.Increase(amount, isPrecise); + else if (volumeMeterEffect.IsHovered) + volumeMeterEffect.Increase(amount, isPrecise); else volumeMeterMaster.Increase(amount, isPrecise); return true; + case GlobalAction.ToggleMute: Show(); - muteButton.Current.Value = !muteButton.Current; + muteButton.Current.Value = !muteButton.Current.Value; return true; } @@ -124,7 +134,7 @@ namespace osu.Game.Overlays public override void Show() { - if (State == Visibility.Visible) + if (State.Value == Visibility.Visible) schedulePopOut(); base.Show(); diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index c5a4953c5e..05d3e7df0a 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; diff --git a/osu.Game/Properties/AssemblyInfo.cs b/osu.Game/Properties/AssemblyInfo.cs index df9045b802..cddea7a35f 100644 --- a/osu.Game/Properties/AssemblyInfo.cs +++ b/osu.Game/Properties/AssemblyInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Runtime.CompilerServices; @@ -9,3 +9,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("osu.Game.Tests")] [assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")] +[assembly: InternalsVisibleTo("osu.Game.Tests.iOS")] \ No newline at end of file diff --git a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs similarity index 84% rename from osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs rename to osu.Game/Replays/Legacy/LegacyReplayFrame.cs index f6de11454c..c3cffa8699 100644 --- a/osu.Game/Rulesets/Replays/Legacy/LegacyReplayFrame.cs +++ b/osu.Game/Replays/Legacy/LegacyReplayFrame.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Replays; using osuTK; -namespace osu.Game.Rulesets.Replays.Legacy +namespace osu.Game.Replays.Legacy { public class LegacyReplayFrame : ReplayFrame { diff --git a/osu.Game/Replays/Legacy/ReplayButtonState.cs b/osu.Game/Replays/Legacy/ReplayButtonState.cs new file mode 100644 index 0000000000..4b02cf2cd5 --- /dev/null +++ b/osu.Game/Replays/Legacy/ReplayButtonState.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Replays.Legacy +{ + [Flags] + public enum ReplayButtonState + { + None = 0, + Left1 = 1, + Right1 = 2, + Left2 = 4, + Right2 = 8, + Smoke = 16 + } +} diff --git a/osu.Game/Replays/Replay.cs b/osu.Game/Replays/Replay.cs new file mode 100644 index 0000000000..31d2ed0d70 --- /dev/null +++ b/osu.Game/Replays/Replay.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Replays +{ + public class Replay + { + public List Frames = new List(); + } +} diff --git a/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs index 09c74d8bac..179cd5e2dc 100644 --- a/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/IRulesetConfigManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Configuration.Tracking; diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 74cece5154..ed5fdf9809 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 1fdebd586f..b4b4bb9cd1 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -1,18 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Difficulty { public class DifficultyAttributes { - public readonly Mod[] Mods; - public readonly double StarRating; + public Mod[] Mods; + public Skill[] Skills; - public DifficultyAttributes(Mod[] mods, double starRating) + public double StarRating; + + public DifficultyAttributes() + { + } + + public DifficultyAttributes(Mod[] mods, Skill[] skills, double starRating) { Mods = mods; + Skills = skills; StarRating = starRating; } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 0de0a620e3..e31c963403 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,12 +7,20 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Difficulty { public abstract class DifficultyCalculator { + /// + /// The length of each strain section. + /// + protected virtual int SectionLength => 400; + private readonly Ruleset ruleset; private readonly WorkingBeatmap beatmap; @@ -29,13 +37,14 @@ namespace osu.Game.Rulesets.Difficulty /// A structure describing the difficulty of the beatmap. public DifficultyAttributes Calculate(params Mod[] mods) { - beatmap.Mods.Value = mods; - IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + mods = mods.Select(m => m.CreateCopy()).ToArray(); + + IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); var clock = new StopwatchClock(); mods.OfType().ForEach(m => m.ApplyToClock(clock)); - return Calculate(playableBeatmap, mods, clock.Rate); + return calculate(playableBeatmap, mods, clock.Rate); } /// @@ -53,12 +62,50 @@ namespace osu.Game.Rulesets.Difficulty } } + private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) + { + var skills = CreateSkills(beatmap); + + if (!beatmap.HitObjects.Any()) + return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + + var difficultyHitObjects = CreateDifficultyHitObjects(beatmap, clockRate).OrderBy(h => h.BaseObject.StartTime).ToList(); + + double sectionLength = SectionLength * clockRate; + + // The first object doesn't generate a strain, so we begin with an incremented section end + double currentSectionEnd = Math.Ceiling(beatmap.HitObjects.First().StartTime / sectionLength) * sectionLength; + + foreach (DifficultyHitObject h in difficultyHitObjects) + { + while (h.BaseObject.StartTime > currentSectionEnd) + { + foreach (Skill s in skills) + { + s.SaveCurrentPeak(); + s.StartNewSectionFrom(currentSectionEnd); + } + + currentSectionEnd += sectionLength; + } + + foreach (Skill s in skills) + s.Process(h); + } + + // The peak strain will not be saved for the last section in the above loop + foreach (Skill s in skills) + s.SaveCurrentPeak(); + + return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + } + /// /// Creates all combinations which adjust the difficulty. /// public Mod[] CreateDifficultyAdjustmentModCombinations() { - return createDifficultyAdjustmentModCombinations(Enumerable.Empty(), DifficultyAdjustmentMods).ToArray(); + return createDifficultyAdjustmentModCombinations(Array.Empty(), DifficultyAdjustmentMods).ToArray(); IEnumerable createDifficultyAdjustmentModCombinations(IEnumerable currentSet, Mod[] adjustmentSet, int currentSetCount = 0, int adjustmentSetStart = 0) { @@ -66,13 +113,18 @@ namespace osu.Game.Rulesets.Difficulty { case 0: // Initial-case: Empty current set - yield return new NoModMod(); + yield return new ModNoMod(); + break; + case 1: yield return currentSet.Single(); + break; + default: yield return new MultiMod(currentSet.ToArray()); + break; } @@ -96,12 +148,27 @@ namespace osu.Game.Rulesets.Difficulty protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); /// - /// Calculates the difficulty of a using a specific combination. + /// Creates to describe beatmap's calculated difficulty. /// - /// The to compute the difficulty for. - /// The s that should be applied. - /// The rate of time in . - /// A structure containing the difficulty attributes. - protected abstract DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate); + /// The whose difficulty was calculated. + /// The s that difficulty was calculated with. + /// The skills which processed the beatmap. + /// The rate at which the gameplay clock is run at. + protected abstract DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate); + + /// + /// Enumerates s to be processed from s in the . + /// + /// The providing the s to enumerate. + /// The rate at which the gameplay clock is run at. + /// The enumerated s. + protected abstract IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate); + + /// + /// Creates the s to calculate the difficulty of an . + /// + /// The whose difficulty will be calculated. + /// The s. + protected abstract Skill[] CreateSkills(IBeatmap beatmap); } } diff --git a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs index ba783ee87b..9ab81b9580 100644 --- a/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/PerformanceCalculator.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -7,7 +7,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Difficulty { @@ -17,17 +17,16 @@ namespace osu.Game.Rulesets.Difficulty protected readonly Ruleset Ruleset; protected readonly IBeatmap Beatmap; - protected readonly Score Score; + protected readonly ScoreInfo Score; protected double TimeRate { get; private set; } = 1; - protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score) + protected PerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, ScoreInfo score) { Ruleset = ruleset; Score = score; - beatmap.Mods.Value = score.Mods; - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, score.Mods); Attributes = ruleset.CreateDifficultyCalculator(beatmap).Calculate(score.Mods); diff --git a/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs new file mode 100644 index 0000000000..ebbffb5143 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/Preprocessing/DifficultyHitObject.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Difficulty.Preprocessing +{ + /// + /// Wraps a and provides additional information to be used for difficulty calculation. + /// + public class DifficultyHitObject + { + /// + /// The this wraps. + /// + public readonly HitObject BaseObject; + + /// + /// The last which occurs before . + /// + public readonly HitObject LastObject; + + /// + /// Amount of time elapsed between and . + /// + public readonly double DeltaTime; + + /// + /// Creates a new . + /// + /// The which this wraps. + /// The last which occurs before in the beatmap. + /// The rate at which the gameplay clock is run at. + public DifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate) + { + BaseObject = hitObject; + LastObject = lastObject; + DeltaTime = (hitObject.StartTime - lastObject.StartTime) / clockRate; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs similarity index 61% rename from osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs rename to osu.Game/Rulesets/Difficulty/Skills/Skill.cs index 47037c1503..227f2f4018 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Skill.cs +++ b/osu.Game/Rulesets/Difficulty/Skills/Skill.cs @@ -1,20 +1,25 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; -using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; -using osu.Game.Rulesets.Osu.Difficulty.Utils; -using osu.Game.Rulesets.Osu.Objects; +using System.Linq; +using osu.Game.Rulesets.Difficulty.Preprocessing; +using osu.Game.Rulesets.Difficulty.Utils; -namespace osu.Game.Rulesets.Osu.Difficulty.Skills +namespace osu.Game.Rulesets.Difficulty.Skills { /// - /// Used to processes strain values of s, keep track of strain levels caused by the processed objects + /// Used to processes strain values of s, keep track of strain levels caused by the processed objects /// and to calculate a final difficulty value representing the difficulty of hitting all the processed objects. /// public abstract class Skill { + /// + /// The peak strain for each section of the beatmap. + /// + public IReadOnlyList StrainPeaks => strainPeaks; + /// /// Strain values are multiplied by this number for the given skill. Used to balance the value of different skills between each other. /// @@ -27,22 +32,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills protected abstract double StrainDecayBase { get; } /// - /// s that were processed previously. They can affect the strain values of the following objects. + /// The weight by which each strain value decays. /// - protected readonly History Previous = new History(2); // Contained objects not used yet + protected virtual double DecayWeight => 0.9; + + /// + /// s that were processed previously. They can affect the strain values of the following objects. + /// + protected readonly LimitedCapacityStack Previous = new LimitedCapacityStack(2); // Contained objects not used yet private double currentStrain = 1; // We keep track of the strain level at all times throughout the beatmap. private double currentSectionPeak = 1; // We also keep track of the peak strain level in the current section. + private readonly List strainPeaks = new List(); /// - /// Process an and update current strain values accordingly. + /// Process a and update current strain values accordingly. /// - public void Process(OsuDifficultyHitObject current) + public void Process(DifficultyHitObject current) { currentStrain *= strainDecay(current.DeltaTime); - if (!(current.BaseObject is Spinner)) - currentStrain += StrainValueOf(current) * SkillMultiplier; + currentStrain += StrainValueOf(current) * SkillMultiplier; currentSectionPeak = Math.Max(currentStrain, currentSectionPeak); @@ -61,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// /// Sets the initial strain level for a new section. /// - /// The beginning of the new section in milliseconds + /// The beginning of the new section in milliseconds. public void StartNewSectionFrom(double offset) { // The maximum strain of the new section is not zero by default, strain decays as usual regardless of section boundaries. @@ -71,29 +81,28 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } /// - /// Returns the calculated difficulty value representing all processed s. + /// Returns the calculated difficulty value representing all processed s. /// public double DifficultyValue() { - strainPeaks.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. - double difficulty = 0; double weight = 1; // Difficulty is the weighted sum of the highest strains from every section. - foreach (double strain in strainPeaks) + // We're sorting from highest to lowest strain. + foreach (double strain in strainPeaks.OrderByDescending(d => d)) { difficulty += strain * weight; - weight *= 0.9; + weight *= DecayWeight; } return difficulty; } /// - /// Calculates the strain value of an . This value is affected by previously processed objects. + /// Calculates the strain value of a . This value is affected by previously processed objects. /// - protected abstract double StrainValueOf(OsuDifficultyHitObject current); + protected abstract double StrainValueOf(DifficultyHitObject current); private double strainDecay(double ms) => Math.Pow(StrainDecayBase, ms / 1000); } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs similarity index 63% rename from osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs rename to osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs index 55bd950209..95b7d9b19d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Utils/History.cs +++ b/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityStack.cs @@ -1,18 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections; using System.Collections.Generic; -namespace osu.Game.Rulesets.Osu.Difficulty.Utils +namespace osu.Game.Rulesets.Difficulty.Utils { /// - /// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full. - /// Indexing starts at the top of the stack. + /// An indexed stack with limited depth. Indexing starts at the top of the stack. /// - public class History : IEnumerable + public class LimitedCapacityStack : IEnumerable { + /// + /// The number of elements in the stack. + /// public int Count { get; private set; } private readonly T[] array; @@ -20,10 +22,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Utils private int marker; // Marks the position of the most recently added item. /// - /// Initializes a new instance of the History class that is empty and has the specified capacity. + /// Constructs a new . /// - /// The number of items the History can hold. - public History(int capacity) + /// The number of items the stack can hold. + public LimitedCapacityStack(int capacity) { if (capacity < 0) throw new ArgumentOutOfRangeException(); @@ -34,8 +36,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Utils } /// - /// The most recently added item is returned at index 0. + /// Retrieves the item at an index in the stack. /// + /// The index of the item to retrieve. The top of the stack is returned at index 0. public T this[int i] { get @@ -52,11 +55,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Utils } /// - /// Adds the item as the most recent one in the history. - /// The oldest item is disposed if the history is full. + /// Pushes an item to this . /// - public void Push(T item) // Overwrite the oldest item instead of shifting every item by one with every addition. + /// The item to push. + public void Push(T item) { + // Overwrite the oldest item instead of shifting every item by one with every addition. if (marker == 0) marker = capacity - 1; else diff --git a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs similarity index 63% rename from osu.Game/Rulesets/Edit/EditRulesetContainer.cs rename to osu.Game/Rulesets/Edit/DrawableEditRuleset.cs index bc54c907ab..2200caeb20 100644 --- a/osu.Game/Rulesets/Edit/EditRulesetContainer.cs +++ b/osu.Game/Rulesets/Edit/DrawableEditRuleset.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Graphics; @@ -11,14 +11,16 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit { - public abstract class EditRulesetContainer : CompositeDrawable + public abstract class DrawableEditRuleset : CompositeDrawable { /// - /// The contained by this . + /// The contained by this . /// public abstract Playfield Playfield { get; } - internal EditRulesetContainer() + public abstract PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer(); + + internal DrawableEditRuleset() { RelativeSizeAxes = Axes.Both; } @@ -38,21 +40,23 @@ namespace osu.Game.Rulesets.Edit internal abstract DrawableHitObject Remove(HitObject hitObject); } - public class EditRulesetContainer : EditRulesetContainer + public class DrawableEditRuleset : DrawableEditRuleset where TObject : HitObject { - public override Playfield Playfield => rulesetContainer.Playfield; + public override Playfield Playfield => drawableRuleset.Playfield; - private Ruleset ruleset => rulesetContainer.Ruleset; - private Beatmap beatmap => rulesetContainer.Beatmap; + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => drawableRuleset.CreatePlayfieldAdjustmentContainer(); - private readonly RulesetContainer rulesetContainer; + private Ruleset ruleset => drawableRuleset.Ruleset; + private Beatmap beatmap => drawableRuleset.Beatmap; - public EditRulesetContainer(RulesetContainer rulesetContainer) + private readonly DrawableRuleset drawableRuleset; + + public DrawableEditRuleset(DrawableRuleset drawableRuleset) { - this.rulesetContainer = rulesetContainer; + this.drawableRuleset = drawableRuleset; - InternalChild = rulesetContainer; + InternalChild = drawableRuleset; Playfield.DisplayJudgements.Value = false; } @@ -68,15 +72,15 @@ namespace osu.Game.Rulesets.Edit // Process object var processor = ruleset.CreateBeatmapProcessor(beatmap); - processor.PreProcess(); + processor?.PreProcess(); tObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); - processor.PostProcess(); + processor?.PostProcess(); // Add visual representation - var drawableObject = rulesetContainer.GetVisualRepresentation(tObject); + var drawableObject = drawableRuleset.CreateDrawableRepresentation(tObject); - rulesetContainer.Playfield.Add(drawableObject); - rulesetContainer.Playfield.PostProcess(); + drawableRuleset.Playfield.Add(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } @@ -91,14 +95,14 @@ namespace osu.Game.Rulesets.Edit // Process the beatmap var processor = ruleset.CreateBeatmapProcessor(beatmap); - processor.PreProcess(); - processor.PostProcess(); + processor?.PreProcess(); + processor?.PostProcess(); // Remove visual representation var drawableObject = Playfield.AllHitObjects.Single(d => d.HitObject == hitObject); - rulesetContainer.Playfield.Remove(drawableObject); - rulesetContainer.Playfield.PostProcess(); + drawableRuleset.Playfield.Remove(drawableObject); + drawableRuleset.Playfield.PostProcess(); return drawableObject; } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 86ecbc0bb0..38ec09535d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -14,6 +14,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Edit { public abstract class HitObjectComposer : CompositeDrawable { - public IEnumerable HitObjects => rulesetContainer.Playfield.AllHitObjects; + public IEnumerable HitObjects => DrawableRuleset.Playfield.AllHitObjects; protected readonly Ruleset Ruleset; @@ -34,7 +35,7 @@ namespace osu.Game.Rulesets.Edit private readonly List layerContainers = new List(); - private EditRulesetContainer rulesetContainer; + protected DrawableEditRuleset DrawableRuleset { get; private set; } private BlueprintContainer blueprintContainer; @@ -48,14 +49,14 @@ namespace osu.Game.Rulesets.Edit } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap, IFrameBasedClock framedClock) + private void load(IBindable beatmap, IFrameBasedClock framedClock) { Beatmap.BindTo(beatmap); try { - rulesetContainer = CreateRulesetContainer(); - rulesetContainer.Clock = framedClock; + DrawableRuleset = CreateDrawableRuleset(); + DrawableRuleset.Clock = framedClock; } catch (Exception e) { @@ -63,10 +64,10 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = CreateLayerContainer(); + var layerBelowRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); layerBelowRuleset.Child = new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }; - var layerAboveRuleset = CreateLayerContainer(); + var layerAboveRuleset = DrawableRuleset.CreatePlayfieldAdjustmentContainer(); layerAboveRuleset.Child = blueprintContainer = new BlueprintContainer(); layerContainers.Add(layerBelowRuleset); @@ -97,7 +98,7 @@ namespace osu.Game.Rulesets.Edit Children = new Drawable[] { layerBelowRuleset, - rulesetContainer, + DrawableRuleset, layerAboveRuleset } } @@ -111,8 +112,8 @@ namespace osu.Game.Rulesets.Edit toolboxCollection.Items = CompositionTools.Select(t => new RadioButton(t.Name, () => blueprintContainer.CurrentTool = t)) - .Prepend(new RadioButton("Select", () => blueprintContainer.CurrentTool = null)) - .ToList(); + .Prepend(new RadioButton("Select", () => blueprintContainer.CurrentTool = null)) + .ToList(); toolboxCollection.Items[0].Select(); } @@ -140,27 +141,27 @@ namespace osu.Game.Rulesets.Edit layerContainers.ForEach(l => { - l.Anchor = rulesetContainer.Playfield.Anchor; - l.Origin = rulesetContainer.Playfield.Origin; - l.Position = rulesetContainer.Playfield.Position; - l.Size = rulesetContainer.Playfield.Size; + l.Anchor = DrawableRuleset.Playfield.Anchor; + l.Origin = DrawableRuleset.Playfield.Origin; + l.Position = DrawableRuleset.Playfield.Position; + l.Size = DrawableRuleset.Playfield.Size; }); } /// /// Whether the user's cursor is currently in an area of the that is valid for placement. /// - public virtual bool CursorInPlacementArea => rulesetContainer.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + public virtual bool CursorInPlacementArea => DrawableRuleset.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); /// /// Adds a to the and visualises it. /// /// The to add. - public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(rulesetContainer.Add(hitObject)); + public void Add(HitObject hitObject) => blueprintContainer.AddBlueprintFor(DrawableRuleset.Add(hitObject)); - public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(rulesetContainer.Remove(hitObject)); + public void Remove(HitObject hitObject) => blueprintContainer.RemoveBlueprintFor(DrawableRuleset.Remove(hitObject)); - internal abstract EditRulesetContainer CreateRulesetContainer(); + internal abstract DrawableEditRuleset CreateDrawableRuleset(); protected abstract IReadOnlyList CompositionTools { get; } @@ -171,15 +172,9 @@ namespace osu.Game.Rulesets.Edit public virtual SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => null; /// - /// Creates a which outlines s - /// and handles hitobject pattern adjustments. + /// Creates a which outlines s and handles movement of selections. /// - public virtual SelectionBox CreateSelectionBox() => new SelectionBox(); - - /// - /// Creates a which provides a layer above or below the . - /// - protected virtual Container CreateLayerContainer() => new Container { RelativeSizeAxes = Axes.Both }; + public virtual SelectionHandler CreateSelectionHandler() => new SelectionHandler(); } public abstract class HitObjectComposer : HitObjectComposer @@ -190,9 +185,9 @@ namespace osu.Game.Rulesets.Edit { } - internal override EditRulesetContainer CreateRulesetContainer() - => new EditRulesetContainer(CreateRulesetContainer(Ruleset, Beatmap.Value)); + internal override DrawableEditRuleset CreateDrawableRuleset() + => new DrawableEditRuleset(CreateDrawableRuleset(Ruleset, Beatmap.Value, Array.Empty())); - protected abstract RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap); + protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods); } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 2414a682e9..757c269358 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Edit /// /// A blueprint which governs the creation of a new to actualisation. /// - public abstract class PlacementBlueprint : CompositeDrawable, IStateful, IRequireHighFrequencyMousePosition + public abstract class PlacementBlueprint : CompositeDrawable, IStateful { /// /// Invoked when has changed. @@ -50,11 +50,15 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both; + // This is required to allow the blueprint's position to be updated via OnMouseMove/Handle + // on the same frame it is made visible via a PlacementState change. + AlwaysPresent = true; + Alpha = 0; } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap, IAdjustableClock clock) + private void load(IBindable beatmap, IAdjustableClock clock) { this.beatmap.BindTo(beatmap); @@ -72,6 +76,7 @@ namespace osu.Game.Rulesets.Edit { if (state == value) return; + state = value; if (state == PlacementState.Shown) @@ -104,7 +109,8 @@ namespace osu.Game.Rulesets.Edit } /// - /// Invokes , refreshing and parameters for the . + /// Invokes , + /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); @@ -118,8 +124,10 @@ namespace osu.Game.Rulesets.Edit { case ScrollEvent _: return false; + case MouseEvent _: return true; + default: return false; } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index b94b6a89a7..0f77b8d584 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework; @@ -38,14 +38,14 @@ namespace osu.Game.Rulesets.Edit /// /// Invoked when this has requested drag. /// - public event Action DragRequested; + public event Action DragRequested; /// /// The which this applies to. /// public readonly DrawableHitObject HitObject; - protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == SelectionState.Selected; + protected override bool ShouldBeAlive => (HitObject.IsAlive && HitObject.IsPresent) || State == SelectionState.Selected; public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; @@ -68,20 +68,25 @@ namespace osu.Game.Rulesets.Edit get => state; set { - if (state == value) return; + if (state == value) + return; state = value; + switch (state) { case SelectionState.Selected: Show(); Selected?.Invoke(this); break; + case SelectionState.NotSelected: Hide(); Deselected?.Invoke(this); break; } + + StateChanged?.Invoke(state); } } @@ -130,12 +135,10 @@ namespace osu.Game.Rulesets.Edit protected override bool OnDrag(DragEvent e) { - DragRequested?.Invoke(e); + DragRequested?.Invoke(this, e); return true; } - public abstract void AdjustPosition(DragEvent dragEvent); - /// /// The screen-space point that causes this to be selected. /// diff --git a/osu.Game/Rulesets/Edit/ToolboxGroup.cs b/osu.Game/Rulesets/Edit/ToolboxGroup.cs index 4de0c518e6..eabb834616 100644 --- a/osu.Game/Rulesets/Edit/ToolboxGroup.cs +++ b/osu.Game/Rulesets/Edit/ToolboxGroup.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Screens.Play.PlayerSettings; diff --git a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs index 1cb3c4c451..825c63e6ee 100644 --- a/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/HitObjectCompositionTool.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Edit.Tools { diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index dd0152d957..2150726a42 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; @@ -32,6 +32,12 @@ namespace osu.Game.Rulesets.Judgements protected Container JudgementBody; protected SpriteText JudgementText; + /// + /// Duration of initial fade in. + /// Default fade out will start immediately after this duration. + /// + protected virtual double FadeInDuration => 100; + /// /// Creates a drawable which visualises a . /// @@ -58,24 +64,32 @@ namespace osu.Game.Rulesets.Judgements Child = new SkinnableDrawable($"Play/{Result.Type}", _ => JudgementText = new OsuSpriteText { Text = Result.Type.GetDescription().ToUpperInvariant(), - Font = @"Venera", + Font = OsuFont.Numeric.With(size: 12), Colour = judgementColour(Result.Type), Scale = new Vector2(0.85f, 1), - TextSize = 12 }, restrictSize: false) }; } + protected virtual void ApplyHitAnimations() + { + JudgementBody.ScaleTo(0.9f); + JudgementBody.ScaleTo(1, 500, Easing.OutElastic); + + this.Delay(FadeInDuration).FadeOut(400); + } + protected override void LoadComplete() { base.LoadComplete(); - this.FadeInFromZero(100, Easing.OutQuint); + this.FadeInFromZero(FadeInDuration, Easing.OutQuint); switch (Result.Type) { case HitResult.None: break; + case HitResult.Miss: JudgementBody.ScaleTo(1.6f); JudgementBody.ScaleTo(1, 100, Easing.In); @@ -85,11 +99,9 @@ namespace osu.Game.Rulesets.Judgements this.Delay(600).FadeOut(200); break; - default: - JudgementBody.ScaleTo(0.9f); - JudgementBody.ScaleTo(1, 500, Easing.OutElastic); - this.Delay(100).FadeOut(400); + default: + ApplyHitAnimations(); break; } @@ -103,13 +115,17 @@ namespace osu.Game.Rulesets.Judgements case HitResult.Perfect: case HitResult.Great: return colours.Blue; + case HitResult.Ok: case HitResult.Good: return colours.Green; + case HitResult.Meh: return colours.Yellow; + case HitResult.Miss: return colours.Red; + default: return Color4.White; } diff --git a/osu.Game/Rulesets/Judgements/Judgement.cs b/osu.Game/Rulesets/Judgements/Judgement.cs index c679df5900..f07f76a2b8 100644 --- a/osu.Game/Rulesets/Judgements/Judgement.cs +++ b/osu.Game/Rulesets/Judgements/Judgement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Judgements /// public int MaxNumericResult => NumericResultFor(MaxResult); + /// + /// The health increase for the maximum achievable result. + /// + public double MaxHealthIncrease => HealthIncreaseFor(MaxResult); + /// /// Retrieves the numeric score representation of a . /// @@ -44,5 +49,21 @@ namespace osu.Game.Rulesets.Judgements /// The to find the numeric score representation for. /// The numeric score representation of . public int NumericResultFor(JudgementResult result) => NumericResultFor(result.Type); + + /// + /// Retrieves the numeric health increase of a . + /// + /// The to find the numeric health increase for. + /// The numeric health increase of . + protected virtual double HealthIncreaseFor(HitResult result) => 0; + + /// + /// Retrieves the numeric health increase of a . + /// + /// The to find the numeric health increase for. + /// The numeric health increase of . + public double HealthIncreaseFor(JudgementResult result) => HealthIncreaseFor(result.Type); + + public override string ToString() => $"AffectsCombo:{AffectsCombo} MaxResult:{MaxResult} MaxScore:{MaxNumericResult}"; } } diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 5cadf7e2ee..195fe316ac 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -37,6 +37,11 @@ namespace osu.Game.Rulesets.Judgements /// public int HighestComboAtJudgement { get; internal set; } + /// + /// The health prior to this occurring. + /// + public double HealthAtJudgement { get; internal set; } + /// /// Whether a miss or hit occurred. /// @@ -55,5 +60,7 @@ namespace osu.Game.Rulesets.Judgements { Judgement = judgement; } + + public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})"; } } diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index 6a4042a906..c0262b783b 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mods { diff --git a/osu.Game/Rulesets/Mods/IApplicableMod.cs b/osu.Game/Rulesets/Mods/IApplicableMod.cs index 30046c3dc2..8ca1a3f8a5 100644 --- a/osu.Game/Rulesets/Mods/IApplicableMod.cs +++ b/osu.Game/Rulesets/Mods/IApplicableMod.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mods { diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs index 1eb74ca76a..a634976b3c 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index 1b8e62b53c..8cefb02904 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -1,15 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods { /// /// Interface for a that applies changes to a . /// - /// The type of converted . public interface IApplicableToBeatmapConverter : IApplicableMod { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToClock.cs b/osu.Game/Rulesets/Mods/IApplicableToClock.cs index a9570d80c2..e5767b5fbf 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToClock.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToClock.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Timing; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs index 3f32253544..4d4cd75434 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index fca04ca513..5630315770 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Rulesets.Objects.Drawables; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs new file mode 100644 index 0000000000..b012beb0c0 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for s that can be applied to s. + /// + public interface IApplicableToDrawableRuleset : IApplicableMod + where TObject : HitObject + { + /// + /// Applies this to a . + /// + /// The to apply to. + void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset); + } +} diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index d6f330d9df..f7f81c92c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects; @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mods public interface IApplicableToHitObject : IApplicableMod { /// - /// Applies this to a . + /// Applies this to a . /// /// The to apply to. void ApplyToHitObject(HitObject hitObject); diff --git a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs b/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs deleted file mode 100644 index a3c5ab3df4..0000000000 --- a/osu.Game/Rulesets/Mods/IApplicableToRulesetContainer.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; - -namespace osu.Game.Rulesets.Mods -{ - /// - /// An interface for s that can be applied to s. - /// - public interface IApplicableToRulesetContainer : IApplicableMod - where TObject : HitObject - { - /// - /// Applies this to a . - /// - /// The to apply to. - void ApplyToRulesetContainer(RulesetContainer rulesetContainer); - } -} diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index 772eac478c..cb00770868 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { @@ -10,6 +11,14 @@ namespace osu.Game.Rulesets.Mods /// public interface IApplicableToScoreProcessor : IApplicableMod { + /// + /// Provide a to a mod. Called once on initialisation of a play instance. + /// void ApplyToScoreProcessor(ScoreProcessor scoreProcessor); + + /// + /// Called every time a rank calculation is requested. Allows mods to adjust the final rank. + /// + ScoreRank AdjustRank(ScoreRank rank, double accuracy); } } diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs new file mode 100644 index 0000000000..a5e19f293c --- /dev/null +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Newtonsoft.Json; + +namespace osu.Game.Rulesets.Mods +{ + public interface IMod : IEquatable + { + /// + /// The shortened name of this mod. + /// + [JsonProperty("acronym")] + string Acronym { get; } + } +} diff --git a/osu.Game/Rulesets/Mods/IReadFromConfig.cs b/osu.Game/Rulesets/Mods/IReadFromConfig.cs index 93c9ae0c34..d66fabce70 100644 --- a/osu.Game/Rulesets/Mods/IReadFromConfig.cs +++ b/osu.Game/Rulesets/Mods/IReadFromConfig.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs index be879759bd..9baa252caf 100644 --- a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs +++ b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.UI; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index a991f7e7b0..023d37497a 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -1,59 +1,76 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Game.Graphics; using System; +using Newtonsoft.Json; +using osu.Framework.Graphics.Sprites; +using osu.Game.IO.Serialization; namespace osu.Game.Rulesets.Mods { /// /// The base class for gameplay modifiers. /// - public abstract class Mod + public abstract class Mod : IMod, IJsonSerializable { /// /// The name of this mod. /// + [JsonIgnore] public abstract string Name { get; } /// /// The shortened name of this mod. /// - public abstract string ShortenedName { get; } + public abstract string Acronym { get; } /// /// The icon of this mod. /// - public virtual FontAwesome Icon => FontAwesome.fa_question; + [JsonIgnore] + public virtual IconUsage Icon => FontAwesome.Solid.Question; /// /// The type of this mod. /// + [JsonIgnore] public virtual ModType Type => ModType.Fun; /// /// The user readable description of this mod. /// + [JsonIgnore] public virtual string Description => string.Empty; /// /// The score multiplier of this mod. /// + [JsonIgnore] public abstract double ScoreMultiplier { get; } /// /// Returns true if this mod is implemented (and playable). /// + [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; /// /// Returns if this mod is ranked. /// + [JsonIgnore] public virtual bool Ranked => false; /// /// The mods this mod cannot be enabled with. /// + [JsonIgnore] public virtual Type[] IncompatibleMods => new Type[] { }; + + /// + /// Creates a copy of this initialised to a default state. + /// + public virtual Mod CreateCopy() => (Mod)Activator.CreateInstance(GetType()); + + public bool Equals(IMod other) => GetType() == other?.GetType(); } } diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 5c03cb9736..e70d58acea 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -1,35 +1,36 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Replays; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModAutoplay : ModAutoplay, IApplicableToRulesetContainer + public abstract class ModAutoplay : ModAutoplay, IApplicableToDrawableRuleset where T : HitObject { - protected virtual Score CreateReplayScore(Beatmap beatmap) => new Score { Replay = new Replay() }; - - public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; - - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) => rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) => drawableRuleset.SetReplayScore(CreateReplayScore(drawableRuleset.Beatmap)); } public abstract class ModAutoplay : Mod, IApplicableFailOverride { public override string Name => "Autoplay"; - public override string ShortenedName => "AT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_auto; + public override string Acronym => "AT"; + public override IconUsage Icon => OsuIcon.ModAuto; public override ModType Type => ModType.Automation; public override string Description => "Watch a perfect automated play through the song."; public override double ScoreMultiplier => 1; public bool AllowFail => false; public override Type[] IncompatibleMods => new[] { typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail) }; + + public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; + + public virtual Score CreateReplayScore(IBeatmap beatmap) => new Score { Replay = new Replay() }; } } diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 421e3a54e4..3c6a3a54aa 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -8,9 +9,9 @@ namespace osu.Game.Rulesets.Mods public class ModCinema : ModAutoplay { public override string Name => "Cinema"; - public override string ShortenedName => "CN"; + public override string Acronym => "CN"; public override bool HasImplementation => false; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_cinema; + public override IconUsage Icon => OsuIcon.ModCinema; public override string Description => "Watch the video without visual distractions."; } } diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index f11d0c76ed..7e6d959119 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.cs @@ -1,24 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Graphics.Sprites; using osu.Framework.Timing; -using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { public abstract class ModDaycore : ModHalfTime { public override string Name => "Daycore"; - public override string ShortenedName => "DC"; - public override FontAwesome Icon => FontAwesome.fa_question; + public override string Acronym => "DC"; + public override IconUsage Icon => FontAwesome.Solid.Question; public override string Description => "Whoaaaaa..."; public override void ApplyToClock(IAdjustableClock clock) { - var pitchAdjust = clock as IHasPitchAdjust; - if (pitchAdjust != null) - pitchAdjust.PitchAdjust = 0.75; + if (clock is IHasPitchAdjust pitchAdjust) + pitchAdjust.PitchAdjust *= RateAdjust; else base.ApplyToClock(clock); } diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 0d44495fce..a5e76e32b1 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -1,25 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Timing; +using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModDoubleTime : Mod, IApplicableToClock + public abstract class ModDoubleTime : ModTimeAdjust, IApplicableToClock { public override string Name => "Double Time"; - public override string ShortenedName => "DT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_doubletime; + public override string Acronym => "DT"; + public override IconUsage Icon => OsuIcon.ModDoubletime; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Zoooooooooom..."; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModHalfTime) }; - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 1.5; - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModHalfTime)).ToArray(); + + protected override double RateAdjust => 1.5; } } diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 93abb82e5b..56ec0bec06 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -10,8 +11,8 @@ namespace osu.Game.Rulesets.Mods public abstract class ModEasy : Mod, IApplicableToDifficulty { public override string Name => "Easy"; - public override string ShortenedName => "EZ"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_easy; + public override string Acronym => "EZ"; + public override IconUsage Icon => OsuIcon.ModEasy; public override ModType Type => ModType.DifficultyReduction; public override double ScoreMultiplier => 0.5; public override bool Ranked => true; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 9388d40146..405d21c711 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -1,35 +1,44 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps.Timing; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModFlashlight : Mod, IApplicableToRulesetContainer, IApplicableToScoreProcessor - where T : HitObject + public abstract class ModFlashlight : Mod { public override string Name => "Flashlight"; - public override string ShortenedName => "FL"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_flashlight; + public override string Acronym => "FL"; + public override IconUsage Icon => OsuIcon.ModFlashlight; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Restricted view area."; public override bool Ranked => true; + internal ModFlashlight() + { + } + } + + public abstract class ModFlashlight : ModFlashlight, IApplicableToDrawableRuleset, IApplicableToScoreProcessor + where T : HitObject + { public const double FLASHLIGHT_FADE_DURATION = 800; protected readonly BindableInt Combo = new BindableInt(); @@ -38,15 +47,17 @@ namespace osu.Game.Rulesets.Mods Combo.BindTo(scoreProcessor.Combo); } - public virtual void ApplyToRulesetContainer(RulesetContainer rulesetContainer) + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + + public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { var flashlight = CreateFlashlight(); flashlight.Combo = Combo; flashlight.RelativeSizeAxes = Axes.Both; flashlight.Colour = Color4.Black; - rulesetContainer.KeyBindingInputManager.Add(flashlight); + drawableRuleset.KeyBindingInputManager.Add(flashlight); - flashlight.Breaks = rulesetContainer.Beatmap.Breaks; + flashlight.Breaks = drawableRuleset.Beatmap.Breaks; } public abstract Flashlight CreateFlashlight(); @@ -54,26 +65,14 @@ namespace osu.Game.Rulesets.Mods public abstract class Flashlight : Drawable { internal BindableInt Combo; - private Shader shader; + private IShader shader; - protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(); + protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this); public override bool RemoveCompletedTransforms => false; public List Breaks; - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var flashNode = (FlashlightDrawNode)node; - - flashNode.Shader = shader; - flashNode.ScreenSpaceDrawQuad = ScreenSpaceDrawQuad; - flashNode.FlashlightPosition = Vector2Extensions.Transform(FlashlightPosition, DrawInfo.Matrix); - flashNode.FlashlightSize = Vector2Extensions.Transform(FlashlightSize, DrawInfo.Matrix); - } - [BackgroundDependencyLoader] private void load(ShaderManager shaderManager) { @@ -86,22 +85,24 @@ namespace osu.Game.Rulesets.Mods Combo.ValueChanged += OnComboChange; - this.FadeInFromZero(FLASHLIGHT_FADE_DURATION); - - foreach (var breakPeriod in Breaks) + using (BeginAbsoluteSequence(0)) { - if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; + foreach (var breakPeriod in Breaks) + { + if (breakPeriod.Duration < FLASHLIGHT_FADE_DURATION * 2) continue; - this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); - this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.StartTime + FLASHLIGHT_FADE_DURATION).FadeOutFromOne(FLASHLIGHT_FADE_DURATION); + this.Delay(breakPeriod.EndTime - FLASHLIGHT_FADE_DURATION).FadeInFromZero(FLASHLIGHT_FADE_DURATION); + } } } - protected abstract void OnComboChange(int newCombo); + protected abstract void OnComboChange(ValueChangedEvent e); protected abstract string FragmentShader { get; } private Vector2 flashlightPosition; + protected Vector2 FlashlightPosition { get => flashlightPosition; @@ -115,6 +116,7 @@ namespace osu.Game.Rulesets.Mods } private Vector2 flashlightSize; + protected Vector2 FlashlightSize { get => flashlightSize; @@ -126,27 +128,61 @@ namespace osu.Game.Rulesets.Mods Invalidate(Invalidation.DrawNode); } } - } - private class FlashlightDrawNode : DrawNode - { - public Shader Shader; - public Quad ScreenSpaceDrawQuad; - public Vector2 FlashlightPosition; - public Vector2 FlashlightSize; + private float flashlightDim; - public override void Draw(Action vertexAction) + public float FlashlightDim { - base.Draw(vertexAction); + get => flashlightDim; + set + { + if (flashlightDim == value) return; - Shader.Bind(); + flashlightDim = value; + Invalidate(Invalidation.DrawNode); + } + } - Shader.GetUniform("flashlightPos").UpdateValue(ref FlashlightPosition); - Shader.GetUniform("flashlightSize").UpdateValue(ref FlashlightSize); + private class FlashlightDrawNode : DrawNode + { + protected new Flashlight Source => (Flashlight)base.Source; - Texture.WhitePixel.DrawQuad(ScreenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + private IShader shader; + private Quad screenSpaceDrawQuad; + private Vector2 flashlightPosition; + private Vector2 flashlightSize; + private float flashlightDim; - Shader.Unbind(); + public FlashlightDrawNode(Flashlight source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; + flashlightPosition = Vector2Extensions.Transform(Source.FlashlightPosition, DrawInfo.Matrix); + flashlightSize = Source.FlashlightSize * DrawInfo.Matrix.ExtractScale().Xy; + flashlightDim = Source.FlashlightDim; + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + shader.Bind(); + + shader.GetUniform("flashlightPos").UpdateValue(ref flashlightPosition); + shader.GetUniform("flashlightSize").UpdateValue(ref flashlightSize); + shader.GetUniform("flashlightDim").UpdateValue(ref flashlightDim); + + DrawQuad(Texture.WhitePixel, screenSpaceDrawQuad, DrawColourInfo.Colour, vertexAction: vertexAction); + + shader.Unbind(); + } } } } diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 3ec4fb9f9f..27369f4c30 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -1,25 +1,24 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Timing; +using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods { - public abstract class ModHalfTime : Mod, IApplicableToClock + public abstract class ModHalfTime : ModTimeAdjust, IApplicableToClock { public override string Name => "Half Time"; - public override string ShortenedName => "HT"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_halftime; + public override string Acronym => "HT"; + public override IconUsage Icon => OsuIcon.ModHalftime; public override ModType Type => ModType.DifficultyReduction; public override string Description => "Less zoom..."; public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModDoubleTime) }; - public virtual void ApplyToClock(IAdjustableClock clock) - { - clock.Rate = 0.75; - } + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModDoubleTime)).ToArray(); + + protected override double RateAdjust => 0.75; } } diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 6379be9bfc..2044cbeae2 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -10,8 +11,8 @@ namespace osu.Game.Rulesets.Mods public abstract class ModHardRock : Mod, IApplicableToDifficulty { public override string Name => "Hard Rock"; - public override string ShortenedName => "HR"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hardrock; + public override string Acronym => "HR"; + public override IconUsage Icon => OsuIcon.ModHardrock; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy) }; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 9b09f0bd6d..0934992f55 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -1,20 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { - public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects + public abstract class ModHidden : Mod, IReadFromConfig, IApplicableToDrawableHitObjects, IApplicableToScoreProcessor { public override string Name => "Hidden"; - public override string ShortenedName => "HD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; + public override string Acronym => "HD"; + public override IconUsage Icon => OsuIcon.ModHidden; public override ModType Type => ModType.DifficultyIncrease; public override bool Ranked => true; @@ -27,10 +30,33 @@ namespace osu.Game.Rulesets.Mods public virtual void ApplyToDrawableHitObjects(IEnumerable drawables) { - foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility ? 1 : 0)) + foreach (var d in drawables.Skip(IncreaseFirstObjectVisibility.Value ? 1 : 0)) d.ApplyCustomUpdateState += ApplyHiddenState; } - protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) { } + public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) + { + // Default value of ScoreProcessor's Rank in Hidden Mod should be SS+ + scoreProcessor.Rank.Value = ScoreRank.XH; + } + + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) + { + switch (rank) + { + case ScoreRank.X: + return ScoreRank.XH; + + case ScoreRank.S: + return ScoreRank.SH; + + default: + return rank; + } + } + + protected virtual void ApplyHiddenState(DrawableHitObject hitObject, ArmedState state) + { + } } } diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 162d654965..dc0fc33088 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Graphics.Sprites; using osu.Framework.Timing; using osu.Game.Graphics; @@ -10,15 +11,14 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNightcore : ModDoubleTime { public override string Name => "Nightcore"; - public override string ShortenedName => "NC"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nightcore; + public override string Acronym => "NC"; + public override IconUsage Icon => OsuIcon.ModNightcore; public override string Description => "Uguuuuuuuu..."; public override void ApplyToClock(IAdjustableClock clock) { - var pitchAdjust = clock as IHasPitchAdjust; - if (pitchAdjust != null) - pitchAdjust.PitchAdjust = 1.5; + if (clock is IHasPitchAdjust pitchAdjust) + pitchAdjust.PitchAdjust *= RateAdjust; else base.ApplyToClock(clock); } diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 7510f62432..1ee1f92d8c 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -9,8 +10,8 @@ namespace osu.Game.Rulesets.Mods public abstract class ModNoFail : Mod, IApplicableFailOverride { public override string Name => "No Fail"; - public override string ShortenedName => "NF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; + public override string Acronym => "NF"; + public override IconUsage Icon => OsuIcon.ModNofail; public override ModType Type => ModType.DifficultyReduction; public override string Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; diff --git a/osu.Game/Rulesets/Mods/NoModMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs similarity index 50% rename from osu.Game/Rulesets/Mods/NoModMod.cs rename to osu.Game/Rulesets/Mods/ModNoMod.cs index dcab3538da..0ddbd2a8cb 100644 --- a/osu.Game/Rulesets/Mods/NoModMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -1,15 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mods { /// /// Indicates a type of mod that doesn't do anything. /// - public sealed class NoModMod : Mod + public sealed class ModNoMod : Mod { public override string Name => "No Mod"; - public override string ShortenedName => "NM"; + public override string Acronym => "NM"; public override double ScoreMultiplier => 1; } } diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 802890866f..0994d1f7d3 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,7 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods @@ -9,10 +11,10 @@ namespace osu.Game.Rulesets.Mods public abstract class ModPerfect : ModSuddenDeath { public override string Name => "Perfect"; - public override string ShortenedName => "PF"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect; + public override string Acronym => "PF"; + public override IconUsage Icon => OsuIcon.ModPerfect; public override string Description => "SS or quit."; - protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1; + protected override bool FailCondition(ScoreProcessor scoreProcessor, JudgementResult result) => scoreProcessor.Accuracy.Value != 1; } } diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 04aa295893..4feb89186c 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; namespace osu.Game.Rulesets.Mods @@ -9,8 +10,8 @@ namespace osu.Game.Rulesets.Mods public abstract class ModRelax : Mod { public override string Name => "Relax"; - public override string ShortenedName => "RX"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_relax; + public override string Acronym => "RX"; + public override IconUsage Icon => OsuIcon.ModRelax; public override ModType Type => ModType.Automation; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModNoFail), typeof(ModSuddenDeath) }; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 48f7d496a5..e332abd914 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -1,17 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Mods { public abstract class ModSuddenDeath : Mod, IApplicableToScoreProcessor { public override string Name => "Sudden Death"; - public override string ShortenedName => "SD"; - public override FontAwesome Icon => FontAwesome.fa_osu_mod_suddendeath; + public override string Acronym => "SD"; + public override IconUsage Icon => OsuIcon.ModSuddendeath; public override ModType Type => ModType.DifficultyIncrease; public override string Description => "Miss and fail."; public override double ScoreMultiplier => 1; @@ -23,6 +26,8 @@ namespace osu.Game.Rulesets.Mods scoreProcessor.FailConditions += FailCondition; } - protected virtual bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Combo.Value == 0; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + + protected virtual bool FailCondition(ScoreProcessor scoreProcessor, JudgementResult result) => scoreProcessor.Combo.Value == 0 && result.Judgement.AffectsCombo; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeAdjust.cs b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs new file mode 100644 index 0000000000..513883f552 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModTimeAdjust.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio; +using osu.Framework.Timing; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModTimeAdjust : Mod + { + public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; + + protected abstract double RateAdjust { get; } + + public virtual void ApplyToClock(IAdjustableClock clock) + { + if (clock is IHasTempoAdjust tempo) + tempo.TempoAdjust *= RateAdjust; + else + clock.Rate *= RateAdjust; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs new file mode 100644 index 0000000000..a5f96087c0 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Audio; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Mods +{ + public abstract class ModTimeRamp : Mod + { + public override Type[] IncompatibleMods => new[] { typeof(ModTimeAdjust) }; + + protected abstract double FinalRateAdjustment { get; } + } + + public abstract class ModTimeRamp : ModTimeRamp, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap + where T : HitObject + { + private double finalRateTime; + + private double beginRampTime; + + private IAdjustableClock clock; + + /// + /// The point in the beatmap at which the final ramping rate should be reached. + /// + private const double final_rate_progress = 0.75f; + + public virtual void ApplyToClock(IAdjustableClock clock) + { + this.clock = clock; + + lastAdjust = 1; + + // for preview purposes. during gameplay, Update will overwrite this setting. + applyAdjustment(1); + } + + public virtual void ApplyToBeatmap(Beatmap beatmap) + { + HitObject lastObject = beatmap.HitObjects.LastOrDefault(); + + beginRampTime = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; + finalRateTime = final_rate_progress * ((lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0); + } + + public virtual void Update(Playfield playfield) + { + applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime); + } + + private double lastAdjust = 1; + + /// + /// Adjust the rate along the specified ramp + /// + /// The amount of adjustment to apply (from 0..1). + private void applyAdjustment(double amount) + { + double adjust = 1 + (Math.Sign(FinalRateAdjustment) * MathHelper.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); + + switch (clock) + { + case IHasPitchAdjust pitch: + pitch.PitchAdjust /= lastAdjust; + pitch.PitchAdjust *= adjust; + break; + + case IHasTempoAdjust tempo: + tempo.TempoAdjust /= lastAdjust; + tempo.TempoAdjust *= adjust; + break; + + default: + clock.Rate /= lastAdjust; + clock.Rate *= adjust; + break; + } + + lastAdjust = adjust; + } + } +} diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index 9c962bbcd5..cd649728cf 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Mods { diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs new file mode 100644 index 0000000000..5d71c8950b --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mods +{ + public class ModWindDown : ModTimeRamp + where T : HitObject + { + public override string Name => "Wind Down"; + public override string Acronym => "WD"; + public override string Description => "Sloooow doooown..."; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleDown; + public override double ScoreMultiplier => 1.0; + + protected override double FinalRateAdjustment => -0.25; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); + } +} diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs new file mode 100644 index 0000000000..aae85cec19 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Objects; + +namespace osu.Game.Rulesets.Mods +{ + public class ModWindUp : ModTimeRamp + where T : HitObject + { + public override string Name => "Wind Up"; + public override string Acronym => "WU"; + public override string Description => "Can you keep up?"; + public override IconUsage Icon => FontAwesome.Solid.ChevronCircleUp; + public override double ScoreMultiplier => 1.0; + + protected override double FinalRateAdjustment => 0.5; + + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); + } +} diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index b65773e93f..f7d574d3c7 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods public class MultiMod : Mod { public override string Name => string.Empty; - public override string ShortenedName => string.Empty; + public override string Acronym => string.Empty; public override string Description => string.Empty; public override double ScoreMultiplier => 0; diff --git a/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs b/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs index 9a72c44d67..b2d9f50602 100644 --- a/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs +++ b/osu.Game/Rulesets/Objects/Drawables/ArmedState.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Drawables { diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 5490e75c14..1f6ca4dd73 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Game.Audio; using osu.Game.Graphics; @@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Objects.Drawables protected SkinnableSound Samples; - protected virtual IEnumerable GetSamples() => HitObject.Samples; + protected virtual IEnumerable GetSamples() => HitObject.Samples; private readonly Lazy> nestedHitObjects = new Lazy>(); public IEnumerable NestedHitObjects => nestedHitObjects.IsValueCreated ? nestedHitObjects.Value : Enumerable.Empty(); @@ -58,7 +59,7 @@ namespace osu.Game.Rulesets.Objects.Drawables public bool AllJudged => Judged && NestedHitObjects.All(h => h.AllJudged); /// - /// Whether this has been hit. This occurs if is . + /// Whether this has been hit. This occurs if is hit. /// Note: This does NOT include nested hitobjects. /// public bool IsHit => Result?.IsHit ?? false; @@ -76,14 +77,12 @@ namespace osu.Game.Rulesets.Objects.Drawables private bool judgementOccurred; - public bool Interactive = true; - public override bool HandleNonPositionalInput => Interactive; - public override bool HandlePositionalInput => Interactive; - public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; + public override bool IsPresent => base.IsPresent || (State.Value == ArmedState.Idle && Clock?.CurrentTime >= LifetimeStart); + public readonly Bindable State = new Bindable(); protected DrawableHitObject(HitObject hitObject) @@ -95,6 +94,7 @@ namespace osu.Game.Rulesets.Objects.Drawables private void load() { var judgement = HitObject.CreateJudgement(); + if (judgement != null) { Result = CreateResult(judgement); @@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Objects.Drawables var samples = GetSamples().ToArray(); - if (samples.Any()) + if (samples.Length > 0) { if (HitObject.SampleControlPoint == null) throw new ArgumentNullException(nameof(HitObject.SampleControlPoint), $"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}." @@ -118,18 +118,20 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + protected override void ClearInternal(bool disposeChildren = true) => throw new InvalidOperationException($"Should never clear a {nameof(DrawableHitObject)}"); + protected override void LoadComplete() { base.LoadComplete(); - State.ValueChanged += state => + State.ValueChanged += armed => { - UpdateState(state); + UpdateState(armed.NewValue); // apply any custom state overrides - ApplyCustomUpdateState?.Invoke(this, state); + ApplyCustomUpdateState?.Invoke(this, armed.NewValue); - if (State == ArmedState.Hit) + if (armed.NewValue == ArmedState.Hit) PlaySamples(); }; @@ -145,6 +147,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// /// Plays all the hit sounds for this . + /// This is invoked automatically when this is hit. /// public void PlaySamples() => Samples?.Play(); @@ -160,6 +163,7 @@ namespace osu.Game.Rulesets.Objects.Drawables { OnRevertResult?.Invoke(this, Result); + Result.TimeOffset = 0; Result.Type = HitResult.None; State.Value = ArmedState.Idle; } @@ -206,9 +210,11 @@ namespace osu.Game.Rulesets.Objects.Drawables { case HitResult.None: break; + case HitResult.Miss: State.Value = ArmedState.Miss; break; + default: State.Value = ArmedState.Hit; break; @@ -217,6 +223,16 @@ namespace osu.Game.Rulesets.Objects.Drawables OnNewResult?.Invoke(this, Result); } + /// + /// Will called at least once after the of this has been passed. + /// + internal void OnLifetimeEnd() + { + foreach (var nested in NestedHitObjects) + nested.OnLifetimeEnd(); + UpdateResult(false); + } + /// /// Processes this , checking if a scoring result has occurred. /// @@ -224,6 +240,10 @@ namespace osu.Game.Rulesets.Objects.Drawables /// Whether a scoring result has occurred from this or any nested . protected bool UpdateResult(bool userTriggered) { + // It's possible for input to get into a bad state when rewinding gameplay, so results should not be processed + if (Time.Elapsed < 0) + return false; + judgementOccurred = false; if (AllJudged) diff --git a/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs b/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs index e10b50e672..8f4c95c634 100644 --- a/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs +++ b/osu.Game/Rulesets/Objects/Drawables/IDrawableHitObjectWithProxiedApproach.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; diff --git a/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs index ce365f04bd..48fcfabc2f 100644 --- a/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/IScrollingHitObject.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Objects.Drawables diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 67a3db7a00..bf04963b76 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using Newtonsoft.Json; @@ -19,12 +19,17 @@ namespace osu.Game.Rulesets.Objects /// public class HitObject { + /// + /// A small adjustment to the start time of control points to account for rounding/precision errors. + /// + private const double control_point_leniency = 1; + /// /// The time at which the HitObject starts. /// public virtual double StartTime { get; set; } - private List samples; + private List samples; /// /// The samples to be played when this hit object is hit. @@ -33,9 +38,9 @@ namespace osu.Game.Rulesets.Objects /// and can be treated as the default samples for the hit object. /// /// - public List Samples + public List Samples { - get => samples ?? (samples = new List()); + get => samples ?? (samples = new List()); set => samples = value; } @@ -48,8 +53,6 @@ namespace osu.Game.Rulesets.Objects [JsonIgnore] public bool Kiai { get; private set; } - private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - /// /// The hit windows for this . /// @@ -69,6 +72,9 @@ namespace osu.Game.Rulesets.Objects { ApplyDefaultsToSelf(controlPointInfo, difficulty); + // This is done here since ApplyDefaultsToSelf may be used to determine the end time + SampleControlPoint = controlPointInfo.SamplePointAt(((this as IHasEndTime)?.EndTime ?? StartTime) + control_point_leniency); + nestedHitObjects.Clear(); CreateNestedHitObjects(); @@ -84,11 +90,7 @@ namespace osu.Game.Rulesets.Objects protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { - SampleControlPoint samplePoint = controlPointInfo.SamplePointAt(StartTime); - EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime); - - Kiai = effectPoint.KiaiMode; - SampleControlPoint = samplePoint; + Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode; if (HitWindows == null) HitWindows = CreateHitWindows(); @@ -111,7 +113,7 @@ namespace osu.Game.Rulesets.Objects /// Creates the for this . /// This can be null to indicate that the has no . /// - /// This will only be invoked if hasn't been set externally (e.g. from a . + /// This will only be invoked if hasn't been set externally (e.g. from a . /// /// protected virtual HitWindows CreateHitWindows() => new HitWindows(); diff --git a/osu.Game/Rulesets/Objects/HitObjectParser.cs b/osu.Game/Rulesets/Objects/HitObjectParser.cs index d44da4e16c..d3c29d90ce 100644 --- a/osu.Game/Rulesets/Objects/HitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/HitObjectParser.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects { diff --git a/osu.Game/Rulesets/Objects/HitWindows.cs b/osu.Game/Rulesets/Objects/HitWindows.cs index 3717209860..fe099aaee7 100644 --- a/osu.Game/Rulesets/Objects/HitWindows.cs +++ b/osu.Game/Rulesets/Objects/HitWindows.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Objects private static readonly IReadOnlyDictionary base_ranges = new Dictionary { { HitResult.Perfect, (44.8, 38.8, 27.8) }, - { HitResult.Great, (128, 98, 68 ) }, + { HitResult.Great, (128, 98, 68) }, { HitResult.Good, (194, 164, 134) }, { HitResult.Ok, (254, 224, 194) }, { HitResult.Meh, (302, 272, 242) }, @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Objects /// /// Hit window for a result. - /// The user can only achieve receive this result if is true. /// public double Perfect { get; protected set; } @@ -38,7 +37,6 @@ namespace osu.Game.Rulesets.Objects /// /// Hit window for an result. - /// The user can only achieve this result if is true. /// public double Ok { get; protected set; } @@ -53,14 +51,37 @@ namespace osu.Game.Rulesets.Objects public double Miss { get; protected set; } /// - /// Whether it's possible to achieve a result. + /// Retrieves the with the largest hit window that produces a successful hit. /// - public bool AllowsPerfect; + /// The lowest allowed successful . + protected HitResult LowestSuccessfulHitResult() + { + for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) + { + if (IsHitResultAllowed(result)) + return result; + } + + return HitResult.None; + } /// - /// Whether it's possible to achieve a result. + /// Check whether it is possible to achieve the provided . /// - public bool AllowsOk; + /// The result type to check. + /// Whether the can be achieved. + public virtual bool IsHitResultAllowed(HitResult result) + { + switch (result) + { + case HitResult.Perfect: + case HitResult.Ok: + return false; + + default: + return true; + } + } /// /// Sets hit windows with values that correspond to a difficulty parameter. @@ -85,18 +106,11 @@ namespace osu.Game.Rulesets.Objects { timeOffset = Math.Abs(timeOffset); - if (AllowsPerfect && timeOffset <= HalfWindowFor(HitResult.Perfect)) - return HitResult.Perfect; - if (timeOffset <= HalfWindowFor(HitResult.Great)) - return HitResult.Great; - if (timeOffset <= HalfWindowFor(HitResult.Good)) - return HitResult.Good; - if (AllowsOk && timeOffset <= HalfWindowFor(HitResult.Ok)) - return HitResult.Ok; - if (timeOffset <= HalfWindowFor(HitResult.Meh)) - return HitResult.Meh; - if (timeOffset <= HalfWindowFor(HitResult.Miss)) - return HitResult.Miss; + for (var result = HitResult.Perfect; result >= HitResult.Miss; --result) + { + if (IsHitResultAllowed(result) && timeOffset <= HalfWindowFor(result)) + return result; + } return HitResult.None; } @@ -113,16 +127,22 @@ namespace osu.Game.Rulesets.Objects { case HitResult.Perfect: return Perfect / 2; + case HitResult.Great: return Great / 2; + case HitResult.Good: return Good / 2; + case HitResult.Ok: return Ok / 2; + case HitResult.Meh: return Meh / 2; + case HitResult.Miss: return Miss / 2; + default: throw new ArgumentException(nameof(result)); } @@ -130,10 +150,10 @@ namespace osu.Game.Rulesets.Objects /// /// Given a time offset, whether the can ever be hit in the future with a non- result. - /// This happens if is less than what is required for a result. + /// This happens if is less than what is required for . /// /// The time offset. /// Whether the can be hit at any point in the future from this time offset. - public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(HitResult.Meh); + public bool CanBeHit(double timeOffset) => timeOffset <= HalfWindowFor(LowestSuccessfulHitResult()); } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs index 0573a08361..febfd3696c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index e8962cd5ec..6e79d0b766 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Game.Audio; @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs index a187caaa26..56790629b4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSlider.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index db79ca60f1..0089d1eb88 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; @@ -8,12 +8,14 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasCombo + internal sealed class ConvertSpinner : HitObject, IHasEndTime, IHasXPosition, IHasCombo { public double EndTime { get; set; } public double Duration => EndTime - StartTime; + public float X => 256; // Required for CatchBeatmapConverter + public bool NewCombo { get; set; } public int ComboOffset { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 85eb29033e..f5b1cbcebf 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -1,11 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using osu.Game.Beatmaps.Formats; using osu.Game.Audio; @@ -46,9 +45,11 @@ namespace osu.Game.Rulesets.Objects.Legacy { string[] split = text.Split(','); - Vector2 pos = new Vector2((int)Convert.ToSingle(split[0], CultureInfo.InvariantCulture), (int)Convert.ToSingle(split[1], CultureInfo.InvariantCulture)); + Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE)); - ConvertHitObjectType type = (ConvertHitObjectType)int.Parse(split[3]); + double startTime = Parsing.ParseDouble(split[2]) + Offset; + + ConvertHitObjectType type = (ConvertHitObjectType)Parsing.ParseInt(split[3]); int comboOffset = (int)(type & ConvertHitObjectType.ComboOffset) >> 4; type &= ~ConvertHitObjectType.ComboOffset; @@ -56,7 +57,7 @@ namespace osu.Game.Rulesets.Objects.Legacy bool combo = type.HasFlag(ConvertHitObjectType.NewCombo); type &= ~ConvertHitObjectType.NewCombo; - var soundType = (LegacySoundType)int.Parse(split[4]); + var soundType = (LegacySoundType)Parsing.ParseInt(split[4]); var bankInfo = new SampleBankInfo(); HitObject result = null; @@ -83,6 +84,7 @@ namespace osu.Game.Rulesets.Objects.Legacy var points = new Vector2[pointCount]; int pointIndex = 1; + foreach (string t in pointSplit) { if (t.Length == 1) @@ -92,12 +94,15 @@ namespace osu.Game.Rulesets.Objects.Legacy case @"C": pathType = PathType.Catmull; break; + case @"B": pathType = PathType.Bezier; break; + case @"L": pathType = PathType.Linear; break; + case @"P": pathType = PathType.PerfectCurve; break; @@ -107,7 +112,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } string[] temp = t.Split(':'); - points[pointIndex++] = new Vector2((int)Convert.ToDouble(temp[0], CultureInfo.InvariantCulture), (int)Convert.ToDouble(temp[1], CultureInfo.InvariantCulture)) - pos; + points[pointIndex++] = new Vector2((int)Parsing.ParseDouble(temp[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseDouble(temp[1], Parsing.MAX_COORDINATE_VALUE)) - pos; } // osu-stable special-cased colinear perfect curves to a CurveType.Linear @@ -116,7 +121,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (points.Length == 3 && pathType == PathType.PerfectCurve && isLinear(points)) pathType = PathType.Linear; - int repeatCount = Convert.ToInt32(split[6], CultureInfo.InvariantCulture); + int repeatCount = Parsing.ParseInt(split[6]); if (repeatCount > 9000) throw new ArgumentOutOfRangeException(nameof(repeatCount), @"Repeat count is way too high"); @@ -124,9 +129,8 @@ namespace osu.Game.Rulesets.Objects.Legacy // osu-stable treated the first span of the slider as a repeat, but no repeats are happening repeatCount = Math.Max(0, repeatCount - 1); - if (split.Length > 7) - length = Convert.ToDouble(split[7], CultureInfo.InvariantCulture); + length = Math.Max(0, Parsing.ParseDouble(split[7])); if (split.Length > 10) readCustomSampleBanks(split[10], bankInfo); @@ -143,6 +147,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 9 && split[9].Length > 0) { string[] sets = split[9].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= sets.Length) @@ -162,6 +167,7 @@ namespace osu.Game.Rulesets.Objects.Legacy if (split.Length > 8 && split[8].Length > 0) { string[] adds = split[8].Split('|'); + for (int i = 0; i < nodes; i++) { if (i >= adds.Length) @@ -174,7 +180,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } // Generate the final per-node samples - var nodeSamples = new List>(nodes); + var nodeSamples = new List>(nodes); for (int i = 0; i < nodes; i++) nodeSamples.Add(convertSoundType(nodeSoundTypes[i], nodeBankInfos[i])); @@ -185,7 +191,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(ConvertHitObjectType.Spinner)) { - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, Convert.ToDouble(split[5], CultureInfo.InvariantCulture) + Offset); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -194,12 +202,12 @@ namespace osu.Game.Rulesets.Objects.Legacy { // Note: Hold is generated by BMS converts - double endTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture); + double endTime = Math.Max(startTime, Parsing.ParseDouble(split[2])); if (split.Length > 5 && !string.IsNullOrEmpty(split[5])) { string[] ss = split[5].Split(':'); - endTime = Convert.ToDouble(ss[0], CultureInfo.InvariantCulture); + endTime = Math.Max(startTime, Parsing.ParseDouble(ss[0])); readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } @@ -212,7 +220,7 @@ namespace osu.Game.Rulesets.Objects.Legacy return null; } - result.StartTime = Convert.ToDouble(split[2], CultureInfo.InvariantCulture) + Offset; + result.StartTime = startTime; if (result.Samples.Count == 0) result.Samples = convertSoundType(soundType, bankInfo); @@ -223,8 +231,14 @@ namespace osu.Game.Rulesets.Objects.Legacy } catch (FormatException) { - throw new FormatException("One or more hit objects were malformed."); + Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); } + catch (OverflowException) + { + Logger.Log("A hitobject could not be parsed correctly and will be ignored", LoggingTarget.Runtime, LogLevel.Important); + } + + return null; } private void readCustomSampleBanks(string str, SampleBankInfo bankInfo) @@ -234,8 +248,8 @@ namespace osu.Game.Rulesets.Objects.Legacy string[] split = str.Split(':'); - var bank = (LegacyBeatmapDecoder.LegacySampleBank)int.Parse(split[0]); - var addbank = (LegacyBeatmapDecoder.LegacySampleBank)int.Parse(split[1]); + var bank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[0]); + var addbank = (LegacyBeatmapDecoder.LegacySampleBank)Parsing.ParseInt(split[1]); string stringBank = bank.ToString().ToLowerInvariant(); if (stringBank == @"none") @@ -248,10 +262,10 @@ namespace osu.Game.Rulesets.Objects.Legacy bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank; if (split.Length > 2) - bankInfo.CustomSampleBank = int.Parse(split[2]); + bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]); if (split.Length > 3) - bankInfo.Volume = int.Parse(split[3]); + bankInfo.Volume = Math.Max(0, Parsing.ParseInt(split[3])); bankInfo.Filename = split.Length > 4 ? split[4] : null; } @@ -277,7 +291,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The slider repeat count. /// The samples to be played when the slider nodes are hit. This includes the head and tail of the slider. /// The hit object. - protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples); + protected abstract HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples); /// /// Creates a legacy Spinner-type hit object. @@ -298,18 +312,27 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The hold end time. protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime); - private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) + private List convertSoundType(LegacySoundType type, SampleBankInfo bankInfo) { // Todo: This should return the normal SampleInfos if the specified sample file isn't found, but that's a pretty edge-case scenario if (!string.IsNullOrEmpty(bankInfo.Filename)) - return new List { new FileSampleInfo { Filename = bankInfo.Filename } }; - - var soundTypes = new List { - new LegacySampleInfo + return new List + { + new FileHitSampleInfo + { + Filename = bankInfo.Filename, + Volume = bankInfo.Volume + } + }; + } + + var soundTypes = new List + { + new LegacyHitSampleInfo { Bank = bankInfo.Normal, - Name = SampleInfo.HIT_NORMAL, + Name = HitSampleInfo.HIT_NORMAL, Volume = bankInfo.Volume, CustomSampleBank = bankInfo.CustomSampleBank } @@ -317,10 +340,10 @@ namespace osu.Game.Rulesets.Objects.Legacy if (type.HasFlag(LegacySoundType.Finish)) { - soundTypes.Add(new LegacySampleInfo + soundTypes.Add(new LegacyHitSampleInfo { Bank = bankInfo.Add, - Name = SampleInfo.HIT_FINISH, + Name = HitSampleInfo.HIT_FINISH, Volume = bankInfo.Volume, CustomSampleBank = bankInfo.CustomSampleBank }); @@ -328,10 +351,10 @@ namespace osu.Game.Rulesets.Objects.Legacy if (type.HasFlag(LegacySoundType.Whistle)) { - soundTypes.Add(new LegacySampleInfo + soundTypes.Add(new LegacyHitSampleInfo { Bank = bankInfo.Add, - Name = SampleInfo.HIT_WHISTLE, + Name = HitSampleInfo.HIT_WHISTLE, Volume = bankInfo.Volume, CustomSampleBank = bankInfo.CustomSampleBank }); @@ -339,10 +362,10 @@ namespace osu.Game.Rulesets.Objects.Legacy if (type.HasFlag(LegacySoundType.Clap)) { - soundTypes.Add(new LegacySampleInfo + soundTypes.Add(new LegacyHitSampleInfo { Bank = bankInfo.Add, - Name = SampleInfo.HIT_CLAP, + Name = HitSampleInfo.HIT_CLAP, Volume = bankInfo.Volume, CustomSampleBank = bankInfo.CustomSampleBank }); @@ -364,7 +387,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } - private class LegacySampleInfo : SampleInfo + private class LegacyHitSampleInfo : HitSampleInfo { public int CustomSampleBank { @@ -376,7 +399,7 @@ namespace osu.Game.Rulesets.Objects.Legacy } } - private class FileSampleInfo : SampleInfo + private class FileHitSampleInfo : HitSampleInfo { public string Filename; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs index fa47e56de7..c9f7224643 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Objects.Legacy Slider = 1 << 1, NewCombo = 1 << 2, Spinner = 1 << 3, - ComboOffset = 1 << 4 | 1 << 5 | 1 << 6, + ComboOffset = (1 << 4) | (1 << 5) | (1 << 6), Hold = 1 << 7 } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 0512a97354..ff6b9be8b5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Distance => Path.Distance; - public List> NodeSamples { get; set; } + public List> NodeSamples { get; set; } public int RepeatCount { get; set; } public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs index cbc8d2d4df..06fde576d2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index b81fed9f03..b20a027e78 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Game.Audio; @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 86a10fd363..096c07f7d2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs index e1572889a3..226d91bb86 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSlider.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index 5a443c2ac2..eb20fa67f1 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs index c8d10816bd..84b66a4c26 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; using osuTK; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index f65452858a..0a4e38df02 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { newCombo |= forceNewCombo; comboOffset += extraComboOffset; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs index bceca0675e..c850feb189 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSlider.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; using osuTK; diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 49f27b01f6..e5a8884aa2 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; using osuTK; diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs index 66e504bf22..5cecc2a59f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHit.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Legacy.Taiko { diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index 16ed49ce2b..7c1514c1eb 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Game.Rulesets.Objects.Types; @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko return new ConvertHit(); } - protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) + protected override HitObject CreateSlider(Vector2 position, bool newCombo, int comboOffset, Vector2[] controlPoints, double length, PathType pathType, int repeatCount, List> nodeSamples) { return new ConvertSlider { diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs index 11c0a2baae..5cedc6e2e5 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSlider.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Legacy.Taiko { diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 4c8807a1d3..ca9fdd53ed 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs new file mode 100644 index 0000000000..7cda7485f9 --- /dev/null +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -0,0 +1,148 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osuTK; + +namespace osu.Game.Rulesets.Objects +{ + public static class SliderEventGenerator + { + public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) + { + // A very lenient maximum length of a slider for ticks to be generated. + // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. + const double max_length = 100000; + + var length = Math.Min(max_length, totalDistance); + tickDistance = MathHelper.Clamp(tickDistance, 0, length); + + var minDistanceFromEnd = velocity * 10; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Head, + SpanIndex = 0, + SpanStartTime = startTime, + Time = startTime, + PathProgress = 0, + }; + + if (tickDistance != 0) + { + for (var span = 0; span < spanCount; span++) + { + var spanStartTime = startTime + span * spanDuration; + var reversed = span % 2 == 1; + + for (var d = tickDistance; d <= length; d += tickDistance) + { + if (d >= length - minDistanceFromEnd) + break; + + var pathProgress = d / length; + var timeProgress = reversed ? 1 - pathProgress : pathProgress; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Tick, + SpanIndex = span, + SpanStartTime = spanStartTime, + Time = spanStartTime + timeProgress * spanDuration, + PathProgress = pathProgress, + }; + } + + if (span < spanCount - 1) + { + yield return new SliderEventDescriptor + { + Type = SliderEventType.Repeat, + SpanIndex = span, + SpanStartTime = startTime + span * spanDuration, + Time = spanStartTime + spanDuration, + PathProgress = (span + 1) % 2, + }; + } + } + } + + double totalDuration = spanCount * spanDuration; + + // Okay, I'll level with you. I made a mistake. It was 2007. + // Times were simpler. osu! was but in its infancy and sliders were a new concept. + // A hack was made, which has unfortunately lived through until this day. + // + // This legacy tick is used for some calculations and judgements where audio output is not required. + // Generally we are keeping this around just for difficulty compatibility. + // Optimistically we do not want to ever use this for anything user-facing going forwards. + + int finalSpanIndex = spanCount - 1; + double finalSpanStartTime = startTime + finalSpanIndex * spanDuration; + double finalSpanEndTime = Math.Max(startTime + totalDuration / 2, (finalSpanStartTime + spanDuration) - (legacyLastTickOffset ?? 0)); + double finalProgress = (finalSpanEndTime - finalSpanStartTime) / spanDuration; + + if (spanCount % 2 == 0) finalProgress = 1 - finalProgress; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.LegacyLastTick, + SpanIndex = finalSpanIndex, + SpanStartTime = finalSpanStartTime, + Time = finalSpanEndTime, + PathProgress = finalProgress, + }; + + yield return new SliderEventDescriptor + { + Type = SliderEventType.Tail, + SpanIndex = finalSpanIndex, + SpanStartTime = startTime + (spanCount - 1) * spanDuration, + Time = startTime + totalDuration, + PathProgress = spanCount % 2, + }; + } + } + + /// + /// Describes a point in time on a slider given special meaning. + /// Should be used by rulesets to visualise the slider. + /// + public struct SliderEventDescriptor + { + /// + /// The type of event. + /// + public SliderEventType Type; + + /// + /// The time of this event. + /// + public double Time; + + /// + /// The zero-based index of the span. In the case of repeat sliders, this will increase after each . + /// + public int SpanIndex; + + /// + /// The time at which the contained begins. + /// + public double SpanStartTime; + + /// + /// The progress along the slider's at which this event occurs. + /// + public double PathProgress; + } + + public enum SliderEventType + { + Tick, + LegacyLastTick, + Head, + Tail, + Repeat + } +} diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index fe38fd4f65..bc9571c85d 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -95,6 +95,7 @@ namespace osu.Game.Rulesets.Objects path.Clear(); int i = 0; + for (; i < calculatedPath.Count && cumulativeLength[i] < d0; ++i) { } @@ -125,6 +126,7 @@ namespace osu.Game.Rulesets.Objects { if (isInitialised) return; + isInitialised = true; controlPoints = controlPoints ?? Array.Empty(); @@ -141,6 +143,7 @@ namespace osu.Game.Rulesets.Objects { case PathType.Linear: return PathApproximator.ApproximateLinear(subControlPoints); + case PathType.PerfectCurve: //we can only use CircularArc iff we have exactly three control points and no dissection. if (ControlPoints.Length != 3 || subControlPoints.Length != 3) @@ -154,6 +157,7 @@ namespace osu.Game.Rulesets.Objects break; return subpath; + case PathType.Catmull: return PathApproximator.ApproximateCatmull(subControlPoints); } @@ -276,11 +280,5 @@ namespace osu.Game.Rulesets.Objects return ControlPoints.SequenceEqual(other.ControlPoints) && ExpectedDistance.Equals(other.ExpectedDistance) && Type == other.Type; } - - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is SliderPath other && Equals(other); - } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs index 95f1a1cb3d..d1a4683a1d 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCombo.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCombo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs index a3dded94ec..e07da93a3a 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasComboInformation.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index a6e2c15f5a..e98a888bd7 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; diff --git a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs index 535eb21ff4..e7f552115e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 8383e879a3..516f1002a4 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasHold.cs b/osu.Game/Rulesets/Objects/Types/IHasHold.cs index 429c582bab..469b8b7892 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasHold.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasHold.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs b/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs index ab2573e933..caf22c3023 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasLegacyLastTickOffset.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs index d14e4cc1fb..8948fe59a9 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPosition.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index ea8784db47..697adeda98 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Audio; using System.Collections.Generic; @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Objects.Types /// n-1: The last repeat.
/// n: The last node. ///
- List> NodeSamples { get; } + List> NodeSamples { get; } } public static class HasRepeatsExtensions diff --git a/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs index 49b1d7cb28..7e55b21050 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasXPosition.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs index 8ec7ab39fe..d2561b10a7 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasYPosition.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Objects/Types/PathType.cs b/osu.Game/Rulesets/Objects/Types/PathType.cs index 5156302fe1..923ce9eba4 100644 --- a/osu.Game/Rulesets/Objects/Types/PathType.cs +++ b/osu.Game/Rulesets/Objects/Types/PathType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Objects.Types { diff --git a/osu.Game/Rulesets/Replays/AutoGenerator.cs b/osu.Game/Rulesets/Replays/AutoGenerator.cs index 3ac0c3297c..1d4cdbf04c 100644 --- a/osu.Game/Rulesets/Replays/AutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/AutoGenerator.cs @@ -1,13 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Objects; using osu.Game.Beatmaps; +using osu.Game.Replays; namespace osu.Game.Rulesets.Replays { - public abstract class AutoGenerator : IAutoGenerator - where T : HitObject + public abstract class AutoGenerator : IAutoGenerator { /// /// Creates the auto replay and returns it. @@ -20,11 +19,11 @@ namespace osu.Game.Rulesets.Replays /// /// The beatmap we're making. /// - protected Beatmap Beatmap; + protected IBeatmap Beatmap; #endregion - protected AutoGenerator(Beatmap beatmap) + protected AutoGenerator(IBeatmap beatmap) { Beatmap = beatmap; } diff --git a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs index b10c6e1f15..3830fa5cbe 100644 --- a/osu.Game/Rulesets/Replays/FramedReplayInputHandler.cs +++ b/osu.Game/Rulesets/Replays/FramedReplayInputHandler.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Input.StateChanges; using osu.Game.Input.Handlers; +using osu.Game.Replays; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Replays { @@ -21,12 +22,37 @@ namespace osu.Game.Rulesets.Replays protected List Frames => replay.Frames; - public TFrame CurrentFrame => !HasFrames ? null : (TFrame)Frames[currentFrameIndex]; - public TFrame NextFrame => !HasFrames ? null : (TFrame)Frames[nextFrameIndex]; + public TFrame CurrentFrame + { + get + { + if (!HasFrames || !currentFrameIndex.HasValue) + return null; - private int currentFrameIndex; + return (TFrame)Frames[currentFrameIndex.Value]; + } + } - private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1); + public TFrame NextFrame + { + get + { + if (!HasFrames) + return null; + + if (!currentFrameIndex.HasValue) + return (TFrame)Frames[0]; + + if (currentDirection > 0) + return currentFrameIndex == Frames.Count - 1 ? null : (TFrame)Frames[currentFrameIndex.Value + 1]; + else + return currentFrameIndex == 0 ? null : (TFrame)Frames[nextFrameIndex]; + } + } + + private int? currentFrameIndex; + + private int nextFrameIndex => currentFrameIndex.HasValue ? MathHelper.Clamp(currentFrameIndex.Value + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1) : 0; protected FramedReplayInputHandler(Replay replay) { @@ -46,12 +72,12 @@ namespace osu.Game.Rulesets.Replays public override List GetPendingInputs() => new List(); - public bool AtLastFrame => currentFrameIndex == Frames.Count - 1; - public bool AtFirstFrame => currentFrameIndex == 0; - private const double sixty_frame_time = 1000.0 / 60; - protected double CurrentTime { get; private set; } + protected virtual double AllowedImportantTimeSpan => sixty_frame_time * 1.2; + + protected double? CurrentTime { get; private set; } + private int currentDirection; /// @@ -62,14 +88,24 @@ namespace osu.Game.Rulesets.Replays protected bool HasFrames => Frames.Count > 0; - private bool inImportantSection => - HasFrames && FrameAccuratePlayback && - //a button is in a pressed state - IsImportant(currentDirection > 0 ? CurrentFrame : NextFrame) && - //the next frame is within an allowable time span - Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= sixty_frame_time * 1.2; + private bool inImportantSection + { + get + { + if (!HasFrames || !FrameAccuratePlayback) + return false; - protected virtual bool IsImportant(TFrame frame) => false; + var frame = currentDirection > 0 ? CurrentFrame : NextFrame; + + if (frame == null) + return false; + + return IsImportant(frame) && //a button is in a pressed state + Math.Abs(CurrentTime - NextFrame?.Time ?? 0) <= AllowedImportantTimeSpan; //the next frame is within an allowable time span + } + } + + protected virtual bool IsImportant([NotNull] TFrame frame) => false; /// /// Update the current frame based on an incoming time value. @@ -80,46 +116,36 @@ namespace osu.Game.Rulesets.Replays /// The usable time value. If null, we should not advance time as we do not have enough data. public override double? SetFrameFromTime(double time) { - currentDirection = time.CompareTo(CurrentTime); - if (currentDirection == 0) currentDirection = 1; + if (!CurrentTime.HasValue) + { + currentDirection = 1; + } + else + { + currentDirection = time.CompareTo(CurrentTime); + if (currentDirection == 0) currentDirection = 1; + } if (HasFrames) { - // check if the next frame is in the "future" for the current playback direction - if (currentDirection != time.CompareTo(NextFrame.Time)) + // check if the next frame is valid for the current playback direction. + // validity is if the next frame is equal or "earlier" + var compare = time.CompareTo(NextFrame?.Time); + + if (compare == 0 || compare == currentDirection) + { + if (advanceFrame()) + return CurrentTime = CurrentFrame.Time; + } + else { // if we didn't change frames, we need to ensure we are allowed to run frames in between, else return null. if (inImportantSection) return null; } - else if (advanceFrame()) - { - // If going backwards, we need to execute once _before_ the frame time to reverse any judgements - // that would occur as a result of this frame in forward playback - if (currentDirection == -1) - return CurrentTime = CurrentFrame.Time - 1; - return CurrentTime = CurrentFrame.Time; - } } return CurrentTime = time; } - - protected class ReplayMouseState : osu.Framework.Input.States.MouseState - { - public ReplayMouseState(Vector2 position) - { - Position = position; - } - } - - protected class ReplayKeyboardState : osu.Framework.Input.States.KeyboardState - { - public ReplayKeyboardState(List keys) - { - foreach (var key in keys) - Keys.Add(key); - } - } } } diff --git a/osu.Game/Rulesets/Replays/IAutoGenerator.cs b/osu.Game/Rulesets/Replays/IAutoGenerator.cs index 4ef5f16f39..b1905e2b6f 100644 --- a/osu.Game/Rulesets/Replays/IAutoGenerator.cs +++ b/osu.Game/Rulesets/Replays/IAutoGenerator.cs @@ -1,5 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Replays; namespace osu.Game.Rulesets.Replays { diff --git a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs b/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs deleted file mode 100644 index ee09414287..0000000000 --- a/osu.Game/Rulesets/Replays/Legacy/ReplayButtonState.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System; - -namespace osu.Game.Rulesets.Replays.Legacy -{ - [Flags] - public enum ReplayButtonState - { - None = 0, - Left1 = 1, - Right1 = 2, - Left2 = 4, - Right2 = 8, - Smoke = 16 - } -} diff --git a/osu.Game/Rulesets/Replays/Replay.cs b/osu.Game/Rulesets/Replays/Replay.cs deleted file mode 100644 index 8fbe4c8194..0000000000 --- a/osu.Game/Rulesets/Replays/Replay.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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.Game.Users; - -namespace osu.Game.Rulesets.Replays -{ - public class Replay - { - public User User; - public List Frames = new List(); - } -} diff --git a/osu.Game/Rulesets/Replays/ReplayFrame.cs b/osu.Game/Rulesets/Replays/ReplayFrame.cs index fedab8053f..85e068ae79 100644 --- a/osu.Game/Rulesets/Replays/ReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/ReplayFrame.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.Replays { diff --git a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs index fdd528f296..7ecdc0715b 100644 --- a/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs +++ b/osu.Game/Rulesets/Replays/Types/IConvertibleReplayFrame.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; -using osu.Game.Rulesets.Replays.Legacy; +using osu.Game.Replays.Legacy; namespace osu.Game.Rulesets.Replays.Types { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 82d9945ef7..42b1322cae 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -1,23 +1,23 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays.Types; -using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Beatmaps.Legacy; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; +using osu.Game.Scoring; namespace osu.Game.Rulesets { @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets /// An enumerable of constructed s public virtual IEnumerable ConvertLegacyMods(LegacyMods mods) => new Mod[] { }; - public Mod GetAutoplayMod() => GetAllMods().First(mod => mod is ModAutoplay); + public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); protected Ruleset(RulesetInfo rulesetInfo = null) { @@ -53,9 +53,10 @@ namespace osu.Game.Rulesets /// Attempt to create a hit renderer for a beatmap /// /// The beatmap to create the hit renderer for. + /// The s to apply. /// Unable to successfully load the beatmap to be usable with this ruleset. /// - public abstract RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap); + public abstract DrawableRuleset CreateDrawableRulesetWith(WorkingBeatmap beatmap, IReadOnlyList mods); /// /// Creates a to convert a to one that is applicable for this . @@ -73,11 +74,11 @@ namespace osu.Game.Rulesets public abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); - public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => null; + public virtual PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => null; public virtual HitObjectComposer CreateHitObjectComposer() => null; - public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_question_circle }; + public virtual Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.QuestionCircle }; public abstract string Description { get; } diff --git a/osu.Game/Rulesets/RulesetConfigCache.cs b/osu.Game/Rulesets/RulesetConfigCache.cs index 7e83ba0961..9a5a4d4acd 100644 --- a/osu.Game/Rulesets/RulesetConfigCache.cs +++ b/osu.Game/Rulesets/RulesetConfigCache.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Configuration; @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets /// public class RulesetConfigCache : Component { - private readonly Dictionary configCache = new Dictionary(); + private readonly ConcurrentDictionary configCache = new ConcurrentDictionary(); private readonly SettingsStore settingsStore; public RulesetConfigCache(SettingsStore settingsStore) @@ -34,10 +34,7 @@ namespace osu.Game.Rulesets if (ruleset.RulesetInfo.ID == null) throw new InvalidOperationException("The provided ruleset doesn't have a valid id."); - if (configCache.TryGetValue(ruleset.RulesetInfo.ID.Value, out var existing)) - return existing; - - return configCache[ruleset.RulesetInfo.ID.Value] = ruleset.CreateConfig(settingsStore); + return configCache.GetOrAdd(ruleset.RulesetInfo.ID.Value, _ => ruleset.CreateConfig(settingsStore)); } } } diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index a6a311f6eb..ca331ec339 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -1,16 +1,13 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; namespace osu.Game.Rulesets { public class RulesetInfo : IEquatable { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] - [JsonIgnore] public int? ID { get; set; } public string Name { get; set; } diff --git a/osu.Game/Rulesets/RulesetSelector.cs b/osu.Game/Rulesets/RulesetSelector.cs new file mode 100644 index 0000000000..8e6ec556d2 --- /dev/null +++ b/osu.Game/Rulesets/RulesetSelector.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Allocation; + +namespace osu.Game.Rulesets +{ + public abstract class RulesetSelector : TabControl + { + [Resolved] + protected RulesetStore Rulesets { get; private set; } + + protected override Dropdown CreateDropdown() => null; + + [BackgroundDependencyLoader] + private void load() + { + foreach (var r in Rulesets.AvailableRulesets) + AddItem(r); + } + } +} diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 513173ef2f..0ebadd73d2 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -22,7 +22,8 @@ namespace osu.Game.Rulesets { AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve; - foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll")) + foreach (string file in Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll") + .Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); } @@ -124,7 +125,7 @@ namespace osu.Game.Rulesets } catch (Exception e) { - Logger.Error(e, "Failed to load ruleset"); + Logger.Error(e, $"Failed to load ruleset {filename}"); } } } diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 98874a4890..2376f12e9e 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; diff --git a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs deleted file mode 100644 index a90cd79186..0000000000 --- a/osu.Game/Rulesets/Scoring/Legacy/LegacyScoreParser.cs +++ /dev/null @@ -1,189 +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.IO; -using osu.Game.Beatmaps; -using osu.Game.IO.Legacy; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Replays.Legacy; -using osu.Game.Users; -using SharpCompress.Compressors.LZMA; -using osu.Game.Beatmaps.Legacy; -using System.Linq; - -namespace osu.Game.Rulesets.Scoring.Legacy -{ - public abstract class LegacyScoreParser - { - private IBeatmap currentBeatmap; - private Ruleset currentRuleset; - - public Score Parse(Stream stream) - { - Score score; - - using (SerializationReader sr = new SerializationReader(stream)) - { - currentRuleset = GetRuleset(sr.ReadByte()); - score = new Score { Ruleset = currentRuleset.RulesetInfo }; - - /* score.Pass = true;*/ - var version = sr.ReadInt32(); - - /* score.FileChecksum = */ - currentBeatmap = GetBeatmap(sr.ReadString()).Beatmap; - score.Beatmap = currentBeatmap.BeatmapInfo; - - /* score.PlayerName = */ - score.User = new User { Username = sr.ReadString() }; - /* var localScoreChecksum = */ - sr.ReadString(); - - var count300 = sr.ReadUInt16(); - var count100 = sr.ReadUInt16(); - var count50 = sr.ReadUInt16(); - var countGeki = sr.ReadUInt16(); - var countKatu = sr.ReadUInt16(); - var countMiss = sr.ReadUInt16(); - - score.Statistics[HitResult.Great] = count300; - score.Statistics[HitResult.Good] = count100; - score.Statistics[HitResult.Meh] = count50; - score.Statistics[HitResult.Perfect] = countGeki; - score.Statistics[HitResult.Ok] = countKatu; - score.Statistics[HitResult.Miss] = countMiss; - - score.TotalScore = sr.ReadInt32(); - score.MaxCombo = sr.ReadUInt16(); - /* score.Perfect = */ - sr.ReadBoolean(); - /* score.EnabledMods = (Mods)*/ - score.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); - /* score.HpGraphString = */ - sr.ReadString(); - /* score.Date = */ - sr.ReadDateTime(); - - var compressedReplay = sr.ReadByteArray(); - - if (version >= 20140721) - /*OnlineId =*/ - sr.ReadInt64(); - else if (version >= 20121008) - /*OnlineId =*/ - sr.ReadInt32(); - - switch (score.Ruleset.ID) - { - case 0: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; - break; - } - case 1: - { - int totalHits = count50 + count100 + count300 + countMiss; - score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; - break; - } - case 2: - { - int totalHits = count50 + count100 + count300 + countMiss + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300 ) / totalHits : 1; - break; - } - case 3: - { - int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; - score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; - break; - } - } - - using (var replayInStream = new MemoryStream(compressedReplay)) - { - byte[] properties = new byte[5]; - if (replayInStream.Read(properties, 0, 5) != 5) - throw new IOException("input .lzma is too short"); - long outSize = 0; - for (int i = 0; i < 8; i++) - { - int v = replayInStream.ReadByte(); - if (v < 0) - throw new IOException("Can't Read 1"); - outSize |= (long)(byte)v << (8 * i); - } - - long compressedSize = replayInStream.Length - replayInStream.Position; - - using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) - using (var reader = new StreamReader(lzma)) - { - score.Replay = new Replay { User = score.User }; - readLegacyReplay(score.Replay, reader); - } - } - } - - return score; - } - - private void readLegacyReplay(Replay replay, StreamReader reader) - { - float lastTime = 0; - - foreach (var l in reader.ReadToEnd().Split(',')) - { - var split = l.Split('|'); - - if (split.Length < 4) - continue; - - if (split[0] == "-12345") - { - // Todo: The seed is provided in split[3], which we'll need to use at some point - continue; - } - - var diff = float.Parse(split[0]); - lastTime += diff; - - // Todo: At some point we probably want to rewind and play back the negative-time frames - // but for now we'll achieve equal playback to stable by skipping negative frames - if (diff < 0) - continue; - - replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, float.Parse(split[1]), float.Parse(split[2]), (ReplayButtonState)int.Parse(split[3])))); - } - } - - private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) - { - var convertible = currentRuleset.CreateConvertibleReplayFrame(); - if (convertible == null) - throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); - convertible.ConvertFrom(legacyFrame, currentBeatmap); - - var frame = (ReplayFrame)convertible; - frame.Time = legacyFrame.Time; - - return frame; - } - - /// - /// Retrieves the for a specific id. - /// - /// The id. - /// The . - protected abstract Ruleset GetRuleset(int rulesetId); - - /// - /// Retrieves the corresponding to an MD5 hash. - /// - /// The MD5 hash. - /// The . - protected abstract WorkingBeatmap GetBeatmap(string md5Hash); - } -} diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs deleted file mode 100644 index 02f528791a..0000000000 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ /dev/null @@ -1,47 +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 Newtonsoft.Json; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Users; -using osu.Game.Rulesets.Replays; - -namespace osu.Game.Rulesets.Scoring -{ - public class Score - { - public ScoreRank Rank { get; set; } - - public double TotalScore { get; set; } - - public double Accuracy { get; set; } - - public double Health { get; set; } = 1; - - public double? PP { get; set; } - - public int MaxCombo { get; set; } - - public int Combo { get; set; } - - public RulesetInfo Ruleset { get; set; } - - public Mod[] Mods { get; set; } = { }; - - public User User; - - [JsonIgnore] - public Replay Replay; - - public BeatmapInfo Beatmap; - - public long OnlineScoreID; - - public DateTimeOffset Date; - - public Dictionary Statistics = new Dictionary(); - } -} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b0cea7009e..47ce28db4c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -1,14 +1,20 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; -using osu.Framework.Configuration; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.MathUtils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring { @@ -34,7 +40,7 @@ namespace osu.Game.Rulesets.Scoring /// /// Additional conditions on top of that cause a failing state. /// - public event Func FailConditions; + public event Func FailConditions; /// /// The current total score. @@ -56,6 +62,16 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableInt Combo = new BindableInt(); + /// + /// The current selected mods + /// + public readonly Bindable> Mods = new Bindable>(Array.Empty()); + + /// + /// Create a for this processor. + /// + public virtual HitWindows CreateHitWindows() => new HitWindows(); + /// /// The current rank. /// @@ -84,12 +100,17 @@ namespace osu.Game.Rulesets.Scoring /// /// The default conditions for failing. /// - protected virtual bool DefaultFailCondition => Health.Value == Health.MinValue; + protected virtual bool DefaultFailCondition => Precision.AlmostBigger(Health.MinValue, Health.Value); protected ScoreProcessor() { Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); }; - Accuracy.ValueChanged += delegate { Rank.Value = rankFrom(Accuracy.Value); }; + Accuracy.ValueChanged += delegate + { + Rank.Value = rankFrom(Accuracy.Value); + foreach (var mod in Mods.Value.OfType()) + Rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); + }; } private ScoreRank rankFrom(double acc) @@ -104,6 +125,7 @@ namespace osu.Game.Rulesets.Scoring return ScoreRank.B; if (acc > 0.7) return ScoreRank.C; + return ScoreRank.D; } @@ -129,12 +151,12 @@ namespace osu.Game.Rulesets.Scoring /// This can only ever notify subscribers once. /// /// - protected void UpdateFailed() + protected void UpdateFailed(JudgementResult result) { if (HasFailed) return; - if (!DefaultFailCondition && FailConditions?.Invoke(this) != true) + if (!DefaultFailCondition && FailConditions?.Invoke(this, result) != true) return; if (Failed?.Invoke() != false) @@ -144,7 +166,6 @@ namespace osu.Game.Rulesets.Scoring /// /// Notifies subscribers of that a new judgement has occurred. /// - /// The judgement to notify subscribers of. /// The judgement scoring result to notify subscribers of. protected void NotifyNewJudgement(JudgementResult result) { @@ -157,16 +178,24 @@ namespace osu.Game.Rulesets.Scoring /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// - public virtual void PopulateScore(Score score) + public virtual void PopulateScore(ScoreInfo score) { - score.TotalScore = TotalScore; - score.Combo = Combo; - score.MaxCombo = HighestCombo; - score.Accuracy = Accuracy; - score.Rank = Rank; + score.TotalScore = (long)Math.Round(TotalScore.Value); + score.Combo = Combo.Value; + score.MaxCombo = HighestCombo.Value; + score.Accuracy = Math.Round(Accuracy.Value, 4); + score.Rank = Rank.Value; score.Date = DateTimeOffset.Now; - score.Health = Health; + + var hitWindows = CreateHitWindows(); + + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + score.Statistics[result] = GetStatistic(result); } + + protected abstract int GetStatistic(HitResult result); + + public abstract double GetStandardisedScore(); } public class ScoreProcessor : ScoreProcessor @@ -192,15 +221,15 @@ namespace osu.Game.Rulesets.Scoring { } - public ScoreProcessor(RulesetContainer rulesetContainer) + public ScoreProcessor(DrawableRuleset drawableRuleset) { Debug.Assert(base_portion + combo_portion == 1.0); - rulesetContainer.OnNewResult += applyResult; - rulesetContainer.OnRevertResult += revertResult; + drawableRuleset.OnNewResult += applyResult; + drawableRuleset.OnRevertResult += revertResult; - ApplyBeatmap(rulesetContainer.Beatmap); - SimulateAutoplay(rulesetContainer.Beatmap); + ApplyBeatmap(drawableRuleset.Beatmap); + SimulateAutoplay(drawableRuleset.Beatmap); Reset(true); if (maxBaseScore == 0 || maxHighestCombo == 0) @@ -258,14 +287,13 @@ namespace osu.Game.Rulesets.Scoring ApplyResult(result); updateScore(); - UpdateFailed(); + UpdateFailed(result); NotifyNewJudgement(result); } /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. private void revertResult(JudgementResult result) { @@ -273,26 +301,34 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } + private readonly Dictionary scoreResultCounts = new Dictionary(); + /// /// Applies the score change of a to this . /// /// The to apply. protected virtual void ApplyResult(JudgementResult result) { - result.ComboAtJudgement = Combo; - result.HighestComboAtJudgement = HighestCombo; + result.ComboAtJudgement = Combo.Value; + result.HighestComboAtJudgement = HighestCombo.Value; + result.HealthAtJudgement = Health.Value; JudgedHits++; + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1; + if (result.Judgement.AffectsCombo) { switch (result.Type) { case HitResult.None: break; + case HitResult.Miss: Combo.Value = 0; break; + default: Combo.Value++; break; @@ -309,20 +345,25 @@ namespace osu.Game.Rulesets.Scoring baseScore += result.Judgement.NumericResultFor(result); rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + + Health.Value += HealthAdjustmentFactorFor(result) * result.Judgement.HealthIncreaseFor(result); } /// /// Reverts the score change of a that was applied to this . /// - /// The judgement to remove. /// The judgement scoring result. protected virtual void RevertResult(JudgementResult result) { Combo.Value = result.ComboAtJudgement; HighestCombo.Value = result.HighestComboAtJudgement; + Health.Value = result.HealthAtJudgement; JudgedHits--; + if (result.Type != HitResult.None) + scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) - 1; + if (result.Judgement.IsBonus) { if (result.IsHit) @@ -335,29 +376,47 @@ namespace osu.Game.Rulesets.Scoring } } + /// + /// An adjustment factor which is multiplied into the health increase provided by a . + /// + /// The for which the adjustment should apply. + /// The adjustment factor. + protected virtual double HealthAdjustmentFactorFor(JudgementResult result) => 1; + private void updateScore() { if (rollingMaxBaseScore != 0) Accuracy.Value = baseScore / rollingMaxBaseScore; - switch (Mode.Value) + TotalScore.Value = getScore(Mode.Value); + } + + private double getScore(ScoringMode mode) + { + switch (mode) { + default: case ScoringMode.Standardised: - TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore; - break; + return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore; + case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) - TotalScore.Value = bonusScore + baseScore * (1 + Math.Max(0, HighestCombo - 1) / 25); - break; + return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) / 25); } } + protected override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result); + + public override double GetStandardisedScore() => getScore(ScoringMode.Standardised); + protected override void Reset(bool storeResults) { + scoreResultCounts.Clear(); + if (storeResults) { MaxHits = JudgedHits; - maxHighestCombo = HighestCombo; + maxHighestCombo = HighestCombo.Value; maxBaseScore = baseScore; } diff --git a/osu.Game/Rulesets/Scoring/ScoreRank.cs b/osu.Game/Rulesets/Scoring/ScoreRank.cs deleted file mode 100644 index d4113bb08a..0000000000 --- a/osu.Game/Rulesets/Scoring/ScoreRank.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.Rulesets.Scoring -{ - public enum ScoreRank - { - [Description(@"F")] - F, - [Description(@"F")] - D, - [Description(@"C")] - C, - [Description(@"B")] - B, - [Description(@"A")] - A, - [Description(@"S")] - S, - [Description(@"SPlus")] - SH, - [Description(@"SS")] - X, - [Description(@"SSPlus")] - XH, - } -} diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs deleted file mode 100644 index 091cb29a71..0000000000 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ /dev/null @@ -1,60 +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.IO; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.IPC; -using osu.Game.Rulesets.Scoring.Legacy; - -namespace osu.Game.Rulesets.Scoring -{ - public class ScoreStore : DatabaseBackedStore, ICanAcceptFiles - { - private readonly BeatmapManager beatmaps; - private readonly RulesetStore rulesets; - - private const string replay_folder = @"replays"; - - public event Action ScoreImported; - - // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) - private ScoreIPCChannel ipc; - - public ScoreStore(DatabaseContextFactory factory, IIpcHost importHost = null, BeatmapManager beatmaps = null, RulesetStore rulesets = null) : base(factory) - { - this.beatmaps = beatmaps; - this.rulesets = rulesets; - - if (importHost != null) - ipc = new ScoreIPCChannel(importHost, this); - } - - public string[] HandledExtensions => new[] { ".osr" }; - - public void Import(params string[] paths) - { - foreach (var path in paths) - { - var score = ReadReplayFile(path); - if (score != null) - ScoreImported?.Invoke(score); - } - } - - public Score ReadReplayFile(string replayFilename) - { - if (File.Exists(replayFilename)) - { - using (var stream = File.OpenRead(replayFilename)) - return new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream); - } - - Logger.Log($"Replay file {replayFilename} cannot be found", LoggingTarget.Information, LogLevel.Error); - return null; - } - } -} diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs index 4988bac5ce..9bab065d1e 100644 --- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs +++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Beatmaps.ControlPoints; diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs new file mode 100644 index 0000000000..52fba9cab3 --- /dev/null +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -0,0 +1,418 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Graphics.Cursor; +using osu.Game.Input.Handlers; +using osu.Game.Overlays; +using osu.Game.Replays; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + /// + /// Displays an interactive ruleset gameplay instance. + /// + /// The type of HitObject contained by this DrawableRuleset. + public abstract class DrawableRuleset : DrawableRuleset, IProvideCursor, ICanAttachKeyCounter + where TObject : HitObject + { + /// + /// The selected variant. + /// + public virtual int Variant => 0; + + /// + /// The key conversion input manager for this DrawableRuleset. + /// + public PassThroughInputManager KeyBindingInputManager; + + public override double GameplayStartTime => Objects.First().StartTime - 2000; + + private readonly Lazy playfield; + + /// + /// The playfield. + /// + public override Playfield Playfield => playfield.Value; + + /// + /// Place to put drawables above hit objects but below UI. + /// + public Container Overlays { get; private set; } + + public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock; + + /// + /// Invoked when a has been applied by a . + /// + public event Action OnNewResult; + + /// + /// Invoked when a is being reverted by a . + /// + public event Action OnRevertResult; + + /// + /// The beatmap. + /// + public Beatmap Beatmap; + + public override IEnumerable Objects => Beatmap.HitObjects; + + protected IRulesetConfigManager Config { get; private set; } + + /// + /// The mods which are to be applied. + /// + [Cached(typeof(IReadOnlyList))] + private readonly IReadOnlyList mods; + + private FrameStabilityContainer frameStabilityContainer; + + private OnScreenDisplay onScreenDisplay; + + /// + /// Creates a ruleset visualisation for the provided ruleset and beatmap. + /// + /// The ruleset being represented. + /// The beatmap to create the hit renderer for. + /// The s to apply. + protected DrawableRuleset(Ruleset ruleset, WorkingBeatmap workingBeatmap, IReadOnlyList mods) + : base(ruleset) + { + if (workingBeatmap == null) + throw new ArgumentException("Beatmap cannot be null.", nameof(workingBeatmap)); + + this.mods = mods.ToArray(); + + RelativeSizeAxes = Axes.Both; + + Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); + + applyBeatmapMods(mods); + + KeyBindingInputManager = CreateInputManager(); + playfield = new Lazy(CreatePlayfield); + + IsPaused.ValueChanged += paused => + { + if (HasReplayLoaded.Value) + return; + + KeyBindingInputManager.UseParentInput = !paused.NewValue; + }; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + onScreenDisplay = dependencies.Get(); + + Config = dependencies.Get().GetConfigFor(Ruleset); + + if (Config != null) + { + dependencies.Cache(Config); + onScreenDisplay?.BeginTracking(this, Config); + } + + return dependencies; + } + + public virtual PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PlayfieldAdjustmentContainer(); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config, CancellationToken? cancellationToken) + { + InternalChildren = new Drawable[] + { + frameStabilityContainer = new FrameStabilityContainer(GameplayStartTime) + { + Child = KeyBindingInputManager + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(Playfield) + ) + }, + Overlays = new Container { RelativeSizeAxes = Axes.Both } + }; + + if ((ResumeOverlay = CreateResumeOverlay()) != null) + { + AddInternal(CreateInputManager() + .WithChild(CreatePlayfieldAdjustmentContainer() + .WithChild(ResumeOverlay))); + } + + applyRulesetMods(mods, config); + + loadObjects(cancellationToken); + } + + /// + /// Creates and adds drawable representations of hit objects to the play field. + /// + private void loadObjects(CancellationToken? cancellationToken) + { + foreach (TObject h in Beatmap.HitObjects) + { + cancellationToken?.ThrowIfCancellationRequested(); + addHitObject(h); + } + + cancellationToken?.ThrowIfCancellationRequested(); + + Playfield.PostProcess(); + + foreach (var mod in mods.OfType()) + mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); + } + + public override void RequestResume(Action continueResume) + { + if (ResumeOverlay != null && (Cursor == null || (Cursor.LastFrameState == Visibility.Visible && Contains(Cursor.ActiveCursor.ScreenSpaceDrawQuad.Centre)))) + { + ResumeOverlay.GameplayCursor = Cursor; + ResumeOverlay.ResumeAction = continueResume; + ResumeOverlay.Show(); + } + else + continueResume(); + } + + public ResumeOverlay ResumeOverlay { get; private set; } + + protected virtual ResumeOverlay CreateResumeOverlay() => null; + + /// + /// Creates and adds the visual representation of a to this . + /// + /// The to add the visual representation for. + private void addHitObject(TObject hitObject) + { + var drawableObject = CreateDrawableRepresentation(hitObject); + + if (drawableObject == null) + return; + + drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); + drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); + + Playfield.Add(drawableObject); + } + + public override void SetReplayScore(Score replayScore) + { + if (!(KeyBindingInputManager is IHasReplayHandler replayInputManager)) + throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); + + var handler = (ReplayScore = replayScore) != null ? CreateReplayInputHandler(replayScore.Replay) : null; + + replayInputManager.ReplayInputHandler = handler; + frameStabilityContainer.ReplayInputHandler = handler; + + HasReplayLoaded.Value = replayInputManager.ReplayInputHandler != null; + + if (replayInputManager.ReplayInputHandler != null) + replayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; + + if (!ProvidingUserCursor) + { + // The cursor is hidden by default (see Playfield.load()), but should be shown when there's a replay + Playfield.Cursor?.Show(); + } + } + + /// + /// Creates a DrawableHitObject from a HitObject. + /// + /// The HitObject to make drawable. + /// The DrawableHitObject. + public abstract DrawableHitObject CreateDrawableRepresentation(TObject h); + + public void Attach(KeyCounterDisplay keyCounter) => + (KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(keyCounter); + + /// + /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. + /// + /// The input manager. + protected abstract PassThroughInputManager CreateInputManager(); + + protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; + + /// + /// Creates a Playfield. + /// + /// The Playfield. + protected abstract Playfield CreatePlayfield(); + + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); + + /// + /// Applies the active mods to the Beatmap. + /// + /// + private void applyBeatmapMods(IReadOnlyList mods) + { + if (mods == null) + return; + + foreach (var mod in mods.OfType>()) + mod.ApplyToBeatmap(Beatmap); + } + + /// + /// Applies the active mods to this DrawableRuleset. + /// + /// The s to apply. + /// The to apply. + private void applyRulesetMods(IReadOnlyList mods, OsuConfigManager config) + { + if (mods == null) + return; + + foreach (var mod in mods.OfType>()) + mod.ApplyToDrawableRuleset(this); + + foreach (var mod in mods.OfType()) + mod.ReadFromConfig(config); + } + + #region IProvideCursor + + protected override bool OnHover(HoverEvent e) => true; // required for IProvideCursor + + CursorContainer IProvideCursor.Cursor => Playfield.Cursor; + + public override GameplayCursorContainer Cursor => Playfield.Cursor; + + public bool ProvidingUserCursor => Playfield.Cursor != null && !HasReplayLoaded.Value; + + #endregion + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (Config != null) + { + onScreenDisplay?.StopTracking(this, Config); + Config = null; + } + } + } + + /// + /// Displays an interactive ruleset gameplay instance. + /// + /// This type is required only for adding non-generic type to the draw hierarchy. + /// Once IDrawable is a thing, this can also become an interface. + /// + /// + public abstract class DrawableRuleset : CompositeDrawable + { + /// + /// Whether a replay is currently loaded. + /// + public readonly BindableBool HasReplayLoaded = new BindableBool(); + + /// + /// Whether the game is paused. Used to block user input. + /// + public readonly BindableBool IsPaused = new BindableBool(); + + /// + /// The playfield. + /// + public abstract Playfield Playfield { get; } + + /// + /// The frame-stable clock which is being used for playfield display. + /// + public abstract GameplayClock FrameStableClock { get; } + + /// ~ + /// The associated ruleset. + /// + public readonly Ruleset Ruleset; + + /// + /// Creates a ruleset visualisation for the provided ruleset. + /// + /// The ruleset. + internal DrawableRuleset(Ruleset ruleset) + { + Ruleset = ruleset; + } + + /// + /// All the converted hit objects contained by this hit renderer. + /// + public abstract IEnumerable Objects { get; } + + /// + /// The point in time at which gameplay starts, including any required lead-in for display purposes. + /// Defaults to two seconds before the first . Override as necessary. + /// + public abstract double GameplayStartTime { get; } + + /// + /// The currently loaded replay. Usually null in the case of a local player. + /// + public Score ReplayScore { get; protected set; } + + /// + /// The cursor being displayed by the . May be null if no cursor is provided. + /// + public abstract GameplayCursorContainer Cursor { get; } + + /// + /// Sets a replay to be used, overriding local input. + /// + /// The replay, null for local input. + public abstract void SetReplayScore(Score replayScore); + + /// + /// Invoked when the interactive user requests resuming from a paused state. + /// Allows potentially delaying the resume process until an interaction is performed. + /// + /// The action to run when resuming is to be completed. + public abstract void RequestResume(Action continueResume); + + /// + /// Create a for the associated ruleset and link with this + /// . + /// + /// A score processor. + public abstract ScoreProcessor CreateScoreProcessor(); + } + + public class BeatmapInvalidForRulesetException : ArgumentException + { + public BeatmapInvalidForRulesetException(string text) + : base(text) + { + } + } +} diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs new file mode 100644 index 0000000000..1cc56fff8b --- /dev/null +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -0,0 +1,179 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Input.Handlers; +using osu.Game.Screens.Play; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which consumes a parent gameplay clock and standardises frame counts for children. + /// Will ensure a minimum of 40 frames per clock second is maintained, regardless of any system lag or seeks. + /// + public class FrameStabilityContainer : Container, IHasReplayHandler + { + private readonly double gameplayStartTime; + + /// + /// The number of frames (per parent frame) which can be run in an attempt to catch-up to real-time. + /// + public int MaxCatchUpFrames { get; set; } = 5; + + [Cached] + public GameplayClock GameplayClock { get; } + + public FrameStabilityContainer(double gameplayStartTime = double.MinValue) + { + RelativeSizeAxes = Axes.Both; + + GameplayClock = new GameplayClock(framedClock = new FramedClock(manualClock = new ManualClock())); + + this.gameplayStartTime = gameplayStartTime; + } + + private readonly ManualClock manualClock; + + private readonly FramedClock framedClock; + + private IFrameBasedClock parentGameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(GameplayClock clock) + { + if (clock != null) + { + parentGameplayClock = clock; + GameplayClock.IsPaused.BindTo(clock.IsPaused); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + setClock(); + } + + /// + /// Whether we are running up-to-date with our parent clock. + /// If not, we will need to keep processing children until we catch up. + /// + private bool requireMoreUpdateLoops; + + /// + /// Whether we are in a valid state (ie. should we keep processing children frames). + /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. + /// + private bool validState; + + protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; + + private bool isAttached => ReplayInputHandler != null; + + private const double sixty_frame_time = 1000.0 / 60; + + private bool firstConsumption = true; + + public override bool UpdateSubTree() + { + requireMoreUpdateLoops = true; + validState = !GameplayClock.IsPaused.Value; + + int loops = 0; + + while (validState && requireMoreUpdateLoops && loops++ < MaxCatchUpFrames) + { + updateClock(); + + if (validState) + { + base.UpdateSubTree(); + UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); + } + } + + return true; + } + + private void updateClock() + { + if (parentGameplayClock == null) + setClock(); // LoadComplete may not be run yet, but we still want the clock. + + validState = true; + + manualClock.Rate = parentGameplayClock.Rate; + manualClock.IsRunning = parentGameplayClock.IsRunning; + + var newProposedTime = parentGameplayClock.CurrentTime; + + try + { + if (firstConsumption) + { + // On the first update, frame-stability seeking would result in unexpected/unwanted behaviour. + // Instead we perform an initial seek to the proposed time. + manualClock.CurrentTime = newProposedTime; + + // do a second process to clear out ElapsedTime + framedClock.ProcessFrame(); + + firstConsumption = false; + } + else if (manualClock.CurrentTime < gameplayStartTime) + manualClock.CurrentTime = newProposedTime = Math.Min(gameplayStartTime, newProposedTime); + else if (Math.Abs(manualClock.CurrentTime - newProposedTime) > sixty_frame_time * 1.2f) + { + newProposedTime = newProposedTime > manualClock.CurrentTime + ? Math.Min(newProposedTime, manualClock.CurrentTime + sixty_frame_time) + : Math.Max(newProposedTime, manualClock.CurrentTime - sixty_frame_time); + } + + if (!isAttached) + { + manualClock.CurrentTime = newProposedTime; + } + else + { + double? newTime = ReplayInputHandler.SetFrameFromTime(newProposedTime); + + if (newTime == null) + { + // we shouldn't execute for this time value. probably waiting on more replay data. + validState = false; + + requireMoreUpdateLoops = true; + manualClock.CurrentTime = newProposedTime; + return; + } + + manualClock.CurrentTime = newTime.Value; + } + + requireMoreUpdateLoops = manualClock.CurrentTime != parentGameplayClock.CurrentTime; + } + finally + { + // The manual clock time has changed in the above code. The framed clock now needs to be updated + // to ensure that the its time is valid for our children before input is processed + framedClock.ProcessFrame(); + } + } + + private void setClock() + { + // in case a parent gameplay clock isn't available, just use the parent clock. + if (parentGameplayClock == null) + parentGameplayClock = Clock; + + Clock = GameplayClock; + ProcessCustomClock = false; + } + + public ReplayInputHandler ReplayInputHandler { get; set; } + } +} diff --git a/osu.Game/Rulesets/UI/GameplayCursorContainer.cs b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs new file mode 100644 index 0000000000..ae5f9c6111 --- /dev/null +++ b/osu.Game/Rulesets/UI/GameplayCursorContainer.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; + +namespace osu.Game.Rulesets.UI +{ + public class GameplayCursorContainer : CursorContainer + { + /// + /// Because Show/Hide are executed by a parent, is updated immediately even if the cursor + /// is in a non-updating state (via limitations). + /// + /// This holds the true visibility value. + /// + public Visibility LastFrameState; + + protected override void Update() + { + base.Update(); + LastFrameState = State.Value; + } + } +} diff --git a/osu.Game/Rulesets/UI/HitObjectContainer.cs b/osu.Game/Rulesets/UI/HitObjectContainer.cs index 261132c56b..2f3a384e95 100644 --- a/osu.Game/Rulesets/UI/HitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/HitObjectContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI { - public class HitObjectContainer : CompositeDrawable + public class HitObjectContainer : LifetimeManagementContainer { public IEnumerable Objects => InternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); public IEnumerable AliveObjects => AliveInternalChildren.Cast().OrderBy(h => h.HitObject.StartTime); @@ -31,5 +31,11 @@ namespace osu.Game.Rulesets.UI int i = yObj.HitObject.StartTime.CompareTo(xObj.HitObject.StartTime); return i == 0 ? CompareReverseChildID(x, y) : i; } + + protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e) + { + if (e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward && e.Child is DrawableHitObject hitObject) + hitObject.OnLifetimeEnd(); + } } } diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index e0d62aba30..f1a68a0323 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index b5c8da8879..86feea09a8 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK.Graphics; @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osuTK; @@ -20,10 +21,10 @@ namespace osu.Game.Rulesets.UI private const float size = 80; - public FontAwesome Icon + public IconUsage Icon { - get { return modIcon.Icon; } - set { modIcon.Icon = value; } + get => modIcon.Icon; + set => modIcon.Icon = value; } private readonly ModType type; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.UI Origin = Anchor.Centre, Anchor = Anchor.Centre, Size = new Vector2(size), - Icon = FontAwesome.fa_osu_mod_bg, + Icon = OsuIcon.ModBg, Y = -6.5f, Shadow = true, }, @@ -75,18 +76,22 @@ namespace osu.Game.Rulesets.UI backgroundColour = colours.Yellow; highlightedColour = colours.YellowLight; break; + case ModType.DifficultyReduction: backgroundColour = colours.Green; highlightedColour = colours.GreenLight; break; + case ModType.Automation: backgroundColour = colours.Blue; highlightedColour = colours.BlueLight; break; + case ModType.Conversion: backgroundColour = colours.Purple; highlightedColour = colours.PurpleLight; break; + case ModType.Fun: backgroundColour = colours.Pink; highlightedColour = colours.PinkLight; @@ -100,7 +105,7 @@ namespace osu.Game.Rulesets.UI public bool Highlighted { - get { return highlighted; } + get => highlighted; set { diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 579462b11c..f2e7f51b52 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -7,8 +7,8 @@ using System.Linq; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Configuration; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; @@ -57,12 +57,24 @@ namespace osu.Game.Rulesets.UI hitObjectContainerLazy = new Lazy(CreateHitObjectContainer); } - private WorkingBeatmap beatmap; + [Resolved] + private IBindable beatmap { get; set; } + + [Resolved] + private IReadOnlyList mods { get; set; } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap) + private void load() { - this.beatmap = beatmap.Value; + Cursor = CreateCursor(); + + if (Cursor != null) + { + // initial showing of the cursor will be handed by MenuCursorContainer (via DrawableRuleset's IProvideCursor implementation). + Cursor.Hide(); + + AddInternal(Cursor); + } } /// @@ -80,7 +92,18 @@ namespace osu.Game.Rulesets.UI /// Remove a DrawableHitObject from this Playfield. /// /// The DrawableHitObject to remove. - public virtual void Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); + public virtual bool Remove(DrawableHitObject h) => HitObjectContainer.Remove(h); + + /// + /// The cursor currently being used by this . May be null if no cursor is provided. + /// + public GameplayCursorContainer Cursor { get; private set; } + + /// + /// Provide an optional cursor which is to be used for gameplay. + /// + /// The cursor, or null if a cursor is not rqeuired. + protected virtual GameplayCursorContainer CreateCursor() => null; /// /// Registers a as a nested . @@ -107,7 +130,7 @@ namespace osu.Game.Rulesets.UI base.Update(); if (beatmap != null) - foreach (var mod in beatmap.Mods.Value) + foreach (var mod in mods) if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); } diff --git a/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs new file mode 100644 index 0000000000..fff4a450e5 --- /dev/null +++ b/osu.Game/Rulesets/UI/PlayfieldAdjustmentContainer.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A container which handles sizing of the and any other components that need to match their size. + /// + public class PlayfieldAdjustmentContainer : Container + { + public PlayfieldAdjustmentContainer() + { + RelativeSizeAxes = Axes.Both; + } + } +} diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs deleted file mode 100644 index 0abd358113..0000000000 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ /dev/null @@ -1,374 +0,0 @@ -// 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.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using osu.Framework.Configuration; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Input; -using osu.Game.Configuration; -using osu.Game.Input.Handlers; -using osu.Game.Overlays; -using osu.Game.Rulesets.Configuration; -using osu.Game.Rulesets.Replays; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Rulesets.UI -{ - /// - /// Base RulesetContainer. Doesn't hold objects. - /// - /// Should not be derived - derive instead. - /// - /// - public abstract class RulesetContainer : Container - { - /// - /// The selected variant. - /// - public virtual int Variant => 0; - - /// - /// The input manager for this RulesetContainer. - /// - internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler; - - /// - /// The key conversion input manager for this RulesetContainer. - /// - public PassThroughInputManager KeyBindingInputManager; - - /// - /// Whether a replay is currently loaded. - /// - public readonly BindableBool HasReplayLoaded = new BindableBool(); - - public abstract IEnumerable Objects { get; } - - /// - /// The point in time at which gameplay starts, including any required lead-in for display purposes. - /// Defaults to two seconds before the first . Override as necessary. - /// - public virtual double GameplayStartTime => Objects.First().StartTime - 2000; - - private readonly Lazy playfield; - - /// - /// The playfield. - /// - public Playfield Playfield => playfield.Value; - - /// - /// The cursor provided by this . May be null if no cursor is provided. - /// - public readonly CursorContainer Cursor; - - public readonly Ruleset Ruleset; - - protected IRulesetConfigManager Config { get; private set; } - - private OnScreenDisplay onScreenDisplay; - - /// - /// A visual representation of a . - /// - /// The ruleset being repesented. - protected RulesetContainer(Ruleset ruleset) - { - Ruleset = ruleset; - playfield = new Lazy(CreatePlayfield); - - IsPaused.ValueChanged += paused => - { - if (HasReplayLoaded) - return; - - KeyBindingInputManager.UseParentInput = !paused; - }; - - Cursor = CreateCursor(); - } - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - onScreenDisplay = dependencies.Get(); - - Config = dependencies.Get().GetConfigFor(Ruleset); - if (Config != null) - { - dependencies.Cache(Config); - onScreenDisplay?.BeginTracking(this, Config); - } - - return dependencies; - } - - public abstract ScoreProcessor CreateScoreProcessor(); - - /// - /// Creates a key conversion input manager. An exception will be thrown if a valid is not returned. - /// - /// The input manager. - public abstract PassThroughInputManager CreateInputManager(); - - protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; - - public Replay Replay { get; private set; } - - /// - /// Whether the game is paused. Used to block user input. - /// - public readonly BindableBool IsPaused = new BindableBool(); - - /// - /// Sets a replay to be used, overriding local input. - /// - /// The replay, null for local input. - public virtual void SetReplay(Replay replay) - { - if (ReplayInputManager == null) - throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); - - Replay = replay; - ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; - - HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; - } - - /// - /// Creates the cursor. May be null if the doesn't provide a custom cursor. - /// - protected virtual CursorContainer CreateCursor() => null; - - /// - /// Creates a Playfield. - /// - /// The Playfield. - protected abstract Playfield CreatePlayfield(); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (Config != null) - { - onScreenDisplay?.StopTracking(this, Config); - Config = null; - } - } - } - - /// - /// RulesetContainer that applies conversion to Beatmaps. Does not contain a Playfield - /// and does not load drawable hit objects. - /// - /// Should not be derived - derive instead. - /// - /// - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - { - /// - /// Invoked when a has been applied by a . - /// - public event Action OnNewResult; - - /// - /// Invoked when a is being reverted by a . - /// - public event Action OnRevertResult; - - /// - /// The Beatmap - /// - public Beatmap Beatmap; - - /// - /// All the converted hit objects contained by this hit renderer. - /// - public override IEnumerable Objects => Beatmap.HitObjects; - - /// - /// The mods which are to be applied. - /// - protected IEnumerable Mods; - - /// - /// The this was created with. - /// - protected readonly WorkingBeatmap WorkingBeatmap; - - public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); - - protected override Container Content => content; - private Container content; - private IEnumerable mods; - - /// - /// Whether to assume the beatmap passed into this is for the current ruleset. - /// Creates a hit renderer for a beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap) - : base(ruleset) - { - Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap."); - - WorkingBeatmap = workingBeatmap; - // ReSharper disable once PossibleNullReferenceException - Mods = workingBeatmap.Mods.Value; - - RelativeSizeAxes = Axes.Both; - - Beatmap = (Beatmap)workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo); - - KeyBindingInputManager = CreateInputManager(); - KeyBindingInputManager.RelativeSizeAxes = Axes.Both; - - applyBeatmapMods(Mods); - } - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - KeyBindingInputManager.Add(content = new Container - { - RelativeSizeAxes = Axes.Both, - }); - - AddInternal(KeyBindingInputManager); - KeyBindingInputManager.Add(Playfield); - - if (Cursor != null) - KeyBindingInputManager.Add(Cursor); - - // Apply mods - applyRulesetMods(Mods, config); - - loadObjects(); - } - - /// - /// Applies the active mods to the Beatmap. - /// - /// - private void applyBeatmapMods(IEnumerable mods) - { - if (mods == null) - return; - - foreach (var mod in mods.OfType>()) - mod.ApplyToBeatmap(Beatmap); - } - - /// - /// Applies the active mods to this RulesetContainer. - /// - /// - private void applyRulesetMods(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) - { - base.SetReplay(replay); - - if (ReplayInputManager?.ReplayInputHandler != null) - ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; - } - - /// - /// Creates and adds drawable representations of hit objects to the play field. - /// - private void loadObjects() - { - foreach (TObject h in Beatmap.HitObjects) - AddRepresentation(h); - - Playfield.PostProcess(); - - foreach (var mod in Mods.OfType()) - mod.ApplyToDrawableHitObjects(Playfield.HitObjectContainer.Objects); - } - - /// - /// Creates and adds the visual representation of a to this . - /// - /// The to add the visual representation for. - internal void AddRepresentation(TObject hitObject) - { - var drawableObject = GetVisualRepresentation(hitObject); - - if (drawableObject == null) - return; - - drawableObject.OnNewResult += (_, r) => OnNewResult?.Invoke(r); - drawableObject.OnRevertResult += (_, r) => OnRevertResult?.Invoke(r); - - Playfield.Add(drawableObject); - } - - - /// - /// Creates a DrawableHitObject from a HitObject. - /// - /// The HitObject to make drawable. - /// The DrawableHitObject. - public abstract DrawableHitObject GetVisualRepresentation(TObject h); - } - - /// - /// A derivable RulesetContainer that manages the Playfield and HitObjects. - /// - /// The type of Playfield contained by this RulesetContainer. - /// The type of HitObject contained by this RulesetContainer. - public abstract class RulesetContainer : RulesetContainer - where TObject : HitObject - where TPlayfield : Playfield - { - /// - /// The playfield. - /// - protected new TPlayfield Playfield => (TPlayfield)base.Playfield; - - /// - /// Creates a hit renderer for a beatmap. - /// - /// The ruleset being repesented. - /// The beatmap to create the hit renderer for. - protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) - { - } - } - - public class BeatmapInvalidForRulesetException : ArgumentException - { - public BeatmapInvalidForRulesetException(string text) - : base(text) - { - } - } -} diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index a76942e3fa..e25c3bd0e7 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; @@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Input.StateChanges.Events; using osu.Framework.Input.States; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Input.Bindings; using osu.Game.Input.Handlers; @@ -27,14 +26,6 @@ namespace osu.Game.Rulesets.UI public abstract class RulesetInputManager : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler where T : struct { - public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer - { - public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) - : base(ruleset, variant, unique) - { - } - } - protected override InputState CreateInitialState() { var state = base.CreateInitialState(); @@ -43,11 +34,21 @@ namespace osu.Game.Rulesets.UI protected readonly KeyBindingContainer KeyBindingContainer; - protected override Container Content => KeyBindingContainer; + protected override Container Content => content; + + private readonly Container content; protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) { - InternalChild = KeyBindingContainer = CreateKeyBindingContainer(ruleset, variant, unique); + InternalChild = KeyBindingContainer = + CreateKeyBindingContainer(ruleset, variant, unique) + .WithChild(content = new Container { RelativeSizeAxes = Axes.Both }); + } + + [BackgroundDependencyLoader(true)] + private void load(OsuConfigManager config) + { + mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); } #region Action mapping (for replays) @@ -91,121 +92,10 @@ namespace osu.Game.Rulesets.UI #endregion - #region Clock control - - private ManualClock clock; - private IFrameBasedClock parentClock; - - protected override void LoadComplete() - { - base.LoadComplete(); - - //our clock will now be our parent's clock, but we want to replace this to allow manual control. - parentClock = Clock; - - ProcessCustomClock = false; - Clock = new FramedClock(clock = new ManualClock - { - CurrentTime = parentClock.CurrentTime, - Rate = parentClock.Rate, - }); - } - - /// - /// Whether we are running up-to-date with our parent clock. - /// If not, we will need to keep processing children until we catch up. - /// - private bool requireMoreUpdateLoops; - - /// - /// Whether we are in a valid state (ie. should we keep processing children frames). - /// This should be set to false when the replay is, for instance, waiting for future frames to arrive. - /// - private bool validState; - - protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState; - - private bool isAttached => replayInputHandler != null && !UseParentInput; - - private const int max_catch_up_updates_per_frame = 50; - - public override bool UpdateSubTree() - { - requireMoreUpdateLoops = true; - validState = true; - - int loops = 0; - - while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) - { - if (!base.UpdateSubTree()) - return false; - - UpdateSubTreeMasking(this, ScreenSpaceDrawQuad.AABBFloat); - - if (isAttached) - { - // When handling replay input, we need to consider the possibility of fast-forwarding, which may cause the clock to be updated - // to a point very far into the future, then playing a frame at that time. In such a case, lifetime MUST be updated before - // input is handled. This is why base.Update is not called from the derived Update when handling replay input, and is instead - // called manually at the correct time here. - base.Update(); - } - } - - return true; - } - - protected override void Update() - { - if (parentClock == null) return; - - clock.Rate = parentClock.Rate; - clock.IsRunning = parentClock.IsRunning; - - if (!isAttached) - { - clock.CurrentTime = parentClock.CurrentTime; - } - else - { - double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime); - - if (newTime == null) - { - // we shouldn't execute for this time value. probably waiting on more replay data. - validState = false; - return; - } - - clock.CurrentTime = newTime.Value; - } - - requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime; - - // The manual clock time has changed in the above code. The framed clock now needs to be updated - // to ensure that the its time is valid for our children before input is processed - Clock.ProcessFrame(); - - if (!isAttached) - { - // For non-replay input handling, this provides equivalent input ordering as if Update was not overridden - base.Update(); - } - } - - #endregion - #region Setting application (disables etc.) private Bindable mouseDisabled; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - mouseDisabled = config.GetBindable(OsuSetting.MouseDisableButtons); - } - protected override bool Handle(UIEvent e) { switch (e) @@ -213,12 +103,16 @@ namespace osu.Game.Rulesets.UI case MouseDownEvent mouseDown when mouseDown.Button == MouseButton.Left || mouseDown.Button == MouseButton.Right: if (mouseDisabled.Value) return false; + break; + case MouseUpEvent mouseUp: if (!CurrentState.Mouse.IsPressed(mouseUp.Button)) return false; + break; } + return base.Handle(e); } @@ -226,18 +120,19 @@ namespace osu.Game.Rulesets.UI #region Key Counter Attachment - public void Attach(KeyCounterCollection keyCounter) + public void Attach(KeyCounterDisplay keyCounter) { var receptor = new ActionReceptor(keyCounter); - Add(receptor); - keyCounter.SetReceptor(receptor); + KeyBindingContainer.Add(receptor); + + keyCounter.SetReceptor(receptor); keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings.Select(b => b.GetAction()).Distinct().Select(b => new KeyCounterAction(b))); } - public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler + public class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler { - public ActionReceptor(KeyCounterCollection target) + public ActionReceptor(KeyCounterDisplay target) : base(target) { } @@ -251,6 +146,14 @@ namespace osu.Game.Rulesets.UI protected virtual RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) => new RulesetKeyBindingContainer(ruleset, variant, unique); + + public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer + { + public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) + : base(ruleset, variant, unique) + { + } + } } /// @@ -262,12 +165,12 @@ namespace osu.Game.Rulesets.UI } /// - /// Supports attaching a . + /// Supports attaching a . /// Keys will be populated automatically and a receptor will be injected inside. /// public interface ICanAttachKeyCounter { - void Attach(KeyCounterCollection keyCounter); + void Attach(KeyCounterDisplay keyCounter); } public class RulesetInputManagerInputState : InputState diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs index 5628fb51f3..75ea3efdf2 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs index 2ece9bef9b..b7a5eedc22 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The current time. /// The amount of visible time. /// The absolute spatial length through . - /// The time at which == . + /// The time at which == . double TimeAt(float position, double currentTime, double timeRange, float scrollLength); /// diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs index 231aaa3c02..5316585493 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Lists; using osu.Game.Rulesets.Timing; diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs index 8f8f546992..3c9a205412 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs similarity index 84% rename from osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs rename to osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 83b9e31a46..42ec0b79b9 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingRulesetContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; using osu.Framework.Lists; @@ -12,6 +13,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; using osu.Game.Input.Bindings; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Timing; @@ -20,12 +22,11 @@ using osu.Game.Rulesets.UI.Scrolling.Algorithms; namespace osu.Game.Rulesets.UI.Scrolling { /// - /// A type of that supports a . - /// s inside this will scroll within the playfield. + /// A type of that supports a . + /// s inside this will scroll within the playfield. /// - public abstract class ScrollingRulesetContainer : RulesetContainer, IKeyBindingHandler + public abstract class DrawableScrollingRuleset : DrawableRuleset, IKeyBindingHandler where TObject : HitObject - where TPlayfield : ScrollingPlayfield { /// /// The default span of time visible by the length of the scrolling axes. @@ -64,13 +65,13 @@ namespace osu.Game.Rulesets.UI.Scrolling protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential; /// - /// Whether the player can change . + /// Whether the player can change . /// protected virtual bool UserScrollSpeedAdjustment => true; /// /// Provides the default s that adjust the scrolling rate of s - /// inside this . + /// inside this . /// /// private readonly SortedList controlPoints = new SortedList(Comparer.Default); @@ -80,8 +81,8 @@ namespace osu.Game.Rulesets.UI.Scrolling [Cached(Type = typeof(IScrollingInfo))] private readonly LocalScrollingInfo scrollingInfo; - protected ScrollingRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) - : base(ruleset, beatmap) + protected DrawableScrollingRuleset(Ruleset ruleset, WorkingBeatmap beatmap, IReadOnlyList mods) + : base(ruleset, beatmap, mods) { scrollingInfo = new LocalScrollingInfo(); scrollingInfo.Direction.BindTo(Direction); @@ -92,9 +93,11 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollVisualisationMethod.Sequential: scrollingInfo.Algorithm = new SequentialScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Overlapping: scrollingInfo.Algorithm = new OverlappingScrollAlgorithm(controlPoints); break; + case ScrollVisualisationMethod.Constant: scrollingInfo.Algorithm = new ConstantScrollAlgorithm(); break; @@ -157,16 +160,25 @@ namespace osu.Game.Rulesets.UI.Scrolling switch (action) { case GlobalAction.IncreaseScrollSpeed: - this.TransformBindableTo(TimeRange, TimeRange - time_span_step, 200, Easing.OutQuint); + this.TransformBindableTo(TimeRange, TimeRange.Value - time_span_step, 200, Easing.OutQuint); return true; + case GlobalAction.DecreaseScrollSpeed: - this.TransformBindableTo(TimeRange, TimeRange + time_span_step, 200, Easing.OutQuint); + this.TransformBindableTo(TimeRange, TimeRange.Value + time_span_step, 200, Easing.OutQuint); return true; } return false; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (!(Playfield is ScrollingPlayfield)) + throw new ArgumentException($"{nameof(Playfield)} must be a {nameof(ScrollingPlayfield)} when using {nameof(DrawableScrollingRuleset)}."); + } + public bool OnReleased(GlobalAction action) => false; private class LocalScrollingInfo : IScrollingInfo diff --git a/osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs b/osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs index 21cbd855a9..cd85932599 100644 --- a/osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs +++ b/osu.Game/Rulesets/UI/Scrolling/IScrollingInfo.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI.Scrolling.Algorithms; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs index d0db8f8835..81e1a6c916 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingDirection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Rulesets.UI.Scrolling { @@ -9,14 +9,17 @@ namespace osu.Game.Rulesets.UI.Scrolling /// Hit objects will scroll vertically from the bottom of the hitobject container. /// Up, + /// /// Hit objects will scroll vertically from the top of the hitobject container. /// Down, + /// /// Hit objects will scroll horizontally from the right of the hitobject container. /// Left, + /// /// Hit objects will scroll horizontally from the left of the hitobject container. /// diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 00642b3d41..069e2d1a0b 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Caching; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -72,6 +72,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: scrollLength = DrawSize.Y; break; + default: scrollLength = DrawSize.X; break; @@ -97,6 +98,7 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Down: hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: case ScrollingDirection.Right: hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime.EndTime, timeRange.Value, scrollLength); @@ -129,12 +131,15 @@ namespace osu.Game.Rulesets.UI.Scrolling case ScrollingDirection.Up: hitObject.Y = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Down: hitObject.Y = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Left: hitObject.X = scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; + case ScrollingDirection.Right: hitObject.X = -scrollingInfo.Algorithm.PositionAt(hitObject.HitObject.StartTime, currentTime, timeRange.Value, scrollLength); break; diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 0eb67b8bb1..bf2203e176 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.UI.Scrolling diff --git a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs similarity index 80% rename from osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs rename to osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs index bfb2b7c13b..77edd24612 100644 --- a/osu.Game/Rulesets/Scoring/Legacy/DatabasedLegacyScoreParser.cs +++ b/osu.Game/Scoring/Legacy/DatabasedLegacyScoreParser.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; +using osu.Game.Rulesets; -namespace osu.Game.Rulesets.Scoring.Legacy +namespace osu.Game.Scoring.Legacy { /// /// A which retrieves the applicable and diff --git a/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs new file mode 100644 index 0000000000..e66f93ec8d --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreInfo.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring.Legacy +{ + public class LegacyScoreInfo : ScoreInfo + { + private int countGeki; + + public int CountGeki + { + get => countGeki; + set + { + countGeki = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 3: + Statistics[HitResult.Perfect] = value; + break; + } + } + } + + private int count300; + + public int Count300 + { + get => count300; + set + { + count300 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 1: + case 3: + Statistics[HitResult.Great] = value; + break; + + case 2: + Statistics[HitResult.Perfect] = value; + break; + } + } + } + + private int countKatu; + + public int CountKatu + { + get => countKatu; + set + { + countKatu = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 3: + Statistics[HitResult.Good] = value; + break; + } + } + } + + private int count100; + + public int Count100 + { + get => count100; + set + { + count100 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 1: + Statistics[HitResult.Good] = value; + break; + + case 3: + Statistics[HitResult.Ok] = value; + break; + } + } + } + + private int count50; + + public int Count50 + { + get => count50; + set + { + count50 = value; + + switch (Ruleset?.ID ?? RulesetID) + { + case 0: + case 3: + Statistics[HitResult.Meh] = value; + break; + } + } + } + + public int CountMiss + { + get => Statistics[HitResult.Miss]; + set => Statistics[HitResult.Miss] = value; + } + } +} diff --git a/osu.Game/Scoring/Legacy/LegacyScoreParser.cs b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs new file mode 100644 index 0000000000..0fdbd56c92 --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyScoreParser.cs @@ -0,0 +1,283 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Linq; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.Beatmaps.Legacy; +using osu.Game.IO.Legacy; +using osu.Game.Replays; +using osu.Game.Replays.Legacy; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Users; +using SharpCompress.Compressors.LZMA; + +namespace osu.Game.Scoring.Legacy +{ + public abstract class LegacyScoreParser + { + private IBeatmap currentBeatmap; + private Ruleset currentRuleset; + + public Score Parse(Stream stream) + { + var score = new Score + { + ScoreInfo = new ScoreInfo(), + Replay = new Replay() + }; + + using (SerializationReader sr = new SerializationReader(stream)) + { + currentRuleset = GetRuleset(sr.ReadByte()); + var scoreInfo = new LegacyScoreInfo { Ruleset = currentRuleset.RulesetInfo }; + + score.ScoreInfo = scoreInfo; + + var version = sr.ReadInt32(); + + var workingBeatmap = GetBeatmap(sr.ReadString()); + if (workingBeatmap is DummyWorkingBeatmap) + throw new BeatmapNotFoundException(); + + currentBeatmap = workingBeatmap.Beatmap; + scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + + scoreInfo.User = new User { Username = sr.ReadString() }; + + // MD5Hash + sr.ReadString(); + + scoreInfo.Count300 = sr.ReadUInt16(); + scoreInfo.Count100 = sr.ReadUInt16(); + scoreInfo.Count50 = sr.ReadUInt16(); + scoreInfo.CountGeki = sr.ReadUInt16(); + scoreInfo.CountKatu = sr.ReadUInt16(); + scoreInfo.CountMiss = sr.ReadUInt16(); + + scoreInfo.TotalScore = sr.ReadInt32(); + scoreInfo.MaxCombo = sr.ReadUInt16(); + + /* score.Perfect = */ + sr.ReadBoolean(); + + scoreInfo.Mods = currentRuleset.ConvertLegacyMods((LegacyMods)sr.ReadInt32()).ToArray(); + + /* score.HpGraphString = */ + sr.ReadString(); + + scoreInfo.Date = sr.ReadDateTime(); + + var compressedReplay = sr.ReadByteArray(); + + if (version >= 20140721) + scoreInfo.OnlineScoreID = sr.ReadInt64(); + else if (version >= 20121008) + scoreInfo.OnlineScoreID = sr.ReadInt32(); + + if (compressedReplay?.Length > 0) + { + using (var replayInStream = new MemoryStream(compressedReplay)) + { + byte[] properties = new byte[5]; + if (replayInStream.Read(properties, 0, 5) != 5) + throw new IOException("input .lzma is too short"); + + long outSize = 0; + + for (int i = 0; i < 8; i++) + { + int v = replayInStream.ReadByte(); + if (v < 0) + throw new IOException("Can't Read 1"); + + outSize |= (long)(byte)v << (8 * i); + } + + long compressedSize = replayInStream.Length - replayInStream.Position; + + using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) + using (var reader = new StreamReader(lzma)) + readLegacyReplay(score.Replay, reader); + } + } + } + + CalculateAccuracy(score.ScoreInfo); + + return score; + } + + protected void CalculateAccuracy(ScoreInfo score) + { + score.Statistics.TryGetValue(HitResult.Miss, out int countMiss); + score.Statistics.TryGetValue(HitResult.Meh, out int count50); + score.Statistics.TryGetValue(HitResult.Good, out int count100); + score.Statistics.TryGetValue(HitResult.Great, out int count300); + score.Statistics.TryGetValue(HitResult.Perfect, out int countGeki); + score.Statistics.TryGetValue(HitResult.Ok, out int countKatu); + + switch (score.Ruleset.ID) + { + case 0: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + count300 * 300) / (totalHits * 300) : 1; + + float ratio300 = (float)count300 / totalHits; + float ratio50 = (float)count50 / totalHits; + + if (ratio300 == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9) + score.Rank = ScoreRank.A; + else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8) + score.Rank = ScoreRank.B; + else if (ratio300 > 0.6) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + + case 1: + { + int totalHits = count50 + count100 + count300 + countMiss; + score.Accuracy = totalHits > 0 ? (double)(count100 * 150 + count300 * 300) / (totalHits * 300) : 1; + + float ratio300 = (float)count300 / totalHits; + float ratio50 = (float)count50 / totalHits; + + if (ratio300 == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (ratio300 > 0.9 && ratio50 <= 0.01 && countMiss == 0) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if ((ratio300 > 0.8 && countMiss == 0) || ratio300 > 0.9) + score.Rank = ScoreRank.A; + else if ((ratio300 > 0.7 && countMiss == 0) || ratio300 > 0.8) + score.Rank = ScoreRank.B; + else if (ratio300 > 0.6) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + + case 2: + { + int totalHits = count50 + count100 + count300 + countMiss + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 + count100 + count300) / totalHits : 1; + + if (score.Accuracy == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (score.Accuracy > 0.98) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (score.Accuracy > 0.94) + score.Rank = ScoreRank.A; + else if (score.Accuracy > 0.9) + score.Rank = ScoreRank.B; + else if (score.Accuracy > 0.85) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + + case 3: + { + int totalHits = count50 + count100 + count300 + countMiss + countGeki + countKatu; + score.Accuracy = totalHits > 0 ? (double)(count50 * 50 + count100 * 100 + countKatu * 200 + (count300 + countGeki) * 300) / (totalHits * 300) : 1; + + if (score.Accuracy == 1) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X; + else if (score.Accuracy > 0.95) + score.Rank = score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S; + else if (score.Accuracy > 0.9) + score.Rank = ScoreRank.A; + else if (score.Accuracy > 0.8) + score.Rank = ScoreRank.B; + else if (score.Accuracy > 0.7) + score.Rank = ScoreRank.C; + else + score.Rank = ScoreRank.D; + break; + } + } + } + + private void readLegacyReplay(Replay replay, StreamReader reader) + { + float lastTime = 0; + + foreach (var l in reader.ReadToEnd().Split(',')) + { + var split = l.Split('|'); + + if (split.Length < 4) + continue; + + if (split[0] == "-12345") + { + // Todo: The seed is provided in split[3], which we'll need to use at some point + continue; + } + + var diff = Parsing.ParseFloat(split[0]); + lastTime += diff; + + // Todo: At some point we probably want to rewind and play back the negative-time frames + // but for now we'll achieve equal playback to stable by skipping negative frames + if (diff < 0) + continue; + + replay.Frames.Add(convertFrame(new LegacyReplayFrame(lastTime, + Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE), + Parsing.ParseFloat(split[2], Parsing.MAX_COORDINATE_VALUE), + (ReplayButtonState)Parsing.ParseInt(split[3])))); + } + } + + private ReplayFrame convertFrame(LegacyReplayFrame legacyFrame) + { + var convertible = currentRuleset.CreateConvertibleReplayFrame(); + if (convertible == null) + throw new InvalidOperationException($"Legacy replay cannot be converted for the ruleset: {currentRuleset.Description}"); + + convertible.ConvertFrom(legacyFrame, currentBeatmap); + + var frame = (ReplayFrame)convertible; + frame.Time = legacyFrame.Time; + + return frame; + } + + /// + /// Retrieves the for a specific id. + /// + /// The id. + /// The . + protected abstract Ruleset GetRuleset(int rulesetId); + + /// + /// Retrieves the corresponding to an MD5 hash. + /// + /// The MD5 hash. + /// The . + protected abstract WorkingBeatmap GetBeatmap(string md5Hash); + + public class BeatmapNotFoundException : Exception + { + public BeatmapNotFoundException() + : base("No corresponding beatmap for the score could be found.") + { + } + } + } +} diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs new file mode 100644 index 0000000000..172e08e2d0 --- /dev/null +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.IO.Stores; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring.Legacy; + +namespace osu.Game.Scoring +{ + public class LegacyDatabasedScore : Score + { + public LegacyDatabasedScore(ScoreInfo score, RulesetStore rulesets, BeatmapManager beatmaps, IResourceStore store) + { + ScoreInfo = score; + + var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; + + using (var stream = store.GetStream(replayFilename)) + Replay = new DatabasedLegacyScoreParser(rulesets, beatmaps).Parse(stream).Replay; + } + } +} diff --git a/osu.Game/Scoring/Score.cs b/osu.Game/Scoring/Score.cs new file mode 100644 index 0000000000..4e82b1584e --- /dev/null +++ b/osu.Game/Scoring/Score.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Replays; + +namespace osu.Game.Scoring +{ + public class Score + { + public ScoreInfo ScoreInfo = new ScoreInfo(); + public Replay Replay = new Replay(); + } +} diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs new file mode 100644 index 0000000000..9075fdec5b --- /dev/null +++ b/osu.Game/Scoring/ScoreFileInfo.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel.DataAnnotations; +using osu.Game.Database; +using osu.Game.IO; + +namespace osu.Game.Scoring +{ + public class ScoreFileInfo : INamedFileInfo, IHasPrimaryKey + { + public int ID { get; set; } + + public int FileInfoID { get; set; } + + public FileInfo FileInfo { get; set; } + + [Required] + public string Filename { get; set; } + } +} diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs new file mode 100644 index 0000000000..8bdc30ac94 --- /dev/null +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -0,0 +1,186 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Users; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring +{ + public class ScoreInfo : IHasFiles, IHasPrimaryKey, ISoftDelete + { + public int ID { get; set; } + + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } + + [JsonProperty("total_score")] + public long TotalScore { get; set; } + + [JsonProperty("accuracy")] + [Column(TypeName = "DECIMAL(1,4)")] + public double Accuracy { get; set; } + + [JsonProperty(@"pp")] + public double? PP { get; set; } + + [JsonProperty("max_combo")] + public int MaxCombo { get; set; } + + [JsonIgnore] + public int Combo { get; set; } // Todo: Shouldn't exist in here + + [JsonIgnore] + public int RulesetID { get; set; } + + [JsonProperty("passed")] + [NotMapped] + public bool Passed { get; set; } = true; + + [JsonIgnore] + public virtual RulesetInfo Ruleset { get; set; } + + private Mod[] mods; + + [JsonProperty("mods")] + [NotMapped] + public Mod[] Mods + { + get + { + if (mods != null) + return mods; + + if (modsJson == null) + return Array.Empty(); + + return getModsFromRuleset(JsonConvert.DeserializeObject(modsJson)); + } + set + { + modsJson = null; + mods = value; + } + } + + private Mod[] getModsFromRuleset(DeserializedMod[] mods) => Ruleset.CreateInstance().GetAllMods().Where(mod => mods.Any(d => d.Acronym == mod.Acronym)).ToArray(); + + private string modsJson; + + [JsonIgnore] + [Column("Mods")] + public string ModsJson + { + get + { + if (modsJson != null) + return modsJson; + + if (mods == null) + return null; + + return modsJson = JsonConvert.SerializeObject(mods); + } + set + { + modsJson = value; + + // we potentially can't update this yet due to Ruleset being late-bound, so instead update on read as necessary. + mods = null; + } + } + + [NotMapped] + [JsonProperty("user")] + public User User { get; set; } + + [JsonIgnore] + [Column("User")] + public string UserString + { + get => User?.Username; + set + { + if (User == null) + User = new User(); + + User.Username = value; + } + } + + [JsonIgnore] + [Column("UserID")] + public long? UserID + { + get => User?.Id ?? 1; + set + { + if (User == null) + User = new User(); + + User.Id = value ?? 1; + } + } + + [JsonIgnore] + public int BeatmapInfoID { get; set; } + + [JsonIgnore] + public virtual BeatmapInfo Beatmap { get; set; } + + [JsonIgnore] + public long? OnlineScoreID { get; set; } + + [JsonIgnore] + public DateTimeOffset Date { get; set; } + + [JsonProperty("statistics")] + public Dictionary Statistics = new Dictionary(); + + [JsonIgnore] + [Column("Statistics")] + public string StatisticsJson + { + get => JsonConvert.SerializeObject(Statistics); + set + { + if (value == null) + { + Statistics.Clear(); + return; + } + + Statistics = JsonConvert.DeserializeObject>(value); + } + } + + [JsonIgnore] + public List Files { get; set; } + + [JsonIgnore] + public string Hash { get; set; } + + [JsonIgnore] + public bool DeletePending { get; set; } + + [Serializable] + protected class DeserializedMod : IMod + { + public string Acronym { get; set; } + + public bool Equals(IMod other) => Acronym == other?.Acronym; + } + + public override string ToString() => $"{User} playing {Beatmap}"; + } +} diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs new file mode 100644 index 0000000000..6b737dc734 --- /dev/null +++ b/osu.Game/Scoring/ScoreManager.cs @@ -0,0 +1,64 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.IO.Archives; +using osu.Game.Rulesets; +using osu.Game.Scoring.Legacy; + +namespace osu.Game.Scoring +{ + public class ScoreManager : ArchiveModelManager + { + public override string[] HandledExtensions => new[] { ".osr" }; + + protected override string[] HashableFileTypes => new[] { ".osr" }; + + protected override string ImportFromStablePath => "Replays"; + + private readonly RulesetStore rulesets; + private readonly Func beatmaps; + + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, IDatabaseContextFactory contextFactory, IIpcHost importHost = null) + : base(storage, contextFactory, new ScoreStore(contextFactory, storage), importHost) + { + this.rulesets = rulesets; + this.beatmaps = beatmaps; + } + + protected override ScoreInfo CreateModel(ArchiveReader archive) + { + if (archive == null) + return null; + + using (var stream = archive.GetStream(archive.Filenames.First(f => f.EndsWith(".osr")))) + { + try + { + return new DatabasedLegacyScoreParser(rulesets, beatmaps()).Parse(stream).ScoreInfo; + } + catch (LegacyScoreParser.BeatmapNotFoundException e) + { + Logger.Log(e.Message, LoggingTarget.Information, LogLevel.Error); + return null; + } + } + } + + public Score GetScore(ScoreInfo score) => new LegacyDatabasedScore(score, rulesets, beatmaps(), Files.Store); + + public List GetAllUsableScores() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); + + public IEnumerable QueryScores(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().Where(query); + + public ScoreInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); + } +} diff --git a/osu.Game/Scoring/ScoreRank.cs b/osu.Game/Scoring/ScoreRank.cs new file mode 100644 index 0000000000..696d493830 --- /dev/null +++ b/osu.Game/Scoring/ScoreRank.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; + +namespace osu.Game.Scoring +{ + public enum ScoreRank + { + [Description(@"D")] + D, + + [Description(@"C")] + C, + + [Description(@"B")] + B, + + [Description(@"A")] + A, + + [Description(@"S")] + S, + + [Description(@"S+")] + SH, + + [Description(@"SS")] + X, + + [Description(@"SS+")] + XH, + } +} diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs new file mode 100644 index 0000000000..9627481f4d --- /dev/null +++ b/osu.Game/Scoring/ScoreStore.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.Scoring +{ + public class ScoreStore : MutableDatabaseBackedStoreWithFileIncludes + { + public ScoreStore(IDatabaseContextFactory factory, Storage storage) + : base(factory, storage) + { + } + + protected override IQueryable AddIncludesForConsumption(IQueryable query) + => base.AddIncludesForConsumption(query) + .Include(s => s.Beatmap) + .Include(s => s.Ruleset); + } +} diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs index ed477c9d0b..bbe162cf7c 100644 --- a/osu.Game/Screens/BackgroundScreen.cs +++ b/osu.Game/Screens/BackgroundScreen.cs @@ -1,8 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using System.Threading; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Input.Events; @@ -12,6 +11,12 @@ namespace osu.Game.Screens { public abstract class BackgroundScreen : Screen, IEquatable { + protected BackgroundScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + public virtual bool Equals(BackgroundScreen other) { return other?.GetType() == GetType(); @@ -26,64 +31,40 @@ namespace osu.Game.Screens return false; } - public override void Push(Screen screen) - { - // When trying to push a non-loaded screen, load it asynchronously and re-invoke Push - // once it's done. - if (screen.LoadState == LoadState.NotLoaded) - { - LoadComponentAsync(screen, d => Push((BackgroundScreen)d)); - return; - } - - // Make sure the in-progress loading is complete before pushing the screen. - while (screen.LoadState < LoadState.Ready) - Thread.Sleep(1); - - try - { - base.Push(screen); - } - catch (ScreenAlreadyExitedException) - { - // screen may have exited before the push was successful. - } - } - protected override void Update() { base.Update(); - Content.Scale = new Vector2(1 + x_movement_amount / DrawSize.X * 2); + Scale = new Vector2(1 + x_movement_amount / DrawSize.X * 2); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { - Content.FadeOut(); - Content.MoveToX(x_movement_amount); + this.FadeOut(); + this.MoveToX(x_movement_amount); - Content.FadeIn(transition_length, Easing.InOutQuart); - Content.MoveToX(0, transition_length, Easing.InOutQuart); + this.FadeIn(transition_length, Easing.InOutQuart); + this.MoveToX(0, transition_length, Easing.InOutQuart); base.OnEntering(last); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { - Content.MoveToX(-x_movement_amount, transition_length, Easing.InOutQuart); + this.MoveToX(-x_movement_amount, transition_length, Easing.InOutQuart); base.OnSuspending(next); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { - Content.FadeOut(transition_length, Easing.OutExpo); - Content.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); + this.FadeOut(transition_length, Easing.OutExpo); + this.MoveToX(x_movement_amount, transition_length, Easing.OutExpo); return base.OnExiting(next); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { - Content.MoveToX(0, transition_length, Easing.OutExpo); + this.MoveToX(0, transition_length, Easing.OutExpo); base.OnResuming(last); } } diff --git a/osu.Game/Screens/BackgroundScreenStack.cs b/osu.Game/Screens/BackgroundScreenStack.cs new file mode 100644 index 0000000000..9c0c5da0fb --- /dev/null +++ b/osu.Game/Screens/BackgroundScreenStack.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osuTK; + +namespace osu.Game.Screens +{ + public class BackgroundScreenStack : ScreenStack + { + public BackgroundScreenStack() + : base(false) + { + Scale = new Vector2(1.06f); + RelativeSizeAxes = Axes.Both; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + //public float ParallaxAmount { set => parallax.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * value; } + + public void Push(BackgroundScreen screen) + { + if (screen == null) + return; + + if (EqualityComparer.Default.Equals((BackgroundScreen)CurrentScreen, screen)) + return; + + base.Push(screen); + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index e326cdb0ca..b6c2d016d2 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -1,21 +1,60 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenBeatmap : BlurrableBackgroundScreen + public class BackgroundScreenBeatmap : BackgroundScreen { + protected Background Background; + private WorkingBeatmap beatmap; + /// + /// Whether or not user dim settings should be applied to this Background. + /// + public readonly Bindable EnableUserDim = new Bindable(); + + public readonly Bindable StoryboardReplacesBackground = new Bindable(); + + /// + /// The amount of blur to be applied in addition to user-specified blur. + /// + public readonly Bindable BlurAmount = new Bindable(); + + private readonly UserDimContainer fadeContainer; + + protected virtual UserDimContainer CreateFadeContainer() => new UserDimContainer { RelativeSizeAxes = Axes.Both }; + + public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) + { + Beatmap = beatmap; + InternalChild = fadeContainer = CreateFadeContainer(); + fadeContainer.EnableUserDim.BindTo(EnableUserDim); + fadeContainer.BlurAmount.BindTo(BlurAmount); + } + + [BackgroundDependencyLoader] + private void load() + { + var background = new BeatmapBackground(beatmap); + LoadComponent(background); + switchBackground(background); + } + + private CancellationTokenSource cancellationSource; + public WorkingBeatmap Beatmap { - get { return beatmap; } + get => beatmap; set { if (beatmap == value && beatmap != null) @@ -25,51 +64,52 @@ namespace osu.Game.Screens.Backgrounds Schedule(() => { - LoadComponentAsync(new BeatmapBackground(beatmap), b => Schedule(() => - { - float newDepth = 0; - if (Background != null) - { - newDepth = Background.Depth + 1; - Background.FinishTransforms(); - Background.FadeOut(250); - Background.Expire(); - } + if ((Background as BeatmapBackground)?.Beatmap == beatmap) + return; - b.Depth = newDepth; - Add(Background = b); - Background.BlurSigma = BlurTarget; - })); + cancellationSource?.Cancel(); + LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token); }); } } - public BackgroundScreenBeatmap(WorkingBeatmap beatmap = null) + private void switchBackground(BeatmapBackground b) { - Beatmap = beatmap; + float newDepth = 0; + + if (Background != null) + { + newDepth = Background.Depth + 1; + Background.FinishTransforms(); + Background.FadeOut(250); + Background.Expire(); + } + + b.Depth = newDepth; + fadeContainer.Background = Background = b; + StoryboardReplacesBackground.BindTo(fadeContainer.StoryboardReplacesBackground); } public override bool Equals(BackgroundScreen other) { - var otherBeatmapBackground = other as BackgroundScreenBeatmap; - if (otherBeatmapBackground == null) return false; + if (!(other is BackgroundScreenBeatmap otherBeatmapBackground)) return false; return base.Equals(other) && beatmap == otherBeatmapBackground.Beatmap; } - private class BeatmapBackground : Background + protected class BeatmapBackground : Background { - private readonly WorkingBeatmap beatmap; + public readonly WorkingBeatmap Beatmap; public BeatmapBackground(WorkingBeatmap beatmap) { - this.beatmap = beatmap; + Beatmap = beatmap; } [BackgroundDependencyLoader] private void load(TextureStore textures) { - Sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); + Sprite.Texture = Beatmap?.Background ?? textures.Get(@"Backgrounds/bg1"); } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs new file mode 100644 index 0000000000..9e2559cc56 --- /dev/null +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osuTK.Graphics; + +namespace osu.Game.Screens.Backgrounds +{ + public class BackgroundScreenBlack : BackgroundScreen + { + public BackgroundScreenBlack() + { + InternalChild = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } + + public override void OnEntering(IScreen last) + { + Show(); + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs index 041391db1e..0cb41bc562 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenCustom.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics.Backgrounds; @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Backgrounds public BackgroundScreenCustom(string textureName) { this.textureName = textureName; - Add(new Background(textureName)); + AddInternal(new Background(textureName)); } public override bool Equals(BackgroundScreen other) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 989883c8b3..7092ac0c4a 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -1,34 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Framework.Threading; using osu.Game.Graphics.Backgrounds; +using osu.Game.Online.API; +using osu.Game.Skinning; +using osu.Game.Users; namespace osu.Game.Screens.Backgrounds { - public class BackgroundScreenDefault : BlurrableBackgroundScreen + public class BackgroundScreenDefault : BackgroundScreen { + private Background background; + private int currentDisplay; private const int background_count = 5; private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}"; + private Bindable user; + private Bindable skin; + [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api, SkinManager skinManager) { + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); + + user.ValueChanged += _ => Next(); + skin.ValueChanged += _ => Next(); + currentDisplay = RNG.Next(0, background_count); - display(new Background(backgroundName)); + + display(createBackground()); } private void display(Background newBackground) { - Background?.FadeOut(800, Easing.InOutSine); - Background?.Expire(); + background?.FadeOut(800, Easing.InOutSine); + background?.Expire(); - Add(Background = newBackground); + AddInternal(background = newBackground); currentDisplay++; } @@ -37,10 +53,38 @@ namespace osu.Game.Screens.Backgrounds public void Next() { nextTask?.Cancel(); - nextTask = Scheduler.AddDelayed(() => + nextTask = Scheduler.AddDelayed(() => { LoadComponentAsync(createBackground(), display); }, 100); + } + + private Background createBackground() + { + Background newBackground; + + if (user.Value?.IsSupporter ?? false) + newBackground = new SkinnedBackground(skin.Value, backgroundName); + else + newBackground = new Background(backgroundName); + + newBackground.Depth = currentDisplay; + + return newBackground; + } + + private class SkinnedBackground : Background + { + private readonly Skin skin; + + public SkinnedBackground(Skin skin, string fallbackTextureName) + : base(fallbackTextureName) { - LoadComponentAsync(new Background(backgroundName) { Depth = currentDisplay }, display); - }, 100); + this.skin = skin; + } + + [BackgroundDependencyLoader] + private void load() + { + Sprite.Texture = skin.GetTexture("menu-background") ?? Sprite.Texture; + } } } } diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs deleted file mode 100644 index 5e08db8907..0000000000 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Backgrounds -{ - public class BackgroundScreenEmpty : BackgroundScreen - { - } -} diff --git a/osu.Game/Screens/BlurrableBackgroundScreen.cs b/osu.Game/Screens/BlurrableBackgroundScreen.cs deleted file mode 100644 index 4b6abbf063..0000000000 --- a/osu.Game/Screens/BlurrableBackgroundScreen.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Transforms; -using osu.Game.Graphics.Backgrounds; -using osuTK; - -namespace osu.Game.Screens -{ - public abstract class BlurrableBackgroundScreen : BackgroundScreen - { - protected Background Background; - - protected Vector2 BlurTarget; - - public TransformSequence BlurTo(Vector2 sigma, double duration, Easing easing = Easing.None) - => Background?.BlurTo(BlurTarget = sigma, duration, easing); - } -} diff --git a/osu.Game/Screens/Charts/ChartInfo.cs b/osu.Game/Screens/Charts/ChartInfo.cs index 35021709d6..d9a9ad5eeb 100644 --- a/osu.Game/Screens/Charts/ChartInfo.cs +++ b/osu.Game/Screens/Charts/ChartInfo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Screens.Charts { diff --git a/osu.Game/Screens/Charts/ChartListing.cs b/osu.Game/Screens/Charts/ChartListing.cs index a618bba8c4..18bba6433f 100644 --- a/osu.Game/Screens/Charts/ChartListing.cs +++ b/osu.Game/Screens/Charts/ChartListing.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,8 +8,9 @@ namespace osu.Game.Screens.Charts { public class ChartListing : ScreenWhiteBox { - protected override IEnumerable PossibleChildren => new[] { - typeof(ChartInfo) + protected override IEnumerable PossibleChildren => new[] + { + typeof(ChartInfo) }; } } diff --git a/osu.Game/Screens/Direct/OnlineListing.cs b/osu.Game/Screens/Direct/OnlineListing.cs index 9d2d09a695..8376383674 100644 --- a/osu.Game/Screens/Direct/OnlineListing.cs +++ b/osu.Game/Screens/Direct/OnlineListing.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Screens.Direct { diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index 3124482c73..ea3b68e3bd 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; -using osu.Framework.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Screens.Edit { @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit public override int Value { - get { return base.Value; } + get => base.Value; set { if (!VALID_DIVISORS.Contains(value)) diff --git a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs index caf9ba27ff..cb5078a479 100644 --- a/osu.Game/Screens/Edit/Components/BottomBarContainer.cs +++ b/osu.Game/Screens/Edit/Components/BottomBarContainer.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Edit.Components } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap, OsuColour colours) + private void load(IBindable beatmap, OsuColour colours) { Beatmap.BindTo(beatmap); background.Colour = colours.Gray1; diff --git a/osu.Game/Screens/Edit/Components/CircularButton.cs b/osu.Game/Screens/Edit/Components/CircularButton.cs index 98c3a7888d..931c7d03a0 100644 --- a/osu.Game/Screens/Edit/Components/CircularButton.cs +++ b/osu.Game/Screens/Edit/Components/CircularButton.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics.UserInterface; using osuTK; diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index a647a8d75f..752615245e 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -110,7 +110,8 @@ namespace osu.Game.Screens.Edit.Components.Menus { public TextContainer() { - NormalText.TextSize = BoldText.TextSize = 14; + NormalText.Font = NormalText.Font.With(size: 14); + BoldText.Font = BoldText.Font.With(size: 14); NormalText.Margin = BoldText.Margin = new MarginPadding { Horizontal = 10, Vertical = MARGIN_VERTICAL }; } } @@ -174,6 +175,7 @@ namespace osu.Game.Screens.Edit.Components.Menus { if (Item is EditorMenuItemSpacer) return true; + return base.OnHover(e); } @@ -181,6 +183,7 @@ namespace osu.Game.Screens.Edit.Components.Menus { if (Item is EditorMenuItemSpacer) return true; + return base.OnClick(e); } } diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs index 1c9253cce7..0a2c073dcd 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItem.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Game.Graphics.UserInterface; diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs index 17ee88241e..4e75a92e19 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuItemSpacer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Screens.Edit.Components.Menus { diff --git a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs index 56f6056bf9..089da4f222 100644 --- a/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs +++ b/osu.Game/Screens/Edit/Components/Menus/ScreenSelectionTabControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 7d68f32495..8d4ad0efa9 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -7,12 +7,14 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osuTK.Input; namespace osu.Game.Screens.Edit.Components { @@ -34,12 +36,11 @@ namespace osu.Game.Screens.Edit.Components playButton = new IconButton { Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre, + Origin = Anchor.CentreLeft, Scale = new Vector2(1.4f), IconScale = new Vector2(1.4f), - Icon = FontAwesome.fa_play_circle_o, + Icon = FontAwesome.Regular.PlayCircle, Action = togglePause, - Padding = new MarginPadding { Left = 20 } }, new OsuSpriteText { @@ -60,7 +61,19 @@ namespace osu.Game.Screens.Edit.Components } }; - tabs.Current.ValueChanged += newValue => Beatmap.Value.Track.Tempo.Value = newValue; + tabs.Current.ValueChanged += tempo => Beatmap.Value.Track.Tempo.Value = tempo.NewValue; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Space: + togglePause(); + return true; + } + + return base.OnKeyDown(e); } private void togglePause() @@ -75,7 +88,7 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); - playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o; + playButton.Icon = adjustableClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } private class PlaybackTabControl : OsuTabControl @@ -101,7 +114,8 @@ namespace osu.Game.Screens.Edit.Components private readonly OsuSpriteText text; private readonly OsuSpriteText textBold; - public PlaybackTabItem(double value) : base(value) + public PlaybackTabItem(double value) + : base(value) { RelativeSizeAxes = Axes.Both; @@ -114,15 +128,14 @@ namespace osu.Game.Screens.Edit.Components Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Text = $"{value:0%}", - TextSize = 14, + Font = OsuFont.GetFont(size: 14) }, textBold = new OsuSpriteText { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Text = $"{value:0%}", - TextSize = 14, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold), Alpha = 0, }, }; @@ -150,9 +163,9 @@ namespace osu.Game.Screens.Edit.Components private void updateState() { - text.FadeColour(Active || IsHovered ? hoveredColour : normalColour, fade_duration, Easing.OutQuint); - text.FadeTo(Active ? 0 : 1, fade_duration, Easing.OutQuint); - textBold.FadeTo(Active ? 1 : 0, fade_duration, Easing.OutQuint); + text.FadeColour(Active.Value || IsHovered ? hoveredColour : normalColour, fade_duration, Easing.OutQuint); + text.FadeTo(Active.Value ? 0 : 1, fade_duration, Easing.OutQuint); + textBold.FadeTo(Active.Value ? 1 : 0, fade_duration, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs index eb113efbab..70c0cf623e 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/DrawableRadioButton.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -80,10 +81,10 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons { base.LoadComplete(); - button.Selected.ValueChanged += v => + button.Selected.ValueChanged += selected => { updateSelectionState(); - if (v) + if (selected.NewValue) Selected?.Invoke(button); }; @@ -95,16 +96,16 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons if (!IsLoaded) return; - BackgroundColour = button.Selected ? selectedBackgroundColour : defaultBackgroundColour; - bubble.Colour = button.Selected ? selectedBubbleColour : defaultBubbleColour; + BackgroundColour = button.Selected.Value ? selectedBackgroundColour : defaultBackgroundColour; + bubble.Colour = button.Selected.Value ? selectedBubbleColour : defaultBubbleColour; } protected override bool OnClick(ClickEvent e) { - if (button.Selected) + if (button.Selected.Value) return true; - if (!Enabled) + if (!Enabled.Value) return true; button.Selected.Value = true; diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs index c671fa71c2..3692c0437b 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButton.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Screens.Edit.Components.RadioButtons { diff --git a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs index fdb5770ecf..16574c0baf 100644 --- a/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs +++ b/osu.Game/Screens/Edit/Components/RadioButtons/RadioButtonCollection.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; @@ -12,13 +12,15 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons public class RadioButtonCollection : CompositeDrawable { private IReadOnlyList items; + public IReadOnlyList Items { - get { return items; } + get => items; set { if (ReferenceEquals(items, value)) return; + items = value; buttonContainer.Clear(); @@ -42,11 +44,12 @@ namespace osu.Game.Screens.Edit.Components.RadioButtons } private RadioButton currentlySelected; + private void addButton(RadioButton button) { - button.Selected.ValueChanged += v => + button.Selected.ValueChanged += selected => { - if (v) + if (selected.NewValue) { currentlySelected?.Deselect(); currentlySelected = button; diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 129ea2bf7d..0391074b11 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using System; using osu.Framework.Allocation; using osu.Framework.Timing; +using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components { @@ -17,15 +18,13 @@ namespace osu.Game.Screens.Edit.Components public TimeInfoContainer() { - Children = new Drawable[] { trackTimer = new OsuSpriteText { Origin = Anchor.BottomLeft, RelativePositionAxes = Axes.Y, - TextSize = 22, - FixedWidth = true, + Font = OsuFont.GetFont(size: 22, fixedWidth: true), Y = 0.5f, } }; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 3af3fcbdd7..103e39e78a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Beatmaps; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs index 1146037004..ceccbffc9c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Beatmaps; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 4bef22463e..102955657e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osu.Framework.Allocation; @@ -26,12 +26,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts // Consider all non-timing points as the same type cpi.SamplePoints.Select(c => (ControlPoint)c) - .Concat(cpi.EffectPoints) - .Concat(cpi.DifficultyPoints) - .Distinct() - // Non-timing points should not be added where there are timing points - .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time) - .ForEach(addNonTimingPoint); + .Concat(cpi.EffectPoints) + .Concat(cpi.DifficultyPoints) + .Distinct() + // Non-timing points should not be added where there are timing points + .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time) + .ForEach(addNonTimingPoint); } private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint)); diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 3f3826d55c..07d307f293 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override bool OnDragStart(DragStartEvent e) => true; protected override bool OnDragEnd(DragEndEvent e) => true; + protected override bool OnDrag(DragEvent e) { seekToPosition(e.ScreenSpaceMousePosition); 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 104fb76f1c..26d9614631 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osuTK; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -27,12 +27,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts Beatmap.ValueChanged += b => { updateRelativeChildSize(); - LoadBeatmap(b); + LoadBeatmap(b.NewValue); }; } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap) + private void load(IBindable beatmap) { Beatmap.BindTo(beatmap); } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 0b4cba00f3..20db2cac21 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs index 0b5c6018e8..de63df5463 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs index 40955195a4..9c00cce57a 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index aa63b02013..c615656d60 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -94,13 +94,13 @@ namespace osu.Game.Screens.Edit.Compose.Components { new DivisorButton { - Icon = FontAwesome.fa_chevron_left, + Icon = FontAwesome.Solid.ChevronLeft, Action = beatDivisor.Previous }, new DivisorText(beatDivisor), new DivisorButton { - Icon = FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, Action = beatDivisor.Next } }, @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components }, new Drawable[] { - new TextFlowContainer(s => s.TextSize = 14) + new TextFlowContainer(s => s.Font = s.Font.With(size: 14)) { Padding = new MarginPadding { Horizontal = 15 }, Text = "beat snap divisor", @@ -157,12 +157,8 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override void LoadComplete() { base.LoadComplete(); - - beatDivisor.ValueChanged += v => updateText(); - updateText(); + beatDivisor.BindValueChanged(val => Text = $"1/{val.NewValue}", true); } - - private void updateText() => Text = $"1/{beatDivisor.Value}"; } private class DivisorButton : IconButton @@ -175,7 +171,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Small offset to look a bit better centered along with the divisor text Y = 1; - ButtonSize = new Vector2(20); + Size = new Vector2(20); IconScale = new Vector2(0.6f); } @@ -219,9 +215,9 @@ namespace osu.Game.Screens.Edit.Compose.Components AddInternal(marker = new Marker()); - CurrentNumber.ValueChanged += v => + CurrentNumber.ValueChanged += div => { - marker.MoveToX(getMappedPosition(v), 100, Easing.OutQuint); + marker.MoveToX(getMappedPosition(div.NewValue), 100, Easing.OutQuint); marker.Flash(); }; } @@ -238,12 +234,14 @@ namespace osu.Game.Screens.Edit.Compose.Components { case Key.Right: beatDivisor.Next(); - OnUserChange(); + OnUserChange(Current.Value); return true; + case Key.Left: beatDivisor.Previous(); - OnUserChange(); + OnUserChange(Current.Value); return true; + default: return false; } @@ -279,7 +277,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); - OnUserChange(); + OnUserChange(Current.Value); } private float getMappedPosition(float divisor) => (float)Math.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f); @@ -311,18 +309,25 @@ namespace osu.Game.Screens.Edit.Compose.Components { case 2: return colours.BlueLight; + case 4: return colours.Blue; + case 8: return colours.BlueDarker; + case 16: return colours.PurpleDark; + case 3: return colours.YellowLight; + case 6: return colours.Yellow; + case 12: return colours.YellowDarker; + default: return Color4.White; } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 1623ef0100..a1e62cd38b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Container placementBlueprintContainer; private PlacementBlueprint currentPlacement; - private SelectionBox selectionBox; + private SelectionHandler selectionHandler; private IEnumerable selections => selectionBlueprints.Children.Where(c => c.IsAlive); @@ -37,16 +37,16 @@ namespace osu.Game.Screens.Edit.Compose.Components [BackgroundDependencyLoader] private void load() { - selectionBox = composer.CreateSelectionBox(); - selectionBox.DeselectAll = deselectAll; + selectionHandler = composer.CreateSelectionHandler(); + selectionHandler.DeselectAll = deselectAll; var dragBox = new DragBox(select); - dragBox.DragEnd += () => selectionBox.UpdateVisibility(); + dragBox.DragEnd += () => selectionHandler.UpdateVisibility(); InternalChildren = new[] { dragBox, - selectionBox, + selectionHandler, selectionBlueprints = new SelectionBlueprintContainer { RelativeSizeAxes = Axes.Both }, placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both }, dragBox.CreateProxy() @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (currentTool == value) return; + currentTool = value; refreshTool(); @@ -168,19 +169,19 @@ namespace osu.Game.Screens.Edit.Compose.Components private void onBlueprintSelected(SelectionBlueprint blueprint) { - selectionBox.HandleSelected(blueprint); + selectionHandler.HandleSelected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 1); } private void onBlueprintDeselected(SelectionBlueprint blueprint) { - selectionBox.HandleDeselected(blueprint); + selectionHandler.HandleDeselected(blueprint); selectionBlueprints.ChangeChildDepth(blueprint, 0); } - private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionBox.HandleSelectionRequested(blueprint, state); + private void onSelectionRequested(SelectionBlueprint blueprint, InputState state) => selectionHandler.HandleSelectionRequested(blueprint, state); - private void onDragRequested(DragEvent dragEvent) => selectionBox.HandleDrag(dragEvent); + private void onDragRequested(SelectionBlueprint blueprint, DragEvent dragEvent) => selectionHandler.HandleDrag(blueprint, dragEvent); private class SelectionBlueprintContainer : Container { @@ -188,6 +189,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { if (!(x is SelectionBlueprint xBlueprint) || !(y is SelectionBlueprint yBlueprint)) return base.Compare(x, y); + return Compare(xBlueprint, yBlueprint); } diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index aa64c43f3a..143615148a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { Masking = true, BorderColour = Color4.White, - BorderThickness = SelectionBox.BORDER_RADIUS, + BorderThickness = SelectionHandler.BORDER_RADIUS, Child = new Box { RelativeSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs b/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs index b62ea4cf81..4d956336b7 100644 --- a/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs +++ b/osu.Game/Screens/Edit/Compose/Components/EditorPlayfieldBorder.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs similarity index 74% rename from osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs rename to osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 494b2c6d34..11e649168f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -12,26 +12,31 @@ using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { /// - /// A box which surrounds s and provides interactive handles, context menus etc. + /// A component which outlines s and handles movement of selections. /// - public class SelectionBox : CompositeDrawable + public class SelectionHandler : CompositeDrawable { public const float BORDER_RADIUS = 2; + protected IEnumerable SelectedBlueprints => selectedBlueprints; private readonly List selectedBlueprints; + protected IEnumerable SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject.HitObject); + private Drawable outline; [Resolved] private IPlacementHandler placementHandler { get; set; } - public SelectionBox() + public SelectionHandler() { selectedBlueprints = new List(); @@ -59,12 +64,13 @@ namespace osu.Game.Screens.Edit.Compose.Components #region User Input Handling - public void HandleDrag(DragEvent dragEvent) + /// + /// Handles the selected s being dragged. + /// + /// The that received the drag event. + /// The drag event. + public virtual void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent) { - // Todo: Various forms of snapping - - foreach (var blueprint in selectedBlueprints) - blueprint.AdjustPosition(dragEvent); } protected override bool OnKeyDown(KeyDownEvent e) @@ -90,19 +96,19 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Bind an action to deselect all selected blueprints. /// - public Action DeselectAll { private get; set; } + internal Action DeselectAll { private get; set; } /// /// Handle a blueprint becoming selected. /// /// The blueprint. - public void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); + internal void HandleSelected(SelectionBlueprint blueprint) => selectedBlueprints.Add(blueprint); /// /// Handle a blueprint becoming deselected. /// /// The blueprint. - public void HandleDeselected(SelectionBlueprint blueprint) + internal void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); @@ -115,7 +121,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handle a blueprint requesting selection. /// /// The blueprint. - public void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) + /// The input state at the point of selection. + internal void HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) { if (state.Keyboard.ControlPressed) { @@ -139,7 +146,7 @@ namespace osu.Game.Screens.Edit.Compose.Components #endregion /// - /// Updates whether this is visible. + /// Updates whether this is visible. /// internal void UpdateVisibility() { @@ -160,8 +167,6 @@ namespace osu.Game.Screens.Edit.Compose.Components var topLeft = new Vector2(float.MaxValue, float.MaxValue); var bottomRight = new Vector2(float.MinValue, float.MinValue); - bool hasSelection = false; - foreach (var blueprint in selectedBlueprints) { topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(blueprint.SelectionQuad.TopLeft)); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs index 620fc255c3..0d4c48b5f6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/CentreMarker.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 0c626f2c54..748c9e2ba3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private WaveformGraph waveform; [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap, IAdjustableClock adjustableClock, OsuColour colours) + private void load(IBindable beatmap, IAdjustableClock adjustableClock, OsuColour colours) { this.adjustableClock = adjustableClock; @@ -49,13 +49,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // We don't want the centre marker to scroll AddInternal(new CentreMarker()); - WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); + WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); Beatmap.BindTo(beatmap); Beatmap.BindValueChanged(b => { - waveform.Waveform = b.Waveform; - track = b.Track; + waveform.Waveform = b.NewValue.Waveform; + track = b.NewValue.Track; }, true); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 1e16a3264c..863a120fc3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; @@ -90,8 +91,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.fa_search_plus, - Action = () => timeline.Zoom++ + Icon = FontAwesome.Solid.SearchPlus, + Action = () => changeZoom(1) }, new TimelineButton { @@ -99,8 +100,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Y, Height = 0.5f, - Icon = FontAwesome.fa_search_minus, - Action = () => timeline.Zoom-- + Icon = FontAwesome.Solid.SearchMinus, + Action = () => changeZoom(-1) }, } } @@ -124,5 +125,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline timeline.WaveformVisible.BindTo(waveformCheckbox.Current); } + + private void changeZoom(float change) => timeline.Zoom += change; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs index df482fc1d9..8865bf31ea 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineButton.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osuTK; @@ -17,10 +18,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action Action; public readonly BindableBool Enabled = new BindableBool(true); - public FontAwesome Icon + public IconUsage Icon { - get { return button.Icon; } - set { button.Icon = value; } + get => button.Icon; + set => button.Icon = value; } private readonly IconButton button; @@ -30,14 +31,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline InternalChild = button = new TimelineIconButton { Action = () => Action?.Invoke() }; button.Enabled.BindTo(Enabled); - Width = button.ButtonSize.X; + Width = button.Width; } protected override void Update() { base.Update(); - button.ButtonSize = new Vector2(button.ButtonSize.X, DrawHeight); + button.Size = new Vector2(button.Width, DrawHeight); } private class TimelineIconButton : IconButton diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 71cea595d0..cffb6bedf3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; @@ -7,11 +7,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Transforms; using osu.Framework.Input.Events; using osu.Framework.MathUtils; +using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - public class ZoomableScrollContainer : ScrollContainer + public class ZoomableScrollContainer : OsuScrollContainer { /// /// The time to zoom into/out of a point. @@ -47,6 +48,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { if (value < 1) throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value)); + minZoom = value; if (Zoom < value) @@ -66,6 +68,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { if (value < 1) throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value)); + maxZoom = value; if (Zoom > value) @@ -90,11 +93,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - protected override void Update() + protected override void LoadComplete() { - base.Update(); + base.LoadComplete(); - zoomedContent.Width = DrawWidth * currentZoom; + // This width only gets updated on the application of a transform, so this needs to be initialized here. + updateZoomedContentWidth(); } protected override bool OnScroll(ScrollEvent e) @@ -107,7 +111,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline return true; } + private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; + private float zoomTarget = 1; + private void setZoomTarget(float newZoom, float focusPoint) { zoomTarget = MathHelper.Clamp(newZoom, MinZoom, MaxZoom); @@ -135,7 +142,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly float scrollOffset; /// - /// Transforms to a new value. + /// Transforms to a new value. /// /// The focus point in absolute coordinates local to the content. /// The size of the content. @@ -166,6 +173,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; d.currentZoom = newZoom; + + d.updateZoomedContentWidth(); + // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. + // TODO: Make sure draw size gets invalidated properly on the framework side, and remove this once it is. + d.Invalidate(Invalidation.DrawSize); d.ScrollTo(targetOffset, false); } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 91332aa94f..5699ef0a84 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; using osu.Framework.Allocation; @@ -100,6 +100,7 @@ namespace osu.Game.Screens.Edit.Compose }; var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance(); + if (ruleset == null) { Logger.Log("Beatmap doesn't have a ruleset assigned."); @@ -108,6 +109,7 @@ namespace osu.Game.Screens.Edit.Compose } composer = ruleset.CreateHitObjectComposer(); + if (composer == null) { Logger.Log($"Ruleset {ruleset.Description} doesn't support hitobject composition."); diff --git a/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs index f93b294536..47a4277430 100644 --- a/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs +++ b/osu.Game/Screens/Edit/Compose/IPlacementHandler.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Objects; diff --git a/osu.Game/Screens/Edit/Design/DesignScreen.cs b/osu.Game/Screens/Edit/Design/DesignScreen.cs index 7c482a9380..2a334e1b30 100644 --- a/osu.Game/Screens/Edit/Design/DesignScreen.cs +++ b/osu.Game/Screens/Edit/Design/DesignScreen.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index c4fb9dc419..89da9ae063 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK.Graphics; @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Platform; @@ -20,15 +21,24 @@ using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Design; +using osuTK.Input; +using System.Collections.Generic; +using osu.Framework; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; +using osu.Game.Users; namespace osu.Game.Screens.Edit { - public class Editor : OsuScreen + public class Editor : OsuScreen, IKeyBindingHandler { protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); - protected override bool HideOverlaysOnEnter => true; - public override bool AllowBeatmapRulesetChange => false; + public override bool AllowBackButton => false; + + public override bool HideOverlaysOnEnter => true; + + public override bool DisallowExternalBeatmapRulesetChanges => true; private Box bottomBackground; private Container screenContainer; @@ -42,6 +52,8 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; private GameHost host; + protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -50,7 +62,7 @@ namespace osu.Game.Screens.Edit { this.host = host; - // TODO: should probably be done at a RulesetContainer level to share logic with Player. + // TODO: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); @@ -60,11 +72,18 @@ namespace osu.Game.Screens.Edit dependencies.Cache(beatDivisor); EditorMenuBar menuBar; - TimeInfoContainer timeInfo; - SummaryTimeline timeline; - PlaybackControl playback; - Children = new[] + var fileMenuItems = new List(); + + if (RuntimeInfo.IsDesktop) + { + fileMenuItems.Add(new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap)); + fileMenuItems.Add(new EditorMenuItemSpacer()); + } + + fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); + + InternalChildren = new[] { new Container { @@ -91,12 +110,7 @@ namespace osu.Game.Screens.Edit { new MenuItem("File") { - Items = new[] - { - new EditorMenuItem("Export", MenuItemType.Standard, exportBeatmap), - new EditorMenuItemSpacer(), - new EditorMenuItem("Exit", MenuItemType.Standard, Exit) - } + Items = fileMenuItems } } } @@ -157,29 +171,20 @@ namespace osu.Game.Screens.Edit bottomBackground.Colour = colours.Gray2; } - private void exportBeatmap() + protected override bool OnKeyDown(KeyDownEvent e) { - host.OpenFileExternally(Beatmap.Value.Save()); - } - - private void onModeChanged(EditorScreenMode mode) - { - currentScreen?.Exit(); - - switch (mode) + switch (e.Key) { - case EditorScreenMode.Compose: - currentScreen = new ComposeScreen(); - break; - case EditorScreenMode.Design: - currentScreen = new DesignScreen(); - break; - default: - currentScreen = new EditorScreen(); - break; + case Key.Left: + seek(e, -1); + return true; + + case Key.Right: + seek(e, 1); + return true; } - LoadComponentAsync(currentScreen, screenContainer.Add); + return base.OnKeyDown(e); } private double scrollAccumulation; @@ -193,9 +198,9 @@ namespace osu.Game.Screens.Edit while (Math.Abs(scrollAccumulation) > precision) { if (scrollAccumulation > 0) - clock.SeekBackward(!clock.IsRunning); + seek(e, -1); else - clock.SeekForward(!clock.IsRunning); + seek(e, 1); scrollAccumulation = scrollAccumulation < 0 ? Math.Min(0, scrollAccumulation + precision) : Math.Max(0, scrollAccumulation - precision); } @@ -203,28 +208,78 @@ namespace osu.Game.Screens.Edit return true; } - protected override void OnResuming(Screen last) + public bool OnPressed(GlobalAction action) + { + if (action == GlobalAction.Back) + { + // as we don't want to display the back button, manual handling of exit action is required. + this.Exit(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + + public override void OnResuming(IScreen last) { Beatmap.Value.Track?.Stop(); base.OnResuming(last); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); Background.FadeColour(Color4.DarkGray, 500); Beatmap.Value.Track?.Stop(); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { Background.FadeColour(Color4.White, 500); + if (Beatmap.Value.Track != null) { Beatmap.Value.Track.Tempo.Value = 1; Beatmap.Value.Track.Start(); } + return base.OnExiting(next); } + + private void exportBeatmap() => host.OpenFileExternally(Beatmap.Value.Save()); + + private void onModeChanged(ValueChangedEvent e) + { + currentScreen?.Exit(); + + switch (e.NewValue) + { + case EditorScreenMode.Compose: + currentScreen = new ComposeScreen(); + break; + + case EditorScreenMode.Design: + currentScreen = new DesignScreen(); + break; + + default: + currentScreen = new EditorScreen(); + break; + } + + LoadComponentAsync(currentScreen, screenContainer.Add); + } + + private void seek(UIEvent e, int direction) + { + double amount = e.ShiftPressed ? 2 : 1; + + if (direction < 1) + clock.SeekBackward(!clock.IsRunning, amount); + else + clock.SeekForward(!clock.IsRunning, amount); + } } } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 465e3a43e2..24fb561f04 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit public bool SeekSnapped(double position) { var timingPoint = ControlPointInfo.TimingPointAt(position); - double beatSnapLength = timingPoint.BeatLength / beatDivisor; + double beatSnapLength = timingPoint.BeatLength / beatDivisor.Value; // We will be snapping to beats within the timing point position -= timingPoint.Time; @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to // the next timing point's start time - var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); if (position > nextTimingPoint?.Time) position = nextTimingPoint.Time; @@ -68,17 +68,22 @@ namespace osu.Game.Screens.Edit /// Seeks backwards by one beat length. /// /// Whether to snap to the closest beat after seeking. - public void SeekBackward(bool snapped = false) => seek(-1, snapped); + /// The relative amount (magnitude) which should be seeked. + public void SeekBackward(bool snapped = false, double amount = 1) => seek(-1, snapped, amount); /// /// Seeks forwards by one beat length. /// /// Whether to snap to the closest beat after seeking. - public void SeekForward(bool snapped = false) => seek(1, snapped); + /// The relative amount (magnitude) which should be seeked. + public void SeekForward(bool snapped = false, double amount = 1) => seek(1, snapped, amount); - private void seek(int direction, bool snapped) + private void seek(int direction, bool snapped, double amount = 1) { + if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); + var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); + if (direction < 0 && timingPoint.Time == CurrentTime) { // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into @@ -87,7 +92,7 @@ namespace osu.Game.Screens.Edit timingPoint = ControlPointInfo.TimingPoints[--activeIndex]; } - double seekAmount = timingPoint.BeatLength / beatDivisor; + double seekAmount = timingPoint.BeatLength / beatDivisor.Value * amount; double seekTime = CurrentTime + seekAmount * direction; if (!snapped || ControlPointInfo.TimingPoints.Count == 0) @@ -119,7 +124,7 @@ namespace osu.Game.Screens.Edit if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) seekTime = timingPoint.Time; - var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 3a8fc3ef80..045e5a1226 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Edit } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap) + private void load(IBindable beatmap) { Beatmap.BindTo(beatmap); } diff --git a/osu.Game/Screens/Edit/EditorScreenMode.cs b/osu.Game/Screens/Edit/EditorScreenMode.cs index 17de6c4125..12cfcc605b 100644 --- a/osu.Game/Screens/Edit/EditorScreenMode.cs +++ b/osu.Game/Screens/Edit/EditorScreenMode.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; @@ -9,10 +9,13 @@ namespace osu.Game.Screens.Edit { [Description("setup")] SongSetup, + [Description("compose")] Compose, + [Description("design")] Design, + [Description("timing")] Timing, } diff --git a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs index eae080bc81..1c53fc7088 100644 --- a/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/Components/LabelledComponents/LabelledTextBox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -38,8 +38,8 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents public float LabelTextSize { - get => label.TextSize; - set => label.TextSize = value; + get => label.Font.Size; + set => label.Font = label.Font.With(size: value); } public string PlaceholderText @@ -60,14 +60,7 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents set => label.Colour = value; } - public Color4 BackgroundColour - { - get => content.Colour; - set => content.Colour = value; - } - private readonly OsuTextBox textBox; - private readonly Container content; private readonly OsuSpriteText label; public LabelledTextBox() @@ -103,8 +96,7 @@ namespace osu.Game.Screens.Edit.Setup.Components.LabelledComponents Origin = Anchor.TopLeft, Padding = new MarginPadding { Left = default_label_left_padding, Top = default_label_top_padding }, Colour = Color4.White, - TextSize = default_label_text_size, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(size: default_label_text_size, weight: FontWeight.Bold), }, textBox = new OsuTextBox { diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs new file mode 100644 index 0000000000..9fc907c2a4 --- /dev/null +++ b/osu.Game/Screens/IOsuScreen.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Overlays; +using osu.Game.Rulesets; + +namespace osu.Game.Screens +{ + public interface IOsuScreen : IScreen + { + /// + /// Whether the beatmap or ruleset should be allowed to be changed by the user or game. + /// Used to mark exclusive areas where this is strongly prohibited, like gameplay. + /// + bool DisallowExternalBeatmapRulesetChanges { get; } + + /// + /// Whether the user can exit this this by pressing the back button. + /// + bool AllowBackButton { get; } + + /// + /// Whether a top-level component should be allowed to exit the current screen to, for example, + /// complete an import. Note that this can be overridden by a user if they specifically request. + /// + bool AllowExternalScreenChange { get; } + + /// + /// Whether this allows the cursor to be displayed. + /// + bool CursorVisible { get; } + + /// + /// Whether all overlays should be hidden when this screen is entered or resumed. + /// + bool HideOverlaysOnEnter { get; } + + /// + /// Whether overlays should be able to be opened once this screen is entered or resumed. + /// + OverlayActivation InitialOverlayActivationMode { get; } + + /// + /// The amount of parallax to be applied while this screen is displayed. + /// + float BackgroundParallaxAmount { get; } + + Bindable Beatmap { get; } + + Bindable Ruleset { get; } + } +} diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index a59a8a77ef..8add730c4e 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -9,20 +9,13 @@ using osu.Framework.Graphics.Shaders; using osu.Game.Screens.Menu; using osuTK; using osu.Framework.Screens; -using osu.Game.Overlays; namespace osu.Game.Screens { - public class Loader : OsuScreen + public class Loader : StartupScreen { private bool showDisclaimer; - protected override bool HideOverlaysOnEnter => true; - - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; - - protected override bool AllowBackButton => false; - public Loader() { ValidForResume = false; @@ -34,6 +27,7 @@ namespace osu.Game.Screens logo.BeatMatching = false; logo.Triangles = false; + logo.RelativePositionAxes = Axes.None; logo.Origin = Anchor.BottomRight; logo.Anchor = Anchor.BottomRight; logo.Position = new Vector2(-40); @@ -55,11 +49,11 @@ namespace osu.Game.Screens protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - LoadComponentAsync(precompiler = CreateShaderPrecompiler(), Add); + LoadComponentAsync(precompiler = CreateShaderPrecompiler(), AddInternal); LoadComponentAsync(loadableScreen = CreateLoadableScreen()); checkIfLoaded(); @@ -73,7 +67,7 @@ namespace osu.Game.Screens return; } - Push(loadableScreen); + this.Push(loadableScreen); } [BackgroundDependencyLoader] @@ -87,7 +81,7 @@ namespace osu.Game.Screens /// public class ShaderPrecompiler : Drawable { - private readonly List loadTargets = new List(); + private readonly List loadTargets = new List(); public bool FinishedCompiling { get; private set; } @@ -104,7 +98,7 @@ namespace osu.Game.Screens loadTargets.Add(manager.Load(VertexShaderDescriptor.TEXTURE_3, FragmentShaderDescriptor.TEXTURE)); } - protected virtual bool AllLoaded => loadTargets.All(s => s.Loaded); + protected virtual bool AllLoaded => loadTargets.All(s => s.IsLoaded); protected override void Update() { diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index fee5453165..badd1e0549 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework; @@ -9,7 +9,6 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -17,6 +16,8 @@ using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; using osu.Framework.Audio.Track; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps.ControlPoints; @@ -48,7 +49,7 @@ namespace osu.Game.Screens.Menu public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => box.ReceivePositionalInputAt(screenSpacePos); - public Button(string text, string sampleName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) + public Button(string text, string sampleName, IconUsage symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) { this.sampleName = sampleName; this.clickAction = clickAction; @@ -63,6 +64,8 @@ namespace osu.Game.Screens.Menu { box = new Container { + // box needs to be always present to ensure the button is always sized correctly for flow + AlwaysPresent = true, Masking = true, MaskingSmoothness = 2, EdgeEffect = new EdgeEffectParameters @@ -118,7 +121,6 @@ namespace osu.Game.Screens.Menu AllowMultiline = false, Anchor = Anchor.Centre, Origin = Anchor.Centre, - TextSize = 16, Position = new Vector2(0, 35), Text = text } @@ -180,9 +182,9 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio) { - sampleHover = audio.Sample.Get(@"Menu/button-hover"); + sampleHover = audio.Samples.Get(@"Menu/button-hover"); if (!string.IsNullOrEmpty(sampleName)) - sampleClick = audio.Sample.Get($@"Menu/{sampleName}"); + sampleClick = audio.Samples.Get($@"Menu/{sampleName}"); } protected override bool OnMouseDown(MouseDownEvent e) @@ -243,7 +245,7 @@ namespace osu.Game.Screens.Menu public ButtonState State { - get { return state; } + get => state; set { @@ -261,6 +263,7 @@ namespace osu.Game.Screens.Menu box.ScaleTo(new Vector2(0, 1), 500, Easing.OutExpo); this.FadeOut(500); break; + case 1: box.ScaleTo(new Vector2(0, 1), 400, Easing.InSine); this.FadeOut(800); @@ -268,11 +271,13 @@ namespace osu.Game.Screens.Menu } break; + case ButtonState.Expanded: const int expand_duration = 500; box.ScaleTo(new Vector2(1, 1), expand_duration, Easing.OutExpo); this.FadeIn(expand_duration / 6f); break; + case ButtonState.Exploded: const int explode_duration = 200; box.ScaleTo(new Vector2(2, 1), explode_duration, Easing.OutExpo); @@ -295,10 +300,12 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Initial: State = ButtonState.Contracted; break; + case ButtonSystemState.EnteringMode: ContractStyle = 1; State = ButtonState.Contracted; break; + default: if (value == VisibleState) State = ButtonState.Expanded; diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs index 88fdb0a05e..d59996a4eb 100644 --- a/osu.Game/Screens/Menu/ButtonArea.cs +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework; @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.X, Size = new Vector2(1, BUTTON_AREA_HEIGHT), Alpha = 0, + AlwaysPresent = true, // Always needs to be present for correct tracking on initial -> toplevel state change Children = new Drawable[] { buttonAreaBackground = new ButtonAreaBackground(), @@ -55,11 +56,12 @@ namespace osu.Game.Screens.Menu case ButtonSystemState.Exit: case ButtonSystemState.Initial: case ButtonSystemState.EnteringMode: - State = Visibility.Hidden; + Hide(); break; + case ButtonSystemState.TopLevel: case ButtonSystemState.Play: - State = Visibility.Visible; + Show(); break; } @@ -80,6 +82,10 @@ namespace osu.Game.Screens.Menu } } + public override void Hide() => State = Visibility.Hidden; + + public override void Show() => State = Visibility.Visible; + public event Action StateChanged; private class ButtonAreaBackground : Box, IStateful @@ -109,6 +115,7 @@ namespace osu.Game.Screens.Menu case ButtonAreaBackgroundState.Flat: this.ScaleTo(new Vector2(2, 0), 300, Easing.InSine); break; + case ButtonAreaBackgroundState.Normal: this.ScaleTo(Vector2.One, 400, Easing.OutQuint); break; @@ -127,6 +134,7 @@ namespace osu.Game.Screens.Menu default: State = ButtonAreaBackgroundState.Normal; break; + case ButtonSystemState.Initial: case ButtonSystemState.Exit: case ButtonSystemState.EnteringMode: diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f54e3d90a6..5aa244cc06 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,14 +8,22 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Input; using osu.Game.Input.Bindings; +using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -26,6 +34,8 @@ namespace osu.Game.Screens.Menu { public event Action StateChanged; + private readonly IBindable isIdle = new BindableBool(); + public Action OnEdit; public Action OnExit; public Action OnDirect; @@ -39,6 +49,10 @@ namespace osu.Game.Screens.Menu private OsuLogo logo; + /// + /// Assign the that this ButtonSystem should manage the position of. + /// + /// The instance of the logo to be assigned. If null, we are suspending from the screen that uses this ButtonSystem. public void SetOsuLogo(OsuLogo logo) { this.logo = logo; @@ -52,9 +66,13 @@ namespace osu.Game.Screens.Menu updateLogoState(); } + else + { + // We should stop tracking as the facade is now out of scope. + logoTrackingContainer.StopTracking(); + } } - private readonly Drawable iconFacade; private readonly ButtonArea buttonArea; private readonly Button backButton; @@ -64,48 +82,102 @@ namespace osu.Game.Screens.Menu private SampleChannel sampleBack; + private readonly LogoTrackingContainer logoTrackingContainer; + public ButtonSystem() { RelativeSizeAxes = Axes.Both; - Child = buttonArea = new ButtonArea(); - - buttonArea.AddRange(new[] + Child = logoTrackingContainer = new LogoTrackingContainer { - new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) + RelativeSizeAxes = Axes.Both, + Child = buttonArea = new ButtonArea() + }; + + buttonArea.AddRange(new Drawable[] + { + new Button(@"settings", string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + backButton = new Button(@"back", @"button-back-select", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleState = ButtonSystemState.Play, }, - iconFacade = new Container //need a container to make the osu! icon flow properly. - { - Size = new Vector2(0, ButtonArea.BUTTON_AREA_HEIGHT) - } + logoTrackingContainer.LogoFacade.With(d => d.Scale = new Vector2(0.74f)) }); - buttonArea.Flow.CentreTarget = iconFacade; + buttonArea.Flow.CentreTarget = logoTrackingContainer.LogoFacade; + } - buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M)); - buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved(CanBeNull = true)] + private NotificationOverlay notifications { get; set; } + + [Resolved(CanBeNull = true)] + private LoginOverlay loginOverlay { get; set; } + + [BackgroundDependencyLoader(true)] + private void load(AudioManager audio, IdleTracker idleTracker, GameHost host) + { + buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M)); + buttonsPlay.Add(new Button(@"chart", @"button-generic-select", OsuIcon.Charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsTopLevel.Add(new Button(@"play", @"button-play-select", FontAwesome.fa_osu_logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); - buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", FontAwesome.fa_osu_edit_o, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); - buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", FontAwesome.fa_osu_chevron_down_o, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); - buttonsTopLevel.Add(new Button(@"exit", string.Empty, FontAwesome.fa_osu_cross_o, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); + buttonsTopLevel.Add(new Button(@"play", @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); + buttonsTopLevel.Add(new Button(@"osu!editor", @"button-generic-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => OnEdit?.Invoke(), 0, Key.E)); + buttonsTopLevel.Add(new Button(@"osu!direct", @"button-direct-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnDirect?.Invoke(), 0, Key.D)); + + if (host.CanExit) + buttonsTopLevel.Add(new Button(@"exit", string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsTopLevel); + + buttonArea.ForEach(b => + { + if (b is Button) + { + b.Origin = Anchor.CentreLeft; + b.Anchor = Anchor.CentreLeft; + } + }); + + isIdle.ValueChanged += idle => updateIdleState(idle.NewValue); + + if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); + + sampleBack = audio.Samples.Get(@"Menu/button-back-select"); } - private OsuGame game; - - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuGame game) + private void onMulti() { - this.game = game; - sampleBack = audio.Sample.Get(@"Menu/button-back-select"); + if (!api.IsLoggedIn) + { + notifications?.Post(new SimpleNotification + { + Text = "You gotta be logged in to multi 'yo!", + Icon = FontAwesome.Solid.Globe, + Activated = () => + { + loginOverlay?.Show(); + return true; + } + }); + + return; + } + + OnMulti?.Invoke(); + } + + private void updateIdleState(bool isIdle) + { + if (isIdle && State != ButtonSystemState.Exit && State != ButtonSystemState.EnteringMode) + State = ButtonSystemState.Initial; } public bool OnPressed(GlobalAction action) @@ -114,9 +186,11 @@ namespace osu.Game.Screens.Menu { case GlobalAction.Back: return goBack(); + case GlobalAction.Select: logo?.Click(); return true; + default: return false; } @@ -132,9 +206,11 @@ namespace osu.Game.Screens.Menu State = ButtonSystemState.Initial; sampleBack?.Play(); return true; + case ButtonSystemState.Play: backButton.Click(); return true; + default: return false; } @@ -146,12 +222,15 @@ namespace osu.Game.Screens.Menu { default: return true; + case ButtonSystemState.Initial: State = ButtonSystemState.TopLevel; return true; + case ButtonSystemState.TopLevel: buttonsTopLevel.First().Click(); return false; + case ButtonSystemState.Play: buttonsPlay.First().Click(); return false; @@ -165,7 +244,7 @@ namespace osu.Game.Screens.Menu public ButtonSystemState State { - get { return state; } + get => state; set { @@ -206,79 +285,57 @@ namespace osu.Game.Screens.Menu logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = false; + logoTrackingContainer.StopTracking(); game?.Toolbar.Hide(); 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 ButtonSystemState.TopLevel: case ButtonSystemState.Play: switch (lastState) { case ButtonSystemState.TopLevel: // coming from toplevel to play break; + case ButtonSystemState.Initial: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; bool impact = logo.Scale.X > 0.6f; if (lastState == ButtonSystemState.Initial) logo.ScaleTo(0.5f, 200, Easing.In); - logo.MoveTo(logoTrackingPosition, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); + logoTrackingContainer.StartTracking(logo, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In); logoDelayedAction?.Cancel(); logoDelayedAction = Scheduler.AddDelayed(() => { - logoTracking = true; - if (impact) logo.Impact(); game?.Toolbar.Show(); }, 200); break; + default: logo.ClearTransforms(targetMember: nameof(Position)); - logo.RelativePositionAxes = Axes.None; - logoTracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); logo.ScaleTo(0.5f, 200, Easing.OutQuint); break; } break; + case ButtonSystemState.EnteringMode: - logoTracking = true; + logoTrackingContainer.StartTracking(logo, 0, Easing.In); break; } } - - private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre); - - private bool logoTracking; - - protected override void Update() - { - //if (OsuGame.IdleTime > 6000 && State != MenuState.Exit) - // State = MenuState.Initial; - - base.Update(); - - if (logo != null) - { - if (logoTracking && logo.RelativePositionAxes == Axes.None && iconFacade.IsLoaded) - logo.Position = logoTrackingPosition; - - iconFacade.Width = logo.SizeForFlow * 0.5f; - } - } } public enum ButtonSystemState diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index c9ad519897..97231a1331 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -1,30 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; using osuTK; using osuTK.Graphics; -using osu.Game.Overlays; +using osu.Game.Users; namespace osu.Game.Screens.Menu { - public class Disclaimer : OsuScreen + public class Disclaimer : StartupScreen { private Intro intro; private SpriteIcon icon; private Color4 iconColour; private LinkFlowContainer textFlow; + private LinkFlowContainer supportFlow; - protected override bool HideOverlaysOnEnter => true; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + private Drawable heart; - public override bool CursorVisible => false; + private const float icon_y = -85; + private const float icon_size = 30; - private const float icon_y = -0.09f; + private readonly Bindable currentUser = new Bindable(); public Disclaimer() { @@ -32,52 +40,107 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, IAPIProvider api) { - Children = new Drawable[] + InternalChildren = new Drawable[] { icon = new SpriteIcon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.fa_warning, - Size = new Vector2(30), - RelativePositionAxes = Axes.Both, + Icon = FontAwesome.Solid.ExclamationTriangle, + Size = new Vector2(icon_size), Y = icon_y, }, - textFlow = new LinkFlowContainer + new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(50), - TextAnchor = Anchor.TopCentre, + Direction = FillDirection.Vertical, + Y = icon_y + icon_size, Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(0, 2), + Origin = Anchor.TopCentre, + Children = new Drawable[] + { + textFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(0, 2), + }, + supportFlow = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + Spacing = new Vector2(0, 2), + }, + } } }; - textFlow.AddText("This is an ", t => - { - t.TextSize = 30; - t.Font = @"Exo2.0-Light"; - }); - textFlow.AddText("early development build", t => - { - t.TextSize = 30; - t.Font = @"Exo2.0-SemiBold"; - }); + textFlow.AddText("This is an ", t => t.Font = t.Font.With(Typeface.Exo, 30, FontWeight.Light)); + textFlow.AddText("early development build", t => t.Font = t.Font.With(Typeface.Exo, 30, FontWeight.SemiBold)); - textFlow.AddParagraph("Don't expect everything to work perfectly."); - textFlow.AddParagraph(""); - textFlow.AddParagraph("Detailed bug reports are welcomed via github issues."); - textFlow.AddParagraph(""); + textFlow.AddParagraph("Things may not work as expected", t => t.Font = t.Font.With(size: 20)); + textFlow.NewParagraph(); - textFlow.AddText("Visit "); - textFlow.AddLink("discord.gg/ppy", "https://discord.gg/ppy"); - textFlow.AddText(" if you want to help out or follow progress!"); + Action format = t => t.Font = OsuFont.GetFont(size: 15, weight: FontWeight.SemiBold); + + textFlow.AddParagraph("Detailed bug reports are welcomed via github issues.", format); + textFlow.NewParagraph(); + + textFlow.AddText("Visit ", format); + textFlow.AddLink("discord.gg/ppy", "https://discord.gg/ppy", creationParameters: format); + textFlow.AddText(" to help out or follow progress!", format); + + textFlow.NewParagraph(); + textFlow.NewParagraph(); + textFlow.NewParagraph(); iconColour = colours.Yellow; + + currentUser.BindTo(api.LocalUser); + currentUser.BindValueChanged(e => + { + supportFlow.Children.ForEach(d => d.FadeOut().Expire()); + + if (e.NewValue.IsSupporter) + { + supportFlow.AddText("Thank you for supporting osu!", format); + } + else + { + supportFlow.AddText("Consider becoming an ", format); + supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", creationParameters: format); + supportFlow.AddText(" to help support the game", format); + } + + heart = supportFlow.AddIcon(FontAwesome.Solid.Heart, t => + { + t.Padding = new MarginPadding { Left = 5 }; + t.Font = t.Font.With(size: 12); + t.Origin = Anchor.Centre; + t.Colour = colours.Pink; + }).First(); + + if (IsLoaded) + animateHeart(); + + if (supportFlow.IsPresent) + supportFlow.FadeInFromZero(500); + }, true); + } + + private void animateHeart() + { + heart.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); } protected override void LoadComplete() @@ -86,19 +149,28 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(intro = new Intro()); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - icon.Delay(1500).FadeColour(iconColour, 200, Easing.OutQuint); - icon.Delay(1500).MoveToY(icon_y * 1.1f, 100, Easing.OutCirc).Then().MoveToY(icon_y, 100, Easing.InCirc); + icon.Delay(1000).FadeColour(iconColour, 200, Easing.OutQuint); + icon.Delay(1000) + .MoveToY(icon_y * 1.1f, 160, Easing.OutCirc) + .RotateTo(-10, 160, Easing.OutCirc) + .Then() + .MoveToY(icon_y, 160, Easing.InCirc) + .RotateTo(0, 160, Easing.InCirc); - Content + supportFlow.FadeOut().Delay(2000).FadeIn(500); + + animateHeart(); + + this .FadeInFromZero(500) .Then(5500) .FadeOut(250) .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(d => Push(intro)); + .Finally(d => this.Push(intro)); } } } diff --git a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs index 4ada46749c..519834859d 100644 --- a/osu.Game/Screens/Menu/ExitConfirmOverlay.cs +++ b/osu.Game/Screens/Menu/ExitConfirmOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs index 1c6fe37b1b..8310ab06eb 100644 --- a/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs +++ b/osu.Game/Screens/Menu/FlowContainerWithOrigin.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Menu if (CentreTarget == null) return base.OriginPosition; - return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2; + return CentreTarget.DrawPosition + CentreTarget.DrawSize / 2 * CentreTarget.Scale; } } } diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index fa01411a0f..c52e8541c5 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.MathUtils; @@ -15,11 +15,10 @@ using osu.Game.IO.Archives; using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; -using osu.Game.Overlays; namespace osu.Game.Screens.Menu { - public class Intro : OsuScreen + public class Intro : StartupScreen { private const string menu_music_beatmap_hash = "3c8b1fcc9434dbb29e2fb613d3b9eada9d7bb6c125ceb32396c3b53437280c83"; @@ -28,18 +27,11 @@ 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 OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; - - public override bool CursorVisible => false; - - protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty(); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); private Bindable menuVoice; private Bindable menuMusic; @@ -47,16 +39,14 @@ namespace osu.Game.Screens.Menu private WorkingBeatmap introBeatmap; [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game, BindableBeatmap beatmap) + private void load(AudioManager audio, OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game) { - this.beatmap.BindTo(beatmap); - menuVoice = config.GetBindable(OsuSetting.MenuVoice); menuMusic = config.GetBindable(OsuSetting.MenuMusic); BeatmapSetInfo setInfo = null; - if (!menuMusic) + if (!menuMusic.Value) { var sets = beatmaps.GetAllUsableBeatmapSets(); if (sets.Count > 0) @@ -70,7 +60,7 @@ namespace osu.Game.Screens.Menu if (setInfo == null) { // we need to import the default menu background beatmap - setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")); + setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")).Result; setInfo.Protected = true; beatmaps.Update(setInfo); @@ -80,8 +70,8 @@ namespace osu.Game.Screens.Menu introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); track = introBeatmap.Track; - welcome = audio.Sample.Get(@"welcome"); - seeya = audio.Sample.Get(@"seeya"); + welcome = audio.Samples.Get(@"welcome"); + seeya = audio.Samples.Get(@"seeya"); } private const double delay_step_one = 2300; @@ -95,15 +85,15 @@ namespace osu.Game.Screens.Menu if (!resuming) { - beatmap.Value = introBeatmap; + Beatmap.Value = introBeatmap; - if (menuVoice) + if (menuVoice.Value) 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) + if (menuMusic.Value) track.Start(); LoadComponentAsync(mainMenu = new MainMenu()); @@ -111,12 +101,11 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(delegate { DidLoadMenu = true; - Push(mainMenu); + this.Push(mainMenu); }, delay_step_one); }, delay_step_two); } - logo.RelativePositionAxes = Axes.Both; logo.Colour = Color4.White; logo.Ripple = false; @@ -145,33 +134,32 @@ namespace osu.Game.Screens.Menu } } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { - Content.FadeOut(300); + this.FadeOut(300); base.OnSuspending(next); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { //cancel exiting if we haven't loaded the menu yet. return !DidLoadMenu; } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { - if (!(last is MainMenu)) - Content.FadeIn(300); + this.FadeIn(300); double fadeOutTime = EXIT_DELAY; //we also handle the exit transition. - if (menuVoice) + if (menuVoice.Value) seeya.Play(); else fadeOutTime = 500; - Scheduler.AddDelayed(Exit, fadeOutTime); + Scheduler.AddDelayed(this.Exit, fadeOutTime); - //don't want to fade out completely else we will stop running updates and shit will hit the fan. + //don't want to fade out completely else we will stop running updates. Game.FadeTo(0.01f, fadeOutTime); base.OnResuming(last); diff --git a/osu.Game/Screens/Menu/IntroSequence.cs b/osu.Game/Screens/Menu/IntroSequence.cs index 91dc7937f8..093d01f12d 100644 --- a/osu.Game/Screens/Menu/IntroSequence.cs +++ b/osu.Game/Screens/Menu/IntroSequence.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using osuTK; @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, AutoSizeAxes = Axes.Both, - Children = new [] + Children = new[] { lineTopLeft = new Box { @@ -102,9 +102,8 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Text = "welcome", Padding = new MarginPadding { Bottom = 10 }, - Font = @"Exo2.0-Light", + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), Alpha = 0, - TextSize = 42, Spacing = new Vector2(5), }, new CircularContainer diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 70a01a5c9f..c6de5857c2 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -1,10 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; -using osuTK.Graphics.ES30; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Batches; using osu.Framework.Graphics.Colour; @@ -14,8 +12,13 @@ using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; namespace osu.Game.Screens.Menu { @@ -64,21 +67,28 @@ namespace osu.Game.Screens.Menu private readonly float[] frequencyAmplitudes = new float[256]; - private Shader shader; + private IShader shader; private readonly Texture texture; + private Bindable user; + private Bindable skin; + public LogoVisualisation() { texture = Texture.WhitePixel; - AccentColour = new Color4(1, 1, 1, 0.2f); Blending = BlendingMode.Additive; } [BackgroundDependencyLoader] - private void load(ShaderManager shaders, IBindableBeatmap beatmap) + private void load(ShaderManager shaders, IBindable beatmap, IAPIProvider api, SkinManager skinManager) { this.beatmap.BindTo(beatmap); shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); + + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); } private void updateAmplitudes() @@ -108,6 +118,16 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(updateAmplitudes, time_between_updates); } + private void updateColour() + { + Color4 defaultColour = Color4.White.Opacity(0.2f); + + if (user.Value?.IsSupporter ?? false) + AccentColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? defaultColour; + else + AccentColour = defaultColour; + } + protected override void LoadComplete() { base.LoadComplete(); @@ -119,6 +139,7 @@ namespace osu.Game.Screens.Menu base.Update(); float decayFactor = (float)Time.Elapsed * decay_per_milisecond; + for (int i = 0; i < bars_per_visualiser; i++) { //3% of extra bar length to make it a little faster when bar is almost at it's minimum @@ -130,68 +151,66 @@ namespace osu.Game.Screens.Menu Invalidate(Invalidation.DrawNode, shallPropagate: false); } - protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(); - - private readonly VisualiserSharedData sharedData = new VisualiserSharedData(); - - protected override void ApplyDrawNode(DrawNode node) - { - base.ApplyDrawNode(node); - - var visNode = (VisualisationDrawNode)node; - - visNode.Shader = shader; - visNode.Texture = texture; - visNode.Size = DrawSize.X; - visNode.Shared = sharedData; - visNode.Colour = AccentColour; - visNode.AudioData = frequencyAmplitudes; - } - - private class VisualiserSharedData - { - public readonly LinearBatch VertexBatch = new LinearBatch(100 * 4, 10, PrimitiveType.Quads); - } + protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); private class VisualisationDrawNode : DrawNode { - public Shader Shader; - public Texture Texture; - public VisualiserSharedData Shared; - //Asuming the logo is a circle, we don't need a second dimension. - public float Size; + protected new LogoVisualisation Source => (LogoVisualisation)base.Source; - public Color4 Colour; - public float[] AudioData; + private IShader shader; + private Texture texture; + + //Asuming the logo is a circle, we don't need a second dimension. + private float size; + + private Color4 colour; + private float[] audioData; + + private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); + + public VisualisationDrawNode(LogoVisualisation source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize.X; + colour = Source.AccentColour; + audioData = Source.frequencyAmplitudes; + } public override void Draw(Action vertexAction) { base.Draw(vertexAction); - Shader.Bind(); - Texture.TextureGL.Bind(); + shader.Bind(); Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; ColourInfo colourInfo = DrawColourInfo.Colour; - colourInfo.ApplyChild(Colour); + colourInfo.ApplyChild(colour); - if (AudioData != null) + if (audioData != null) { for (int j = 0; j < visualiser_rounds; j++) { for (int i = 0; i < bars_per_visualiser; i++) { - if (AudioData[i] < amplitude_dead_zone) + if (audioData[i] < amplitude_dead_zone) continue; float rotation = MathHelper.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); float rotationCos = (float)Math.Cos(rotation); float rotationSin = (float)Math.Sin(rotation); //taking the cos and sin to the 0..1 range - var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * Size; + var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; - var barSize = new Vector2(Size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * AudioData[i]); + var barSize = new Vector2(size * (float)Math.Sqrt(2 * (1 - Math.Cos(MathHelper.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); //The distance between the position and the sides of the bar. var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); //The distance between the bottom side of the bar and the top side. @@ -204,17 +223,26 @@ namespace osu.Game.Screens.Menu Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) ); - Texture.DrawQuad( + DrawQuad( + texture, rectangle, colourInfo, null, - Shared.VertexBatch.AddAction, + vertexBatch.AddAction, //barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. Vector2.Divide(inflation, barSize.Yx)); } } } - Shader.Unbind(); + + shader.Unbind(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBatch.Dispose(); } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index cafd718055..7e6de54d1b 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -1,51 +1,55 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Input.Events; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Charts; using osu.Game.Screens.Direct; using osu.Game.Screens.Edit; using osu.Game.Screens.Multi; using osu.Game.Screens.Select; -using osu.Game.Screens.Tournament; namespace osu.Game.Screens.Menu { public class MainMenu : OsuScreen { - private readonly ButtonSystem buttons; + private ButtonSystem buttons; - protected override bool HideOverlaysOnEnter => buttons.State == ButtonSystemState.Initial; + public override bool HideOverlaysOnEnter => buttons == null || buttons.State == ButtonSystemState.Initial; - protected override bool AllowBackButton => buttons.State != ButtonSystemState.Initial; + public override bool AllowBackButton => false; + + public override bool AllowExternalScreenChange => true; - private readonly BackgroundScreenDefault background; private Screen songSelect; - private readonly MenuSideFlashes sideFlashes; + private MenuSideFlashes sideFlashes; + + [Resolved] + private GameHost host { get; set; } + + private BackgroundScreenDefault background; protected override BackgroundScreen CreateBackground() => background; - public MainMenu() + [BackgroundDependencyLoader(true)] + private void load(DirectOverlay direct, SettingsOverlay settings) { - background = new BackgroundScreenDefault(); + if (host.CanExit) + AddInternal(new ExitConfirmOverlay { Action = this.Exit }); - Children = new Drawable[] + AddRangeInternal(new Drawable[] { - new ExitConfirmOverlay - { - Action = Exit, - }, new ParallaxContainer { ParallaxAmount = 0.01f, @@ -53,17 +57,17 @@ namespace osu.Game.Screens.Menu { buttons = new ButtonSystem { - OnChart = delegate { Push(new ChartListing()); }, - OnDirect = delegate { Push(new OnlineListing()); }, - OnEdit = delegate { Push(new Editor()); }, + OnChart = delegate { this.Push(new ChartListing()); }, + OnDirect = delegate { this.Push(new OnlineListing()); }, + OnEdit = delegate { this.Push(new Editor()); }, OnSolo = onSolo, - OnMulti = delegate { Push(new Multiplayer()); }, - OnExit = Exit, + OnMulti = delegate { this.Push(new Multiplayer()); }, + OnExit = this.Exit, } } }, sideFlashes = new MenuSideFlashes(), - }; + }); buttons.StateChanged += state => { @@ -71,26 +75,19 @@ namespace osu.Game.Screens.Menu { case ButtonSystemState.Initial: case ButtonSystemState.Exit: - background.FadeColour(Color4.White, 500, Easing.OutSine); + Background.FadeColour(Color4.White, 500, Easing.OutSine); break; + default: - background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); + Background.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine); break; } }; - } - [BackgroundDependencyLoader(true)] - private void load(OsuGame game = null) - { - LoadComponentAsync(background); - - if (game != null) - { - buttons.OnSettings = game.ToggleSettings; - buttons.OnDirect = game.ToggleDirect; - } + buttons.OnSettings = () => settings?.ToggleVisibility(); + buttons.OnDirect = () => direct?.ToggleVisibility(); + LoadComponentAsync(background = new BackgroundScreenDefault()); preloadSongSelect(); } @@ -102,7 +99,7 @@ namespace osu.Game.Screens.Menu public void LoadToSolo() => Schedule(onSolo); - private void onSolo() => Push(consumeSongSelect()); + private void onSolo() => this.Push(consumeSongSelect()); private Screen consumeSongSelect() { @@ -111,7 +108,7 @@ namespace osu.Game.Screens.Menu return s; } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); buttons.FadeInFromZero(500); @@ -146,8 +143,8 @@ namespace osu.Game.Screens.Menu const float length = 300; - Content.FadeIn(length, Easing.OutQuint); - Content.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); + this.FadeIn(length, Easing.OutQuint); + this.MoveTo(new Vector2(0, 0), length, Easing.OutQuint); sideFlashes.Delay(length).FadeIn(64, Easing.InQuint); } @@ -155,20 +152,22 @@ namespace osu.Game.Screens.Menu protected override void LogoSuspending(OsuLogo logo) { - logo.FadeOut(300, Easing.InSine) - .ScaleTo(0.2f, 300, Easing.InSine) - .OnComplete(l => buttons.SetOsuLogo(null)); + var seq = logo.FadeOut(300, Easing.InSine) + .ScaleTo(0.2f, 300, Easing.InSine); + + seq.OnComplete(_ => buttons.SetOsuLogo(null)); + seq.OnAbort(_ => buttons.SetOsuLogo(null)); } - private void beatmap_ValueChanged(WorkingBeatmap newValue) + private void beatmap_ValueChanged(ValueChangedEvent e) { - if (!IsCurrentScreen) + if (!this.IsCurrentScreen()) return; - background.Next(); + ((BackgroundScreenDefault)Background).Next(); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); @@ -176,38 +175,27 @@ namespace osu.Game.Screens.Menu buttons.State = ButtonSystemState.EnteringMode; - Content.FadeOut(length, Easing.InSine); - Content.MoveTo(new Vector2(-800, 0), length, Easing.InSine); + this.FadeOut(length, Easing.InSine); + this.MoveTo(new Vector2(-800, 0), length, Easing.InSine); sideFlashes.FadeOut(64, Easing.OutQuint); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); - background.Next(); + (Background as BackgroundScreenDefault)?.Next(); //we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { buttons.State = ButtonSystemState.Exit; - Content.FadeOut(3000); + this.FadeOut(3000); return base.OnExiting(next); } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (!e.Repeat && e.ControlPressed && e.ShiftPressed && e.Key == Key.D) - { - Push(new Drawings()); - return true; - } - - return base.OnKeyDown(e); - } } } diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index ec5528b13f..95d0bf04b4 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -1,10 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -13,7 +12,11 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; using System; +using osu.Framework.Bindables; namespace osu.Game.Screens.Menu { @@ -32,6 +35,12 @@ namespace osu.Game.Screens.Menu private const double box_fade_in_time = 65; private const int box_width = 200; + private Bindable user; + private Bindable skin; + + [Resolved] + private OsuColour colours { get; set; } + public MenuSideFlashes() { EarlyActivationMilliseconds = box_fade_in_time; @@ -42,13 +51,12 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap, OsuColour colours) + private void load(IBindable beatmap, IAPIProvider api, SkinManager skinManager) { 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(); + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); Children = new Drawable[] { @@ -58,11 +66,11 @@ namespace osu.Game.Screens.Menu Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, Width = box_width * 2, + Height = 1.5f, // 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) + Blending = BlendingMode.Additive }, rightBox = new Box { @@ -70,12 +78,15 @@ namespace osu.Game.Screens.Menu Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, Width = box_width * 2, + Height = 1.5f, X = box_width, Alpha = 0, - Blending = BlendingMode.Additive, - Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight) + Blending = BlendingMode.Additive } }; + + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) @@ -95,5 +106,20 @@ namespace osu.Game.Screens.Menu .Then() .FadeOut(beatLength, Easing.In); } + + private void updateColour() + { + Color4 baseColour = colours.Blue; + + if (user.Value?.IsSupporter ?? false) + baseColour = skin.Value.GetValue(s => s.CustomColours.ContainsKey("MenuGlow") ? s.CustomColours["MenuGlow"] : (Color4?)null) ?? baseColour; + + // linear colour looks better in this case, so let's use it for now. + Color4 gradientDark = baseColour.Opacity(0).ToLinear(); + Color4 gradientLight = baseColour.Opacity(0.6f).ToLinear(); + + leftBox.Colour = ColourInfo.GradientHorizontal(gradientLight, gradientDark); + rightBox.Colour = ColourInfo.GradientHorizontal(gradientDark, gradientLight); + } } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f858fded92..479b3d80b6 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; @@ -54,7 +54,13 @@ namespace osu.Game.Screens.Menu /// public Func Action; - public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.74f; + /// + /// The size of the logo Sprite with respect to the scale of its hover and bounce containers. + /// + /// Does not account for the scale of this + public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X; + + public bool IsTracking { get; set; } private readonly Sprite ripple; @@ -62,7 +68,7 @@ namespace osu.Game.Screens.Menu public bool Triangles { - set { colourAndTriangles.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } + set => colourAndTriangles.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } public bool BeatMatching = true; @@ -71,16 +77,14 @@ namespace osu.Game.Screens.Menu public bool Ripple { - get { return rippleContainer.Alpha > 0; } - set { rippleContainer.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } + get => rippleContainer.Alpha > 0; + set => rippleContainer.FadeTo(value ? 1 : 0, transition_length, Easing.OutQuint); } private readonly Box flashLayer; private readonly Container impactContainer; - private const float default_size = 480; - private const double early_activation = 60; public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; @@ -89,8 +93,6 @@ namespace osu.Game.Screens.Menu { EarlyActivationMilliseconds = early_activation; - Size = new Vector2(default_size); - Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -253,8 +255,8 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(TextureStore textures, AudioManager audio) { - sampleClick = audio.Sample.Get(@"Menu/osu-logo-select"); - sampleBeat = audio.Sample.Get(@"Menu/osu-logo-heartbeat"); + sampleClick = audio.Samples.Get(@"Menu/osu-logo-select"); + sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat"); logo.Texture = textures.Get(@"Menu/logo"); ripple.Texture = textures.Get(@"Menu/logo"); diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index 6dc59f5cac..e096fb33da 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -1,68 +1,87 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; -using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; namespace osu.Game.Screens.Multi.Components { - public class BeatmapTitle : FillFlowContainer + public class BeatmapTitle : MultiplayerComposite { - private readonly OsuSpriteText beatmapTitle, beatmapDash, beatmapArtist; - - 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(); - } - } + private readonly LinkFlowContainer textFlow; 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", }, - }; + InternalChild = textFlow = new LinkFlowContainer { AutoSizeAxes = Axes.Both }; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); - updateText(); + CurrentItem.BindValueChanged(_ => updateText(), true); } + private float textSize = OsuFont.DEFAULT_FONT_SIZE; + + public float TextSize + { + get => textSize; + set + { + if (textSize == value) + return; + + textSize = value; + + updateText(); + } + } + + [Resolved] + private OsuColour colours { get; set; } + private void updateText() { + if (LoadState < LoadState.Loading) + return; + + textFlow.Clear(); + + var beatmap = CurrentItem.Value?.Beatmap; + if (beatmap == null) - { - beatmapTitle.Text = "Changing map"; - beatmapDash.Text = beatmapArtist.Text = string.Empty; - } + textFlow.AddText("No beatmap selected", s => + { + s.Font = s.Font.With(size: TextSize); + s.Colour = colours.PinkLight; + }); else { - beatmapTitle.Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)); - beatmapDash.Text = @" - "; - beatmapArtist.Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)); + textFlow.AddLink(new[] + { + new OsuSpriteText + { + Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)), + Font = OsuFont.GetFont(size: TextSize), + }, + new OsuSpriteText + { + Text = " - ", + Font = OsuFont.GetFont(size: TextSize), + }, + new OsuSpriteText + { + Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)), + Font = OsuFont.GetFont(size: TextSize), + } + }, null, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); } } } diff --git a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs index a22e171275..d63f2fecd2 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTypeInfo.cs @@ -1,70 +1,67 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Multiplayer; +using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Screens.Multi.Components { - public class BeatmapTypeInfo : FillFlowContainer + public class BeatmapTypeInfo : MultiplayerComposite { - 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) + private void load() { - beatmapAuthor.Colour = colours.Gray9; + LinkFlowContainer beatmapAuthor; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + LayoutDuration = 100, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new ModeTypeInfo(), + new Container + { + AutoSizeAxes = Axes.X, + Height = 30, + Margin = new MarginPadding { Left = 5 }, + Children = new Drawable[] + { + new BeatmapTitle(), + beatmapAuthor = new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + AutoSizeAxes = Axes.Both + }, + }, + }, + } + }; + + CurrentItem.BindValueChanged(item => + { + beatmapAuthor.Clear(); + + var beatmap = item.NewValue?.Beatmap; + + if (beatmap != null) + { + beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f)); + beatmapAuthor.AddUserLink(beatmap.Metadata.Author); + } + }, true); } } } diff --git a/osu.Game/Screens/Multi/Components/DisableableTabControl.cs b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs new file mode 100644 index 0000000000..27b5aec4d3 --- /dev/null +++ b/osu.Game/Screens/Multi/Components/DisableableTabControl.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; + +namespace osu.Game.Screens.Multi.Components +{ + public abstract class DisableableTabControl : TabControl + { + public readonly BindableBool Enabled = new BindableBool(); + + protected override void AddTabItem(TabItem tab, bool addToDropdown = true) + { + if (tab is DisableableTabItem disableable) + disableable.Enabled.BindTo(Enabled); + base.AddTabItem(tab, addToDropdown); + } + + protected abstract class DisableableTabItem : TabItem + { + protected DisableableTabItem(T value) + : base(value) + { + } + + protected override bool OnClick(ClickEvent e) + { + if (!Enabled.Value) + return true; + + return base.OnClick(e); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Components/DrawableGameType.cs b/osu.Game/Screens/Multi/Components/DrawableGameType.cs index 3406e179d4..f4941dd73a 100644 --- a/osu.Game/Screens/Multi/Components/DrawableGameType.cs +++ b/osu.Game/Screens/Multi/Components/DrawableGameType.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs index 95f0c2153d..6080458aec 100644 --- a/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs +++ b/osu.Game/Screens/Multi/Components/ModeTypeInfo.cs @@ -1,83 +1,65 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Screens.Multi.Components { - public class ModeTypeInfo : Container + public class ModeTypeInfo : MultiplayerComposite { private const float height = 30; private const float transition_duration = 100; - private readonly Container rulesetContainer, gameTypeContainer; - - public BeatmapInfo Beatmap - { - set - { - if (value != null) - { - rulesetContainer.FadeIn(transition_duration); - rulesetContainer.Children = new[] - { - new DifficultyIcon(value) - { - Size = new Vector2(height), - }, - }; - } - else - { - rulesetContainer.FadeOut(transition_duration); - } - } - } - - public GameType Type - { - set - { - gameTypeContainer.Children = new[] - { - new DrawableGameType(value) - { - Size = new Vector2(height), - }, - }; - } - } + private Container drawableRuleset; public ModeTypeInfo() { AutoSizeAxes = Axes.Both; + } - Children = new[] + [BackgroundDependencyLoader] + private void load() + { + Container gameTypeContainer; + + InternalChild = new FillFlowContainer { - new FillFlowContainer + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), + LayoutDuration = 100, + Children = new[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5f, 0f), - LayoutDuration = 100, - Children = new[] + drawableRuleset = new Container { - rulesetContainer = new Container - { - AutoSizeAxes = Axes.Both, - }, - gameTypeContainer = new Container - { - AutoSizeAxes = Axes.Both, - }, + AutoSizeAxes = Axes.Both, + }, + gameTypeContainer = new Container + { + AutoSizeAxes = Axes.Both, }, }, }; + + CurrentItem.BindValueChanged(item => updateBeatmap(item.NewValue), true); + + Type.BindValueChanged(type => gameTypeContainer.Child = new DrawableGameType(type.NewValue) { Size = new Vector2(height) }, true); + } + + private void updateBeatmap(PlaylistItem item) + { + if (item?.Beatmap != null) + { + drawableRuleset.FadeIn(transition_duration); + drawableRuleset.Child = new DifficultyIcon(item.Beatmap, item.Ruleset) { Size = new Vector2(height) }; + } + else + drawableRuleset.FadeOut(transition_duration); } } } diff --git a/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs new file mode 100644 index 0000000000..968fa6e72e --- /dev/null +++ b/osu.Game/Screens/Multi/Components/MultiplayerBackgroundSprite.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Beatmaps.Drawables; + +namespace osu.Game.Screens.Multi.Components +{ + public class MultiplayerBackgroundSprite : MultiplayerComposite + { + private readonly BeatmapSetCoverType beatmapSetCoverType; + + public MultiplayerBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover) + { + this.beatmapSetCoverType = beatmapSetCoverType; + } + + [BackgroundDependencyLoader] + private void load() + { + UpdateableBeatmapBackgroundSprite sprite; + + InternalChild = sprite = CreateBackgroundSprite(); + + CurrentItem.BindValueChanged(item => sprite.Beatmap.Value = item.NewValue?.Beatmap, true); + } + + protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; + } +} diff --git a/osu.Game/Screens/Multi/Components/ParticipantCount.cs b/osu.Game/Screens/Multi/Components/ParticipantCount.cs deleted file mode 100644 index e7183cbd92..0000000000 --- a/osu.Game/Screens/Multi/Components/ParticipantCount.cs +++ /dev/null @@ -1,79 +0,0 @@ -// 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/ParticipantCountDisplay.cs b/osu.Game/Screens/Multi/Components/ParticipantCountDisplay.cs new file mode 100644 index 0000000000..498eeb09b3 --- /dev/null +++ b/osu.Game/Screens/Multi/Components/ParticipantCountDisplay.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Multi.Components +{ + public class ParticipantCountDisplay : MultiplayerComposite + { + private const float text_size = 30; + private const float transition_duration = 100; + + private OsuSpriteText slash, maxText; + + public ParticipantCountDisplay() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + OsuSpriteText count; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + LayoutDuration = transition_duration, + Children = new[] + { + count = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: text_size) + }, + slash = new OsuSpriteText + { + Text = @"/", + Font = OsuFont.GetFont(weight: FontWeight.Light, size: text_size) + }, + maxText = new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Light, size: text_size) + }, + } + }; + + MaxParticipants.BindValueChanged(_ => updateMax(), true); + ParticipantCount.BindValueChanged(c => count.Text = c.NewValue.ToString("#,0"), true); + } + + private void updateMax() + { + if (MaxParticipants.Value == null) + { + slash.FadeOut(transition_duration); + maxText.FadeOut(transition_duration); + } + else + { + slash.FadeIn(transition_duration); + maxText.Text = MaxParticipants.Value.ToString(); + maxText.FadeIn(transition_duration); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Components/ParticipantInfo.cs deleted file mode 100644 index b1c77a04af..0000000000 --- a/osu.Game/Screens/Multi/Components/ParticipantInfo.cs +++ /dev/null @@ -1,145 +0,0 @@ -// 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.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Users; -using osuTK; - -namespace osu.Game.Screens.Multi.Components -{ - public class ParticipantInfo : Container - { - private readonly Container flagContainer; - private readonly OsuSpriteText host; - private readonly FillFlowContainer levelRangeContainer; - private readonly OsuSpriteText levelRangeLower; - private readonly OsuSpriteText levelRangeHigher; - - public User Host - { - set - { - host.Text = value.Username; - flagContainer.Children = new[] { new DrawableFlag(value.Country) { RelativeSizeAxes = Axes.Both } }; - } - } - - public IEnumerable Participants - { - set - { - var ranks = value.Select(u => u.Statistics.Ranks.Global); - levelRangeLower.Text = ranks.Min().ToString(); - levelRangeHigher.Text = ranks.Max().ToString(); - } - } - - public ParticipantInfo(string rankPrefix = null) - { - RelativeSizeAxes = Axes.X; - Height = 15f; - - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5f, 0f), - Children = new Drawable[] - { - flagContainer = new Container - { - Width = 22f, - RelativeSizeAxes = Axes.Y, - }, - new Container //todo: team banners - { - Width = 38f, - RelativeSizeAxes = Axes.Y, - CornerRadius = 2f, - Masking = true, - Children = new[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"ad387e"), - }, - }, - }, - new OsuSpriteText - { - Text = "hosted by", - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = 14, - }, - host = new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - TextSize = 14, - Font = @"Exo2.0-BoldItalic", - }, - }, - }, - levelRangeContainer = new FillFlowContainer - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = rankPrefix, - TextSize = 14, - }, - new OsuSpriteText - { - Text = "#", - TextSize = 14, - }, - levelRangeLower = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - new OsuSpriteText - { - Text = " - ", - TextSize = 14, - }, - new OsuSpriteText - { - Text = "#", - TextSize = 14, - }, - levelRangeHigher = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - levelRangeContainer.Colour = colours.Gray9; - host.Colour = colours.Blue; - } - } -} diff --git a/osu.Game/Screens/Multi/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Components/RoomInspector.cs deleted file mode 100644 index 1ec509d5a8..0000000000 --- a/osu.Game/Screens/Multi/Components/RoomInspector.cs +++ /dev/null @@ -1,303 +0,0 @@ -// 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; -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.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Online.Multiplayer; -using osu.Game.Users; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Multi.Components -{ - public class RoomInspector : Container - { - private const float transition_duration = 100; - - private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 }; - private readonly Bindable nameBind = new Bindable(); - private readonly Bindable hostBind = new Bindable(); - private readonly Bindable statusBind = new Bindable(); - 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 OsuColour colours; - private Box statusStrip; - private UpdateableBeatmapSetCover cover; - private ParticipantCount participantCount; - private FillFlowContainer topFlow, participantsFlow; - private OsuSpriteText name, status; - private BeatmapTypeInfo beatmapTypeInfo; - private ScrollContainer participantsScroll; - private ParticipantInfo participantInfo; - - private Room room; - public Room Room - { - get { return room; } - set - { - if (value == room) return; - room = value; - - nameBind.UnbindBindings(); - hostBind.UnbindBindings(); - statusBind.UnbindBindings(); - typeBind.UnbindBindings(); - beatmapBind.UnbindBindings(); - maxParticipantsBind.UnbindBindings(); - participantsBind.UnbindBindings(); - - if (room != null) - { - nameBind.BindTo(room.Name); - hostBind.BindTo(room.Host); - statusBind.BindTo(room.Status); - typeBind.BindTo(room.Type); - beatmapBind.BindTo(room.Beatmap); - maxParticipantsBind.BindTo(room.MaxParticipants); - participantsBind.BindTo(room.Participants); - } - - updateState(); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - this.colours = colours; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"343138"), - }, - topFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = 200, - Masking = true, - Children = new Drawable[] - { - cover = new UpdateableBeatmapSetCover - { - RelativeSizeAxes = Axes.Both, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)), - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(20), - Children = new Drawable[] - { - participantCount = new ParticipantCount - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - }, - name = new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - TextSize = 30, - }, - }, - }, - }, - }, - statusStrip = new Box - { - RelativeSizeAxes = Axes.X, - Height = 5, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"28242d"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = transition_duration, - Padding = contentPadding, - Spacing = new Vector2(0f, 5f), - Children = new Drawable[] - { - status = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - beatmapTypeInfo = new BeatmapTypeInfo(), - }, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = contentPadding, - Children = new Drawable[] - { - participantInfo = new ParticipantInfo(@"Rank Range "), - }, - }, - }, - }, - participantsScroll = new OsuScrollContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Top = contentPadding.Top, Left = 38, Right = 37 }, - Children = new[] - { - participantsFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - LayoutDuration = transition_duration, - Spacing = new Vector2(5f), - }, - }, - }, - }; - - nameBind.ValueChanged += n => name.Text = n; - hostBind.ValueChanged += h => participantInfo.Host = h; - typeBind.ValueChanged += t => beatmapTypeInfo.Type = t; - maxParticipantsBind.ValueChanged += m => participantCount.Max = m; - statusBind.ValueChanged += displayStatus; - - beatmapBind.ValueChanged += b => - { - cover.BeatmapSet = b?.BeatmapSet; - beatmapTypeInfo.Beatmap = b; - }; - - participantsBind.ValueChanged += p => - { - participantCount.Count = p.Count(); - participantInfo.Participants = p; - participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u)); - }; - - updateState(); - } - - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - participantsScroll.Height = DrawHeight - topFlow.DrawHeight; - } - - private void displayStatus(RoomStatus s) - { - status.Text = s.Message; - - Color4 c = s.GetAppropriateColour(colours); - statusStrip.FadeColour(c, transition_duration); - status.FadeColour(c, transition_duration); - } - - private void updateState() - { - if (Room == null) - { - cover.BeatmapSet = null; - participantsFlow.FadeOut(transition_duration); - participantCount.FadeOut(transition_duration); - beatmapTypeInfo.FadeOut(transition_duration); - name.FadeOut(transition_duration); - participantInfo.FadeOut(transition_duration); - - displayStatus(new RoomStatusNoneSelected()); - } - else - { - participantsFlow.FadeIn(transition_duration); - participantCount.FadeIn(transition_duration); - beatmapTypeInfo.FadeIn(transition_duration); - name.FadeIn(transition_duration); - participantInfo.FadeIn(transition_duration); - - statusBind.TriggerChange(); - beatmapBind.TriggerChange(); - } - } - - private class UserTile : Container, IHasTooltip - { - private readonly User user; - - public string TooltipText => user.Username; - - public UserTile(User user) - { - this.user = user; - Size = new Vector2(70f); - CornerRadius = 5f; - Masking = true; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"27252d"), - }, - new UpdateableAvatar - { - RelativeSizeAxes = Axes.Both, - User = user, - }, - }; - } - } - - private class RoomStatusNoneSelected : RoomStatus - { - public override string Message => @"No Room Selected"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8; - } - } -} diff --git a/osu.Game/Screens/Multi/Components/RoomStatusInfo.cs b/osu.Game/Screens/Multi/Components/RoomStatusInfo.cs new file mode 100644 index 0000000000..d799f846c2 --- /dev/null +++ b/osu.Game/Screens/Multi/Components/RoomStatusInfo.cs @@ -0,0 +1,110 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.RoomStatuses; + +namespace osu.Game.Screens.Multi.Components +{ + public class RoomStatusInfo : MultiplayerComposite + { + public RoomStatusInfo() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + StatusPart statusPart; + EndDatePart endDatePart; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + statusPart = new StatusPart + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14) + }, + endDatePart = new EndDatePart { Font = OsuFont.GetFont(size: 14) } + } + }; + + statusPart.EndDate.BindTo(EndDate); + statusPart.Status.BindTo(Status); + statusPart.Availability.BindTo(Availability); + endDatePart.EndDate.BindTo(EndDate); + } + + private class EndDatePart : DrawableDate + { + public readonly IBindable EndDate = new Bindable(); + + public EndDatePart() + : base(DateTimeOffset.UtcNow) + { + EndDate.BindValueChanged(date => Date = date.NewValue); + } + + protected override string Format() + { + var diffToNow = Date.Subtract(DateTimeOffset.Now); + + if (diffToNow.TotalSeconds < -5) + return $"Closed {base.Format()}"; + + if (diffToNow.TotalSeconds < 0) + return "Closed"; + + if (diffToNow.TotalSeconds < 5) + return "Closing soon"; + + return $"Closing {base.Format()}"; + } + } + + private class StatusPart : EndDatePart + { + public readonly IBindable Status = new Bindable(); + public readonly IBindable Availability = new Bindable(); + + [Resolved] + private OsuColour colours { get; set; } + + public StatusPart() + { + EndDate.BindValueChanged(_ => Format()); + Status.BindValueChanged(_ => Format()); + Availability.BindValueChanged(_ => Format()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Text = Format(); + } + + protected override string Format() + { + if (!IsLoaded) + return string.Empty; + + RoomStatus status = Date < DateTimeOffset.Now ? new RoomStatusEnded() : Status.Value ?? new RoomStatusOpen(); + + this.FadeColour(status.GetAppropriateColour(colours), 100); + return $"{Availability.Value.GetDescription()}, {status.Message}"; + } + } + } +} diff --git a/osu.Game/Screens/Multi/Components/StatusColouredContainer.cs b/osu.Game/Screens/Multi/Components/StatusColouredContainer.cs new file mode 100644 index 0000000000..97af6674bf --- /dev/null +++ b/osu.Game/Screens/Multi/Components/StatusColouredContainer.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.Multi.Components +{ + public class StatusColouredContainer : Container + { + private readonly double transitionDuration; + + [Resolved(typeof(Room), nameof(Room.Status))] + private Bindable status { get; set; } + + public StatusColouredContainer(double transitionDuration = 100) + { + this.transitionDuration = transitionDuration; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + status.BindValueChanged(s => this.FadeColour(s.NewValue.GetAppropriateColour(colours), transitionDuration), true); + } + } +} diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 6bd300eadc..1cbf2a45e7 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -7,11 +7,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; 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 osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Multi @@ -20,11 +17,11 @@ namespace osu.Game.Screens.Multi { public const float HEIGHT = 121; - private readonly OsuSpriteText screenType; private readonly HeaderBreadcrumbControl breadcrumbs; - public Header(Screen initialScreen) + public Header(ScreenStack stack) { + MultiHeaderTitle title; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -38,45 +35,16 @@ namespace osu.Game.Screens.Multi new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { - new FillFlowContainer + title = new MultiHeaderTitle { Anchor = Anchor.CentreLeft, Origin = Anchor.BottomLeft, - Position = new Vector2(-35f, 5f), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10f, 0f), - Children = new Drawable[] - { - new SpriteIcon - { - Size = new Vector2(25), - Icon = FontAwesome.fa_osu_multi, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Text = "multiplayer ", - TextSize = 25, - }, - screenType = new OsuSpriteText - { - TextSize = 25, - Font = @"Exo2.0-Light", - }, - }, - }, - }, + X = -ScreenTitle.ICON_WIDTH, }, - breadcrumbs = new HeaderBreadcrumbControl(initialScreen) + breadcrumbs = new HeaderBreadcrumbControl(stack) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -86,20 +54,41 @@ namespace osu.Game.Screens.Multi }, }; - breadcrumbs.Current.ValueChanged += s => screenType.Text = ((MultiplayerScreen)s).Type.ToLowerInvariant(); + breadcrumbs.Current.ValueChanged += screen => + { + if (screen.NewValue is IMultiplayerSubScreen multiScreen) + title.Screen = multiScreen; + }; + breadcrumbs.Current.TriggerChange(); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - screenType.Colour = colours.Yellow; breadcrumbs.StripColour = colours.Green; } + private class MultiHeaderTitle : ScreenTitle + { + public IMultiplayerSubScreen Screen + { + set => Section = value.ShortTitle.ToLowerInvariant(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Title = "multi"; + Icon = OsuIcon.Multi; + AccentColour = colours.Yellow; + } + } + private class HeaderBreadcrumbControl : ScreenBreadcrumbControl { - public HeaderBreadcrumbControl(Screen initialScreen) : base(initialScreen) + public HeaderBreadcrumbControl(ScreenStack stack) + : base(stack) { } diff --git a/osu.Game/Screens/Multi/IMultiplayerSubScreen.cs b/osu.Game/Screens/Multi/IMultiplayerSubScreen.cs new file mode 100644 index 0000000000..31ee123f83 --- /dev/null +++ b/osu.Game/Screens/Multi/IMultiplayerSubScreen.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Multi +{ + public interface IMultiplayerSubScreen : IOsuScreen + { + string Title { get; } + + string ShortTitle { get; } + } +} diff --git a/osu.Game/Screens/Multi/IRoomManager.cs b/osu.Game/Screens/Multi/IRoomManager.cs new file mode 100644 index 0000000000..f6c979851e --- /dev/null +++ b/osu.Game/Screens/Multi/IRoomManager.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.Multi +{ + public interface IRoomManager + { + /// + /// Invoked when the s have been updated. + /// + event Action RoomsUpdated; + + /// + /// All the active s. + /// + IBindableList Rooms { get; } + + /// + /// Creates a new . + /// + /// The to create. + /// An action to be invoked if the creation succeeds. + /// An action to be invoked if an error occurred. + void CreateRoom(Room room, Action onSuccess = null, Action onError = null); + + /// + /// Joins a . + /// + /// The to join. must be populated. + /// + /// + void JoinRoom(Room room, Action onSuccess = null, Action onError = null); + + /// + /// Parts the currently-joined . + /// + void PartRoom(); + } +} diff --git a/osu.Game/Screens/Multi/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs similarity index 62% rename from osu.Game/Screens/Multi/Components/DrawableRoom.cs rename to osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 05ec2f0fac..6ec8f2bfe5 100644 --- a/osu.Game/Screens/Multi/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -1,16 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; @@ -18,11 +18,11 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; -using osu.Game.Users; +using osu.Game.Screens.Multi.Components; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Multi.Components +namespace osu.Game.Screens.Multi.Lounge.Components { public class DrawableRoom : OsuClickableContainer, IStateful, IFilterable { @@ -30,28 +30,29 @@ namespace osu.Game.Screens.Multi.Components private const float corner_radius = 5; private const float transition_duration = 60; private const float content_padding = 10; - private const float height = 100; + private const float height = 110; private const float side_strip_width = 5; private const float cover_width = 145; - private readonly Box selectionBox; + public event Action StateChanged; - private readonly Bindable nameBind = new Bindable(); - private readonly Bindable hostBind = new Bindable(); - 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 Box selectionBox; + private CachedModelDependencyContainer dependencies; + + [Resolved] + private BeatmapManager beatmaps { get; set; } public readonly Room Room; private SelectionState state; + public SelectionState State { - get { return state; } + get => state; set { if (value == state) return; + state = value; if (state == SelectionState.Selected) @@ -66,9 +67,10 @@ namespace osu.Game.Screens.Multi.Components public IEnumerable FilterTerms => new[] { Room.Name.Value }; private bool matchingFilter; + public bool MatchingFilter { - get { return matchingFilter; } + get => matchingFilter; set { matchingFilter = value; @@ -76,18 +78,7 @@ namespace osu.Game.Screens.Multi.Components } } - private Action action; - public new Action Action - { - get { return action; } - set - { - action = value; - Enabled.Value = action != null; - } - } - - public event Action StateChanged; + public bool FilteringActive { get; set; } public DrawableRoom(Room room) { @@ -109,16 +100,13 @@ namespace osu.Game.Screens.Multi.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - Box sideStrip; - UpdateableBeatmapSetCover cover; - OsuSpriteText name, status; - ParticipantInfo participantInfo; - BeatmapTitle beatmapTitle; - ModeTypeInfo modeTypeInfo; - Children = new Drawable[] { - selectionBox, + new StatusColouredContainer(transition_duration) + { + RelativeSizeAxes = Axes.Both, + Child = selectionBox + }, new Container { RelativeSizeAxes = Axes.Both, @@ -141,17 +129,19 @@ namespace osu.Game.Screens.Multi.Components RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"212121"), }, - sideStrip = new Box + new StatusColouredContainer(transition_duration) { RelativeSizeAxes = Axes.Y, Width = side_strip_width, + Child = new Box { RelativeSizeAxes = Axes.Both } }, - cover = new UpdateableBeatmapSetCover + new Container { - Width = cover_width, RelativeSizeAxes = Axes.Y, + Width = cover_width, Masking = true, Margin = new MarginPadding { Left = side_strip_width }, + Child = new MultiplayerBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both } }, new Container { @@ -172,11 +162,8 @@ namespace osu.Game.Screens.Multi.Components Spacing = new Vector2(5f), Children = new Drawable[] { - name = new OsuSpriteText - { - TextSize = 18, - }, - participantInfo = new ParticipantInfo(), + new RoomName { Font = OsuFont.GetFont(size: 18) }, + new ParticipantInfo(), }, }, new FillFlowContainer @@ -186,21 +173,14 @@ namespace osu.Game.Screens.Multi.Components RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), Children = new Drawable[] { - status = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - beatmapTitle = new BeatmapTitle - { - TextSize = 14, - Colour = colours.Gray9 - }, + new RoomStatusInfo(), + new BeatmapTitle { TextSize = 14 }, }, }, - modeTypeInfo = new ModeTypeInfo + new ModeTypeInfo { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -211,33 +191,13 @@ namespace osu.Game.Screens.Multi.Components }, }, }; + } - nameBind.ValueChanged += n => name.Text = n; - hostBind.ValueChanged += h => participantInfo.Host = h; - typeBind.ValueChanged += m => modeTypeInfo.Type = m; - participantsBind.ValueChanged += p => participantInfo.Participants = p; - - statusBind.ValueChanged += s => - { - status.Text = s.Message; - - foreach (Drawable d in new Drawable[] { selectionBox, sideStrip, status }) - d.FadeColour(s.GetAppropriateColour(colours), transition_duration); - }; - - beatmapBind.ValueChanged += b => - { - cover.BeatmapSet = b?.BeatmapSet; - beatmapTitle.Beatmap = b; - modeTypeInfo.Beatmap = b; - }; - - nameBind.BindTo(Room.Name); - hostBind.BindTo(Room.Host); - statusBind.BindTo(Room.Status); - typeBind.BindTo(Room.Type); - beatmapBind.BindTo(Room.Beatmap); - participantsBind.BindTo(Room.Participants); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Model.Value = Room; + return dependencies; } protected override void LoadComplete() @@ -246,15 +206,16 @@ namespace osu.Game.Screens.Multi.Components this.FadeInFromZero(transition_duration); } - protected override bool OnClick(ClickEvent e) + private class RoomName : OsuSpriteText { - if (Enabled.Value) - { - Action?.Invoke(this); - State = SelectionState.Selected; - } + [Resolved(typeof(Room), nameof(Online.Multiplayer.Room.Name))] + private Bindable name { get; set; } - return true; + [BackgroundDependencyLoader] + private void load() + { + Current = name; + } } } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs new file mode 100644 index 0000000000..8e14f76e4b --- /dev/null +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.ComponentModel; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Graphics; +using osu.Game.Overlays.SearchableList; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Lounge.Components +{ + public class FilterControl : SearchableListFilterControl + { + protected override Color4 BackgroundColour => OsuColour.FromHex(@"362e42"); + protected override PrimaryFilter DefaultTab => PrimaryFilter.Open; + + protected override float ContentHorizontalPadding => base.ContentHorizontalPadding + OsuScreen.HORIZONTAL_OVERFLOW_PADDING; + + [Resolved(CanBeNull = true)] + private Bindable filter { get; set; } + + public FilterControl() + { + DisplayStyleControl.Hide(); + } + + [BackgroundDependencyLoader] + private void load() + { + if (filter == null) + filter = new Bindable(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Search.Current.BindValueChanged(_ => updateFilter()); + Tabs.Current.BindValueChanged(_ => updateFilter(), true); + } + + private void updateFilter() + { + filter.Value = new FilterCriteria + { + SearchString = Search.Current.Value ?? string.Empty, + PrimaryFilter = Tabs.Current.Value, + SecondaryFilter = DisplayStyleControl.Dropdown.Current.Value + }; + } + } + + public enum PrimaryFilter + { + Open, + + [Description("Recently Ended")] + RecentlyEnded, + Participated, + Owned, + } + + public enum SecondaryFilter + { + Public, + //Private, + } +} diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs new file mode 100644 index 0000000000..666bc44a8d --- /dev/null +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs @@ -0,0 +1,12 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Multi.Lounge.Components +{ + public class FilterCriteria + { + public string SearchString; + public PrimaryFilter PrimaryFilter; + public SecondaryFilter SecondaryFilter; + } +} diff --git a/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs new file mode 100644 index 0000000000..a55db096af --- /dev/null +++ b/osu.Game/Screens/Multi/Lounge/Components/ParticipantInfo.cs @@ -0,0 +1,89 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Users.Drawables; +using osuTK; + +namespace osu.Game.Screens.Multi.Lounge.Components +{ + public class ParticipantInfo : MultiplayerComposite + { + public ParticipantInfo() + { + RelativeSizeAxes = Axes.X; + Height = 15f; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + OsuSpriteText summary; + Container flagContainer; + LinkFlowContainer hostText; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5f, 0f), + Children = new Drawable[] + { + flagContainer = new Container + { + Width = 22f, + RelativeSizeAxes = Axes.Y, + }, + hostText = new LinkFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both + } + }, + }, + new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Colour = colours.Gray9, + Children = new[] + { + summary = new OsuSpriteText + { + Text = "0 participants", + Font = OsuFont.GetFont(size: 14) + } + }, + }, + }; + + Host.BindValueChanged(host => + { + hostText.Clear(); + flagContainer.Clear(); + + if (host.NewValue != null) + { + hostText.AddText("hosted by "); + hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + + flagContainer.Child = new UpdateableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both }; + } + }, true); + + ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true); + } + } +} diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs new file mode 100644 index 0000000000..5030d8cb50 --- /dev/null +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -0,0 +1,325 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.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.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Components; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Lounge.Components +{ + public class RoomInspector : MultiplayerComposite + { + private const float transition_duration = 100; + + private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 }; + + private ParticipantCountDisplay participantCount; + private OsuSpriteText name; + private BeatmapTypeInfo beatmapTypeInfo; + private ParticipantInfo participantInfo; + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + private readonly Bindable status = new Bindable(new RoomStatusNoneSelected()); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"343138"), + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed), + }, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 200, + Masking = true, + Children = new Drawable[] + { + new MultiplayerBackgroundSprite { RelativeSizeAxes = Axes.Both }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Children = new Drawable[] + { + participantCount = new ParticipantCountDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + name = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: 30), + Current = RoomName + }, + }, + }, + }, + }, + new StatusColouredContainer(transition_duration) + { + RelativeSizeAxes = Axes.X, + Height = 5, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = transition_duration, + Padding = contentPadding, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + new StatusColouredContainer(transition_duration) + { + AutoSizeAxes = Axes.Both, + Child = new StatusText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), + } + }, + beatmapTypeInfo = new BeatmapTypeInfo(), + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = contentPadding, + Children = new Drawable[] + { + participantInfo = new ParticipantInfo(), + }, + }, + }, + }, + }, + new Drawable[] + { + new MatchParticipants + { + RelativeSizeAxes = Axes.Both, + } + } + } + } + }; + + Status.BindValueChanged(_ => updateStatus(), true); + RoomID.BindValueChanged(_ => updateStatus(), true); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(status, new CacheInfo(nameof(Room.Status), typeof(Room))); + return dependencies; + } + + private void updateStatus() + { + if (RoomID.Value == null) + { + status.Value = new RoomStatusNoneSelected(); + + participantCount.FadeOut(transition_duration); + beatmapTypeInfo.FadeOut(transition_duration); + name.FadeOut(transition_duration); + participantInfo.FadeOut(transition_duration); + } + else + { + status.Value = Status.Value; + + participantCount.FadeIn(transition_duration); + beatmapTypeInfo.FadeIn(transition_duration); + name.FadeIn(transition_duration); + participantInfo.FadeIn(transition_duration); + } + } + + private class RoomStatusNoneSelected : RoomStatus + { + public override string Message => @"No Room Selected"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8; + } + + private class StatusText : OsuSpriteText + { + [Resolved(typeof(Room), nameof(Room.Status))] + private Bindable status { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + status.BindValueChanged(s => Text = s.NewValue.Message, true); + } + } + + private class MatchParticipants : MultiplayerComposite + { + private readonly FillFlowContainer fill; + + public MatchParticipants() + { + Padding = new MarginPadding { Horizontal = 10 }; + + InternalChild = new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = fill = new FillFlowContainer + { + Spacing = new Vector2(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + } + }; + } + + [BackgroundDependencyLoader] + private void load() + { + RoomID.BindValueChanged(_ => updateParticipants(), true); + } + + [Resolved] + private IAPIProvider api { get; set; } + + private GetRoomScoresRequest request; + + private void updateParticipants() + { + var roomId = RoomID.Value ?? 0; + + request?.Cancel(); + + // nice little progressive fade + int time = 500; + + foreach (var c in fill.Children) + { + c.Delay(500 - time).FadeOut(time, Easing.Out); + time = Math.Max(20, time - 20); + c.Expire(); + } + + if (roomId == 0) return; + + request = new GetRoomScoresRequest(roomId); + request.Success += scores => + { + if (roomId != RoomID.Value) + return; + + fill.Clear(); + foreach (var s in scores) + fill.Add(new UserTile(s.User)); + + fill.FadeInFromZero(1000, Easing.OutQuint); + }; + + api.Queue(request); + } + + protected override void Dispose(bool isDisposing) + { + request?.Cancel(); + base.Dispose(isDisposing); + } + + private class UserTile : CompositeDrawable, IHasTooltip + { + private readonly User user; + + public string TooltipText => user.Username; + + public UserTile(User user) + { + this.user = user; + Size = new Vector2(70f); + CornerRadius = 5f; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"27252d"), + }, + new UpdateableAvatar + { + RelativeSizeAxes = Axes.Both, + User = user, + }, + }; + } + } + } + } +} diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs new file mode 100644 index 0000000000..99a6de0064 --- /dev/null +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -0,0 +1,135 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osuTK; + +namespace osu.Game.Screens.Multi.Lounge.Components +{ + public class RoomsContainer : CompositeDrawable + { + public Action JoinRequested; + + private readonly IBindableList rooms = new BindableList(); + + private readonly FillFlowContainer roomFlow; + public IReadOnlyList Rooms => roomFlow; + + [Resolved] + private Bindable currentRoom { get; set; } + + [Resolved] + private IRoomManager roomManager { get; set; } + + public RoomsContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = roomFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + }; + } + + [BackgroundDependencyLoader] + private void load() + { + rooms.BindTo(roomManager.Rooms); + + rooms.ItemsAdded += addRooms; + rooms.ItemsRemoved += removeRooms; + + roomManager.RoomsUpdated += updateSorting; + + addRooms(rooms); + } + + private FilterCriteria currentFilter; + + public void Filter(FilterCriteria criteria) + { + roomFlow.Children.ForEach(r => + { + if (criteria == null) + r.MatchingFilter = true; + else + { + bool matchingFilter = true; + matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); + + switch (criteria.SecondaryFilter) + { + default: + case SecondaryFilter.Public: + r.MatchingFilter = r.Room.Availability.Value == RoomAvailability.Public; + break; + } + + r.MatchingFilter = matchingFilter; + } + }); + + currentFilter = criteria; + } + + private void addRooms(IEnumerable rooms) + { + foreach (var r in rooms) + roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) }); + + Filter(currentFilter); + } + + private void removeRooms(IEnumerable rooms) + { + foreach (var r in rooms) + { + var toRemove = roomFlow.Single(d => d.Room == r); + toRemove.Action = null; + + roomFlow.Remove(toRemove); + + selectRoom(null); + } + } + + private void updateSorting() + { + foreach (var room in roomFlow) + roomFlow.SetLayoutPosition(room, room.Room.Position.Value); + } + + private void selectRoom(Room room) + { + var drawable = roomFlow.FirstOrDefault(r => r.Room == room); + + if (drawable != null && drawable.State == SelectionState.Selected) + JoinRequested?.Invoke(room); + else + roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected); + + currentRoom.Value = room; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (roomManager != null) + roomManager.RoomsUpdated -= updateSorting; + } + } +} diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs new file mode 100644 index 0000000000..7f8e690516 --- /dev/null +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -0,0 +1,143 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using osu.Game.Screens.Multi.Lounge.Components; +using osu.Game.Screens.Multi.Match; + +namespace osu.Game.Screens.Multi.Lounge +{ + public class LoungeSubScreen : MultiplayerSubScreen + { + public override string Title => "Lounge"; + + protected readonly FilterControl Filter; + + private readonly Container content; + private readonly ProcessingOverlay processingOverlay; + + [Resolved] + private Bindable currentRoom { get; set; } + + public LoungeSubScreen() + { + InternalChildren = new Drawable[] + { + Filter = new FilterControl { Depth = -1 }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.55f, + Children = new Drawable[] + { + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarOverlapsContent = false, + Padding = new MarginPadding(10), + Child = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new RoomsContainer { JoinRequested = joinRequested } + }, + }, + processingOverlay = new ProcessingOverlay { Alpha = 0 } + } + }, + new RoomInspector + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Both, + Width = 0.45f, + }, + }, + }, + }; + + Filter.Search.Exit += this.Exit; + } + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + content.Padding = new MarginPadding + { + Top = Filter.DrawHeight, + Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING, + Right = SearchableListOverlay.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING, + }; + } + + protected override void OnFocus(FocusEvent e) + { + Filter.Search.TakeFocus(); + } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + Filter.Search.HoldFocus = true; + } + + public override bool OnExiting(IScreen next) + { + Filter.Search.HoldFocus = false; + return base.OnExiting(next); + } + + public override void OnSuspending(IScreen next) + { + base.OnSuspending(next); + Filter.Search.HoldFocus = false; + } + + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + + if (currentRoom.Value?.RoomID.Value == null) + currentRoom.Value = new Room(); + } + + private void joinRequested(Room room) + { + processingOverlay.Show(); + RoomManager?.JoinRoom(room, r => + { + Open(room); + processingOverlay.Hide(); + }, _ => processingOverlay.Hide()); + } + + /// + /// Push a room as a new subscreen. + /// + public void Open(Room room) + { + // Handles the case where a room is clicked 3 times in quick succession + if (!this.IsCurrentScreen()) + return; + + currentRoom.Value = room; + + this.Push(new MatchSubScreen(room)); + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs new file mode 100644 index 0000000000..b69cb9705d --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/GameTypePicker.cs @@ -0,0 +1,114 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.GameTypes; +using osu.Game.Screens.Multi.Components; +using osuTK; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class GameTypePicker : DisableableTabControl + { + private const float height = 40; + private const float selection_width = 3; + + protected override TabItem CreateTabItem(GameType value) => new GameTypePickerItem(value); + + protected override Dropdown CreateDropdown() => null; + + public GameTypePicker() + { + Height = height + selection_width * 2; + TabContainer.Spacing = new Vector2(10 - selection_width * 2); + + AddItem(new GameTypeTag()); + AddItem(new GameTypeVersus()); + AddItem(new GameTypeTagTeam()); + AddItem(new GameTypeTeamVersus()); + AddItem(new GameTypeTimeshift()); + } + + private class GameTypePickerItem : DisableableTabItem + { + private const float transition_duration = 200; + + private readonly CircularContainer hover, selection; + + public GameTypePickerItem(GameType value) + : base(value) + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + selection = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + new DrawableGameType(Value) + { + Size = new Vector2(height), + Margin = new MarginPadding(selection_width), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(selection_width), + Child = hover = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.Yellow; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs new file mode 100644 index 0000000000..73994fa369 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -0,0 +1,144 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Multi.Components; +using osu.Game.Screens.Play.HUD; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class Header : MultiplayerComposite + { + public const float HEIGHT = 200; + + public MatchTabControl Tabs; + + public Action RequestBeatmapSelection; + + public Header() + { + RelativeSizeAxes = Axes.X; + Height = HEIGHT; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BeatmapSelectButton beatmapButton; + ModDisplay modDisplay; + + InternalChildren = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new HeaderBackgroundSprite { RelativeSizeAxes = Axes.Both }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.4f), Color4.Black.Opacity(0.6f)), + }, + } + }, + new Box + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = colours.Yellow + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 20 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new BeatmapTypeInfo(), + modDisplay = new ModDisplay + { + Scale = new Vector2(0.75f), + DisplayUnrankedText = false + }, + } + }, + new Container + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.Y, + Width = 200, + Padding = new MarginPadding { Vertical = 10 }, + Child = beatmapButton = new BeatmapSelectButton + { + RelativeSizeAxes = Axes.Both, + Height = 1, + }, + }, + Tabs = new MatchTabControl + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X + }, + }, + }, + }; + + CurrentItem.BindValueChanged(item => modDisplay.Current.Value = item.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(), true); + + beatmapButton.Action = () => RequestBeatmapSelection?.Invoke(); + } + + private class BeatmapSelectButton : HeaderButton + { + [Resolved(typeof(Room), nameof(Room.RoomID))] + private Bindable roomId { get; set; } + + public BeatmapSelectButton() + { + Text = "Select beatmap"; + } + + [BackgroundDependencyLoader] + private void load() + { + roomId.BindValueChanged(id => this.FadeTo(id.NewValue.HasValue ? 0 : 1), true); + } + } + + private class HeaderBackgroundSprite : MultiplayerBackgroundSprite + { + protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both }; + + private class BackgroundSprite : UpdateableBeatmapBackgroundSprite + { + protected override double TransformDuration => 200; + } + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/HeaderButton.cs b/osu.Game/Screens/Multi/Match/Components/HeaderButton.cs new file mode 100644 index 0000000000..f3412d0be7 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/HeaderButton.cs @@ -0,0 +1,47 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class HeaderButton : TriangleButton + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundColour = OsuColour.FromHex(@"1187aa"); + + Triangles.ColourLight = OsuColour.FromHex(@"277b9c"); + Triangles.ColourDark = OsuColour.FromHex(@"1f6682"); + Triangles.TriangleScale = 1.5f; + + Add(new Container + { + RelativeSizeAxes = Axes.Both, + Alpha = 1f, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.15f, + Blending = BlendingMode.Additive, + }, + }); + } + + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 30), + }; + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/HostInfo.cs b/osu.Game/Screens/Multi/Match/Components/HostInfo.cs new file mode 100644 index 0000000000..8851a96605 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/HostInfo.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osuTK; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class HostInfo : CompositeDrawable + { + public readonly IBindable Host = new Bindable(); + + private readonly LinkFlowContainer linkContainer; + private readonly UpdateableAvatar avatar; + + public HostInfo() + { + AutoSizeAxes = Axes.X; + Height = 50; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + avatar = new UpdateableAvatar { Size = new Vector2(50) }, + new FillFlowContainer + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Child = linkContainer = new LinkFlowContainer { AutoSizeAxes = Axes.Both } + } + } + }; + + Host.BindValueChanged(host => updateHost(host.NewValue)); + } + + private void updateHost(User host) + { + avatar.User = host; + + if (host != null) + { + linkContainer.AddText("hosted by"); + linkContainer.NewLine(); + linkContainer.AddUserLink(host, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true)); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/Info.cs b/osu.Game/Screens/Multi/Match/Components/Info.cs new file mode 100644 index 0000000000..a185c4db50 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/Info.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.SearchableList; +using osu.Game.Screens.Multi.Components; +using osuTK; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class Info : MultiplayerComposite + { + public Action OnStart; + + public Info() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load() + { + ReadyButton readyButton; + ViewBeatmapButton viewBeatmapButton; + HostInfo hostInfo; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Padding = new MarginPadding { Vertical = 20 }, + Children = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(size: 30), + Current = RoomName + }, + new RoomStatusInfo(), + } + }, + hostInfo = new HostInfo(), + }, + }, + new FillFlowContainer + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.X, + Height = 70, + Spacing = new Vector2(10, 0), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + viewBeatmapButton = new ViewBeatmapButton(), + readyButton = new ReadyButton + { + Action = () => OnStart?.Invoke() + } + } + } + }, + }, + }; + + CurrentItem.BindValueChanged(item => + { + viewBeatmapButton.Beatmap.Value = item.NewValue?.Beatmap; + readyButton.Beatmap.Value = item.NewValue?.Beatmap; + }, true); + + hostInfo.Host.BindTo(Host); + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchChatDisplay.cs b/osu.Game/Screens/Multi/Match/Components/MatchChatDisplay.cs new file mode 100644 index 0000000000..f8b64a54ef --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/MatchChatDisplay.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Chat; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class MatchChatDisplay : StandAloneChatDisplay + { + [Resolved(typeof(Room), nameof(Room.RoomID))] + private Bindable roomId { get; set; } + + [Resolved(typeof(Room), nameof(Room.ChannelId))] + private Bindable channelId { get; set; } + + [Resolved(CanBeNull = true)] + private ChannelManager channelManager { get; set; } + + public MatchChatDisplay() + : base(true) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + channelId.BindValueChanged(_ => updateChannel(), true); + } + + private void updateChannel() + { + if (roomId.Value == null || channelId.Value == 0) + return; + + Channel.Value = channelManager?.JoinChannel(new Channel { Id = channelId.Value, Type = ChannelType.Multiplayer, Name = $"#lazermp_{roomId.Value}" }); + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs new file mode 100644 index 0000000000..fff713f026 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class MatchLeaderboard : Leaderboard + { + public Action> ScoresLoaded; + + [Resolved(typeof(Room), nameof(Room.RoomID))] + private Bindable roomId { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + roomId.BindValueChanged(id => + { + if (id.NewValue == null) + return; + + Scores = null; + UpdateScores(); + }, true); + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + if (roomId.Value == null) + return null; + + var req = new GetRoomScoresRequest(roomId.Value ?? 0); + + req.Success += r => + { + scoresCallback?.Invoke(r); + ScoresLoaded?.Invoke(r); + }; + + return req; + } + + protected override LeaderboardScore CreateDrawableScore(APIRoomScoreInfo model, int index) => new MatchLeaderboardScore(model, index); + } + + public enum MatchLeaderboardScope + { + Overall + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs new file mode 100644 index 0000000000..92074abe4b --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboardScore.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class MatchLeaderboardScore : LeaderboardScore + { + public MatchLeaderboardScore(APIRoomScoreInfo score, int rank) + : base(score, rank) + { + } + + [BackgroundDependencyLoader] + private void load() + { + RankContainer.Alpha = 0; + } + + protected override IEnumerable GetStatistics(ScoreInfo model) => new[] + { + new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy)), + new LeaderboardScoreStatistic(FontAwesome.Solid.Sync, "Total Attempts", ((APIRoomScoreInfo)model).TotalAttempts.ToString()), + new LeaderboardScoreStatistic(FontAwesome.Solid.Check, "Completed Beatmaps", ((APIRoomScoreInfo)model).CompletedBeatmaps.ToString()), + }; + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchPage.cs b/osu.Game/Screens/Multi/Match/Components/MatchPage.cs new file mode 100644 index 0000000000..fc98db157b --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/MatchPage.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public abstract class MatchPage + { + public abstract string Name { get; } + + public readonly BindableBool Enabled = new BindableBool(true); + + public override string ToString() => Name; + public override int GetHashCode() => GetType().GetHashCode(); + public override bool Equals(object obj) => GetType() == obj?.GetType(); + } + + public class SettingsMatchPage : MatchPage + { + public override string Name => "Settings"; + } + + public class RoomMatchPage : MatchPage + { + public override string Name => "Room"; + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs new file mode 100644 index 0000000000..410aaed788 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -0,0 +1,418 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class MatchSettingsOverlay : FocusedOverlayContainer + { + private const float transition_duration = 350; + private const float field_padding = 45; + + protected MatchSettings Settings { get; private set; } + + [BackgroundDependencyLoader] + private void load() + { + Masking = true; + + Child = Settings = new MatchSettings + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Y + }; + } + + protected override void PopIn() + { + Settings.MoveToY(0, transition_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + Settings.MoveToY(-1, transition_duration, Easing.InSine); + } + + protected class MatchSettings : MultiplayerComposite + { + private const float disabled_alpha = 0.2f; + + public OsuTextBox NameField, MaxParticipantsField; + public OsuDropdown DurationField; + public RoomAvailabilityPicker AvailabilityPicker; + public GameTypePicker TypePicker; + public TriangleButton ApplyButton; + + public OsuSpriteText ErrorText; + + private OsuSpriteText typeLabel; + private ProcessingOverlay processingOverlay; + + [Resolved(CanBeNull = true)] + private IRoomManager manager { get; set; } + + [Resolved] + private Bindable currentRoom { get; set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.Distributed), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] + { + new OsuScrollContainer + { + Padding = new MarginPadding + { + Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING, + Vertical = 10 + }, + RelativeSizeAxes = Axes.Both, + Children = new[] + { + new Container + { + Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new SectionContainer + { + Padding = new MarginPadding { Right = field_padding / 2 }, + Children = new[] + { + new Section("Room name") + { + Child = NameField = new SettingsTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + new Section("Room visibility") + { + Alpha = disabled_alpha, + Child = AvailabilityPicker = new RoomAvailabilityPicker + { + Enabled = { Value = false } + }, + }, + new Section("Game type") + { + Alpha = disabled_alpha, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(7), + Children = new Drawable[] + { + TypePicker = new GameTypePicker + { + RelativeSizeAxes = Axes.X, + Enabled = { Value = false } + }, + typeLabel = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14), + Colour = colours.Yellow + }, + }, + }, + }, + }, + }, + new SectionContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Padding = new MarginPadding { Left = field_padding / 2 }, + Children = new[] + { + new Section("Max participants") + { + Alpha = disabled_alpha, + Child = MaxParticipantsField = new SettingsNumberTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + ReadOnly = true, + OnCommit = (sender, text) => apply() + }, + }, + new Section("Duration") + { + Child = DurationField = new DurationDropdown + { + RelativeSizeAxes = Axes.X, + Items = new[] + { + TimeSpan.FromMinutes(30), + TimeSpan.FromHours(1), + TimeSpan.FromHours(2), + TimeSpan.FromHours(4), + TimeSpan.FromHours(8), + TimeSpan.FromHours(12), + //TimeSpan.FromHours(16), + TimeSpan.FromHours(24), + TimeSpan.FromDays(3), + TimeSpan.FromDays(7) + } + } + }, + new Section("Password (optional)") + { + Alpha = disabled_alpha, + Child = new SettingsPasswordTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + ReadOnly = true, + OnCommit = (sender, text) => apply() + }, + }, + }, + }, + }, + } + }, + }, + }, + new Drawable[] + { + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Y = 2, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d").Darken(0.5f).Opacity(1f), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding { Vertical = 20 }, + Padding = new MarginPadding { Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, + Children = new Drawable[] + { + ApplyButton = new CreateRoomButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(230, 55), + Enabled = { Value = false }, + Action = apply, + }, + ErrorText = new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Alpha = 0, + Depth = 1, + Colour = colours.RedDark + } + } + } + } + } + } + } + }, + processingOverlay = new ProcessingOverlay { Alpha = 0 } + }; + + TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true); + RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true); + Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true); + Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true); + MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true); + Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue, true); + } + + protected override void Update() + { + base.Update(); + + ApplyButton.Enabled.Value = hasValidSettings; + } + + private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0; + + private void apply() + { + hideError(); + + RoomName.Value = NameField.Text; + Availability.Value = AvailabilityPicker.Current.Value; + Type.Value = TypePicker.Current.Value; + + if (int.TryParse(MaxParticipantsField.Text, out int max)) + MaxParticipants.Value = max; + else + MaxParticipants.Value = null; + + Duration.Value = DurationField.Current.Value; + + manager?.CreateRoom(currentRoom.Value, onSuccess, onError); + + processingOverlay.Show(); + } + + private void hideError() => ErrorText.FadeOut(50); + + private void onSuccess(Room room) => processingOverlay.Hide(); + + private void onError(string text) + { + ErrorText.Text = text; + ErrorText.FadeIn(50); + + processingOverlay.Hide(); + } + } + + private class SettingsTextBox : OsuTextBox + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = Color4.Black; + BackgroundFocused = Color4.Black; + } + } + + private class SettingsNumberTextBox : SettingsTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } + + private class SettingsPasswordTextBox : OsuPasswordTextBox + { + [BackgroundDependencyLoader] + private void load() + { + BackgroundUnfocused = Color4.Black; + BackgroundFocused = Color4.Black; + } + } + + private class SectionContainer : FillFlowContainer
+ { + public SectionContainer() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Width = 0.5f; + Direction = FillDirection.Vertical; + Spacing = new Vector2(field_padding); + } + } + + private class Section : Container + { + private readonly Container content; + + protected override Container Content => content; + + public Section(string title) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), + Text = title.ToUpper(), + }, + content = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, + }, + }; + } + } + + private class CreateRoomButton : TriangleButton + { + public CreateRoomButton() + { + Text = "Create"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Yellow; + Triangles.ColourLight = colours.YellowLight; + Triangles.ColourDark = colours.YellowDark; + } + } + + private class DurationDropdown : OsuDropdown + { + public DurationDropdown() + { + Menu.MaxHeight = 100; + } + + protected override string GenerateItemText(TimeSpan item) + { + return item.Humanize(); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/MatchTabControl.cs b/osu.Game/Screens/Multi/Match/Components/MatchTabControl.cs new file mode 100644 index 0000000000..c700d7b88a --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/MatchTabControl.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class MatchTabControl : PageTabControl + { + [Resolved(typeof(Room), nameof(Room.RoomID))] + private Bindable roomId { get; set; } + + public MatchTabControl() + { + AddItem(new RoomMatchPage()); + AddItem(new SettingsMatchPage()); + } + + [BackgroundDependencyLoader] + private void load() + { + roomId.BindValueChanged(id => + { + if (id.NewValue.HasValue) + { + Items.ForEach(t => t.Enabled.Value = !(t is SettingsMatchPage)); + Current.Value = new RoomMatchPage(); + } + else + { + Items.ForEach(t => t.Enabled.Value = t is SettingsMatchPage); + Current.Value = new SettingsMatchPage(); + } + }, true); + } + + protected override TabItem CreateTabItem(MatchPage value) => new TabItem(value); + + private class TabItem : PageTabItem + { + private readonly IBindable enabled = new BindableBool(); + + public TabItem(MatchPage value) + : base(value) + { + enabled.BindTo(value.Enabled); + enabled.BindValueChanged(enabled => Colour = enabled.NewValue ? Color4.White : Color4.Gray, true); + } + + protected override bool OnClick(ClickEvent e) + { + if (!enabled.Value) + return true; + + return base.OnClick(e); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Match/Participants.cs b/osu.Game/Screens/Multi/Match/Components/Participants.cs similarity index 57% rename from osu.Game/Screens/Multi/Screens/Match/Participants.cs rename to osu.Game/Screens/Multi/Match/Components/Participants.cs index 55541a1acd..ad38ec6a99 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Participants.cs +++ b/osu.Game/Screens/Multi/Match/Components/Participants.cs @@ -1,57 +1,38 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Components; using osu.Game.Users; using osuTK; -namespace osu.Game.Screens.Multi.Screens.Match +namespace osu.Game.Screens.Multi.Match.Components { - public class Participants : Container + public class Participants : MultiplayerComposite { - private readonly ParticipantCount count; - private readonly FillFlowContainer usersFlow; - - public IEnumerable Users + [BackgroundDependencyLoader] + private void load() { - set { - usersFlow.Children = value.Select(u => new UserPanel(u) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Width = 300, - OnLoadComplete = d => d.FadeInFromZero(60), - }).ToList(); + FillFlowContainer usersFlow; - count.Count = value.Count(); - } - } - - public int? Max - { - set => count.Max = value; - } - - public Participants() - { - Child = new Container + InternalChild = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, Children = new Drawable[] { - new ScrollContainer + new OsuScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 10 }, Children = new Drawable[] { - count = new ParticipantCount + new ParticipantCountDisplay { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -69,6 +50,23 @@ namespace osu.Game.Screens.Multi.Screens.Match }, }, }; + + Participants.BindValueChanged(participants => + { + usersFlow.Children = participants.NewValue.Select(u => + { + var panel = new UserPanel(u) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 300, + }; + + panel.OnLoadComplete += d => d.FadeInFromZero(60); + + return panel; + }).ToList(); + }, true); } } } diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs new file mode 100644 index 0000000000..8ab0b8f61f --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osuTK; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class ReadyButton : HeaderButton + { + public readonly Bindable Beatmap = new Bindable(); + + [Resolved(typeof(Room), nameof(Room.EndDate))] + private Bindable endDate { get; set; } + + [Resolved] + private IBindable gameBeatmap { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + private bool hasBeatmap; + + public ReadyButton() + { + RelativeSizeAxes = Axes.Y; + Size = new Vector2(200, 1); + + Text = "Start"; + } + + [BackgroundDependencyLoader] + private void load() + { + beatmaps.ItemAdded += beatmapAdded; + beatmaps.ItemRemoved += beatmapRemoved; + + Beatmap.BindValueChanged(b => updateBeatmap(b.NewValue), true); + } + + private void updateBeatmap(BeatmapInfo beatmap) + { + hasBeatmap = false; + + if (beatmap?.OnlineBeatmapID == null) + return; + + hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null; + } + + private void beatmapAdded(BeatmapSetInfo model) + { + if (model.Beatmaps.Any(b => b.OnlineBeatmapID == Beatmap.Value.OnlineBeatmapID)) + Schedule(() => hasBeatmap = true); + } + + private void beatmapRemoved(BeatmapSetInfo model) + { + if (Beatmap.Value == null) + return; + + if (model.OnlineBeatmapSetID == Beatmap.Value.BeatmapSet.OnlineBeatmapSetID) + Schedule(() => hasBeatmap = false); + } + + protected override void Update() + { + base.Update(); + + updateEnabledState(); + } + + private void updateEnabledState() + { + if (gameBeatmap.Value == null) + { + Enabled.Value = false; + return; + } + + bool hasEnoughTime = DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < endDate.Value; + + Enabled.Value = hasBeatmap && hasEnoughTime; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmaps != null) + beatmaps.ItemAdded -= beatmapAdded; + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs new file mode 100644 index 0000000000..9de4a61cde --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/RoomAvailabilityPicker.cs @@ -0,0 +1,107 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Components; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class RoomAvailabilityPicker : DisableableTabControl + { + protected override TabItem CreateTabItem(RoomAvailability value) => new RoomAvailabilityPickerItem(value); + protected override Dropdown CreateDropdown() => null; + + public RoomAvailabilityPicker() + { + RelativeSizeAxes = Axes.X; + Height = 35; + + TabContainer.Spacing = new Vector2(10); + + AddItem(RoomAvailability.Public); + AddItem(RoomAvailability.FriendsOnly); + AddItem(RoomAvailability.InviteOnly); + } + + private class RoomAvailabilityPickerItem : DisableableTabItem + { + private const float transition_duration = 200; + + private readonly Box hover, selection; + + public RoomAvailabilityPickerItem(RoomAvailability value) + : base(value) + { + RelativeSizeAxes = Axes.Y; + Width = 102; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"3d3943"), + }, + selection = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = value.GetDescription(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.GreenLight; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/ViewBeatmapButton.cs b/osu.Game/Screens/Multi/Match/Components/ViewBeatmapButton.cs new file mode 100644 index 0000000000..8d1ff21124 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/ViewBeatmapButton.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osuTK; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class ViewBeatmapButton : HeaderButton + { + public readonly Bindable Beatmap = new Bindable(); + + [Resolved(CanBeNull = true)] + private OsuGame osuGame { get; set; } + + public ViewBeatmapButton() + { + RelativeSizeAxes = Axes.Y; + Size = new Vector2(200, 1); + + Text = "View beatmap"; + } + + [BackgroundDependencyLoader] + private void load() + { + if (osuGame != null) + Beatmap.BindValueChanged(beatmap => updateAction(beatmap.NewValue), true); + } + + private void updateAction(BeatmapInfo beatmap) + { + if (beatmap == null) + { + Enabled.Value = false; + return; + } + + Action = () => osuGame.ShowBeatmap(beatmap.OnlineBeatmapID ?? 0); + Enabled.Value = true; + } + } +} diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs new file mode 100644 index 0000000000..5469d4c8c8 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -0,0 +1,246 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Multiplayer.GameTypes; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Screens.Multi.Play; +using osu.Game.Screens.Select; +using PlaylistItem = osu.Game.Online.Multiplayer.PlaylistItem; + +namespace osu.Game.Screens.Multi.Match +{ + public class MatchSubScreen : MultiplayerSubScreen + { + public override bool DisallowExternalBeatmapRulesetChanges => true; + + public override string Title { get; } + + public override string ShortTitle => "room"; + + [Resolved(typeof(Room), nameof(Room.RoomID))] + private Bindable roomId { get; set; } + + [Resolved(typeof(Room), nameof(Room.Name))] + private Bindable name { get; set; } + + [Resolved(typeof(Room), nameof(Room.Type))] + private Bindable type { get; set; } + + [Resolved(typeof(Room))] + protected BindableList Playlist { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable CurrentItem { get; private set; } + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } + + private MatchLeaderboard leaderboard; + + public MatchSubScreen(Room room) + { + Title = room.RoomID.Value == null ? "New room" : room.Name.Value; + } + + [BackgroundDependencyLoader] + private void load() + { + MatchChatDisplay chat; + Components.Header header; + Info info; + GridContainer bottomRow; + MatchSettingsOverlay settings; + + InternalChildren = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + header = new Components.Header + { + Depth = -1, + RequestBeatmapSelection = () => + { + this.Push(new MatchSongSelect + { + Selected = item => + { + Playlist.Clear(); + Playlist.Add(item); + } + }); + } + } + }, + new Drawable[] { info = new Info { OnStart = onStart } }, + new Drawable[] + { + bottomRow = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + leaderboard = new MatchLeaderboard + { + Padding = new MarginPadding + { + Left = 10 + HORIZONTAL_OVERFLOW_PADDING, + Right = 10, + Vertical = 10, + }, + RelativeSizeAxes = Axes.Both + }, + new Container + { + Padding = new MarginPadding + { + Left = 10, + Right = 10 + HORIZONTAL_OVERFLOW_PADDING, + Vertical = 10, + }, + RelativeSizeAxes = Axes.Both, + Child = chat = new MatchChatDisplay + { + RelativeSizeAxes = Axes.Both + } + }, + }, + }, + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed), + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = Components.Header.HEIGHT }, + Child = settings = new MatchSettingsOverlay { RelativeSizeAxes = Axes.Both }, + }, + }; + + header.Tabs.Current.BindValueChanged(tab => + { + const float fade_duration = 500; + + if (tab.NewValue is SettingsMatchPage) + { + settings.Show(); + info.FadeOut(fade_duration, Easing.OutQuint); + bottomRow.FadeOut(fade_duration, Easing.OutQuint); + } + else + { + settings.Hide(); + info.FadeIn(fade_duration, Easing.OutQuint); + bottomRow.FadeIn(fade_duration, Easing.OutQuint); + } + }, true); + + chat.Exit += () => + { + if (this.IsCurrentScreen()) + this.Exit(); + }; + + beatmapManager.ItemAdded += beatmapAdded; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentItem.BindValueChanged(currentItemChanged, true); + } + + public override bool OnExiting(IScreen next) + { + RoomManager?.PartRoom(); + + Mods.Value = Array.Empty(); + + return base.OnExiting(next); + } + + /// + /// Handles propagation of the current playlist item's content to game-wide mechanisms. + /// + private void currentItemChanged(ValueChangedEvent e) + { + // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info + var localBeatmap = e.NewValue?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == e.NewValue.Beatmap.OnlineBeatmapID); + + Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); + Mods.Value = e.NewValue?.RequiredMods?.ToArray() ?? Array.Empty(); + + if (e.NewValue?.Ruleset != null) + Ruleset.Value = e.NewValue.Ruleset; + } + + /// + /// Handle the case where a beatmap is imported (and can be used by this match). + /// + private void beatmapAdded(BeatmapSetInfo model) => Schedule(() => + { + if (Beatmap.Value != beatmapManager.DefaultBeatmap) + return; + + if (CurrentItem.Value == null) + return; + + // Try to retrieve the corresponding local beatmap + var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == CurrentItem.Value.Beatmap.OnlineBeatmapID); + + if (localBeatmap != null) + Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); + }); + + [Resolved(canBeNull: true)] + private Multiplayer multiplayer { get; set; } + + private void onStart() + { + switch (type.Value) + { + default: + case GameTypeTimeshift _: + multiplayer?.Start(() => new TimeshiftPlayer(CurrentItem.Value) + { + Exited = () => leaderboard.RefreshScores() + }); + break; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmapManager != null) + beatmapManager.ItemAdded -= beatmapAdded; + } + } +} diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 7822fa68dc..9ffd620e55 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -1,95 +1,313 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input; +using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Screens.Menu; -using osu.Game.Screens.Multi.Screens.Lounge; +using osu.Game.Screens.Multi.Lounge; +using osu.Game.Screens.Multi.Lounge.Components; +using osu.Game.Screens.Multi.Match; +using osu.Game.Screens.Play; +using osuTK; namespace osu.Game.Screens.Multi { - public class Multiplayer : OsuScreen + [Cached] + public class Multiplayer : OsuScreen, IOnlineComponent { + public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true; + + public override bool DisallowExternalBeatmapRulesetChanges => true; + private readonly MultiplayerWaveContainer waves; - protected override Container Content => waves; + private readonly OsuButton createButton; + private readonly LoungeSubScreen loungeSubScreen; + private readonly ScreenStack screenStack; + + private readonly IBindable isIdle = new BindableBool(); + + [Cached] + private readonly Bindable currentRoom = new Bindable(); + + [Cached] + private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); + + [Cached(Type = typeof(IRoomManager))] + private RoomManager roomManager; + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved(CanBeNull = true)] + private OsuLogo logo { get; set; } public Multiplayer() { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + Padding = new MarginPadding { Horizontal = -HORIZONTAL_OVERFLOW_PADDING }; + InternalChild = waves = new MultiplayerWaveContainer { RelativeSizeAxes = Axes.Both, - }; - - Lounge lounge; - Children = new Drawable[] - { - new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] + new Container { - new Box + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"3e3a44"), - }, - new Triangles - { - RelativeSizeAxes = Axes.Both, - ColourLight = OsuColour.FromHex(@"3c3842"), - ColourDark = OsuColour.FromHex(@"393540"), - TriangleScale = 5, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"3e3a44"), + }, + new Triangles + { + RelativeSizeAxes = Axes.Both, + ColourLight = OsuColour.FromHex(@"3c3842"), + ColourDark = OsuColour.FromHex(@"393540"), + TriangleScale = 5, + }, }, }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = Header.HEIGHT }, - Child = lounge = new Lounge(), - }, - new Header(lounge), + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = Header.HEIGHT }, + Child = screenStack = new OsuScreenStack(loungeSubScreen = new LoungeSubScreen()) { RelativeSizeAxes = Axes.Both } + }, + new Header(screenStack), + createButton = new HeaderButton + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.None, + Size = new Vector2(150, Header.HEIGHT - 20), + Margin = new MarginPadding + { + Top = 10, + Right = 10 + HORIZONTAL_OVERFLOW_PADDING, + }, + Text = "Create room", + Action = () => loungeSubScreen.Open(new Room + { + Name = { Value = $"{api.LocalUser}'s awesome room" } + }), + }, + roomManager = new RoomManager() + } }; - lounge.Exited += s => Exit(); + screenStack.ScreenPushed += screenPushed; + screenStack.ScreenExited += screenExited; } - protected override void OnEntering(Screen last) + [BackgroundDependencyLoader(true)] + private void load(IdleTracker idleTracker) { - base.OnEntering(last); + api.Register(this); + + if (idleTracker != null) + isIdle.BindTo(idleTracker.IsIdle); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + isIdle.BindValueChanged(idle => updatePollingRate(idle.NewValue), true); + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Model.BindTo(currentRoom); + return dependencies; + } + + private void updatePollingRate(bool idle) + { + roomManager.TimeBetweenPolls = !this.IsCurrentScreen() || !(screenStack.CurrentScreen is LoungeSubScreen) ? 0 : (idle ? 120000 : 15000); + Logger.Log($"Polling adjusted to {roomManager.TimeBetweenPolls}"); + } + + /// + /// Push a to the main screen stack to begin gameplay. + /// Generally called from a via DI resolution. + /// + public void Start(Func player) + { + if (!this.IsCurrentScreen()) + return; + + this.Push(new PlayerLoader(player)); + } + + public void APIStateChanged(IAPIProvider api, APIState state) + { + if (state != APIState.Online) + forcefullyExit(); + } + + private void forcefullyExit() + { + // This is temporary since we don't currently have a way to force screens to be exited + if (this.IsCurrentScreen()) + this.Exit(); + else + { + this.MakeCurrent(); + Schedule(forcefullyExit); + } + } + + public override void OnEntering(IScreen last) + { + this.FadeIn(); waves.Show(); + + beginHandlingTrack(); } - protected override bool OnExiting(Screen next) + public override void OnResuming(IScreen last) { - waves.Hide(); - return base.OnExiting(next); - } + this.FadeIn(250); + this.ScaleTo(1, 250, Easing.OutSine); - protected override void OnResuming(Screen last) - { base.OnResuming(last); - waves.Show(); + + beginHandlingTrack(); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { - base.OnSuspending(next); + this.ScaleTo(1.1f, 250, Easing.InSine); + this.FadeOut(250); + + endHandlingTrack(); + + roomManager.TimeBetweenPolls = 0; + } + + public override bool OnExiting(IScreen next) + { + if (!(screenStack.CurrentScreen is LoungeSubScreen)) + { + screenStack.Exit(); + return true; + } + waves.Hide(); + + this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); + + if (screenStack.CurrentScreen != null) + loungeSubScreen.MakeCurrent(); + + endHandlingTrack(); + + base.OnExiting(next); + return false; } protected override void LogoExiting(OsuLogo logo) { + base.LogoExiting(logo); + // the wave overlay transition takes longer than expected to run. logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut(); - base.LogoExiting(logo); + } + + private void beginHandlingTrack() + { + Beatmap.BindValueChanged(updateTrack, true); + } + + private void endHandlingTrack() + { + cancelLooping(); + Beatmap.ValueChanged -= updateTrack; + } + + private void screenPushed(IScreen lastScreen, IScreen newScreen) => subScreenChanged(newScreen); + + private void screenExited(IScreen lastScreen, IScreen newScreen) + { + subScreenChanged(newScreen); + + if (screenStack.CurrentScreen == null && this.IsCurrentScreen()) + this.Exit(); + } + + private void subScreenChanged(IScreen newScreen) + { + updatePollingRate(isIdle.Value); + createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200); + + updateTrack(); + } + + private void updateTrack(ValueChangedEvent _ = null) + { + bool isMatch = screenStack.CurrentScreen is MatchSubScreen; + + Beatmap.Disabled = isMatch; + + if (isMatch) + { + var track = Beatmap.Value?.Track; + + if (track != null) + { + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.Looping = true; + + if (!track.IsRunning) + track.Restart(); + } + } + else + { + cancelLooping(); + } + } + + private void cancelLooping() + { + var track = Beatmap?.Value?.Track; + + if (track != null) + { + track.Looping = false; + track.RestartPoint = 0; + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + api?.Unregister(this); } private class MultiplayerWaveContainer : WaveContainer diff --git a/osu.Game/Screens/Multi/MultiplayerComposite.cs b/osu.Game/Screens/Multi/MultiplayerComposite.cs new file mode 100644 index 0000000000..8c09d576ff --- /dev/null +++ b/osu.Game/Screens/Multi/MultiplayerComposite.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Multiplayer; +using osu.Game.Users; + +namespace osu.Game.Screens.Multi +{ + public class MultiplayerComposite : CompositeDrawable + { + [Resolved(typeof(Room))] + protected Bindable RoomID { get; private set; } + + [Resolved(typeof(Room), nameof(Room.Name))] + protected Bindable RoomName { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable Host { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable Status { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable Type { get; private set; } + + [Resolved(typeof(Room))] + protected BindableList Playlist { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable CurrentItem { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable> Participants { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable ParticipantCount { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable MaxParticipants { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable EndDate { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable Availability { get; private set; } + + [Resolved(typeof(Room))] + protected Bindable Duration { get; private set; } + } +} diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreen.cs b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs new file mode 100644 index 0000000000..ff94f63f01 --- /dev/null +++ b/osu.Game/Screens/Multi/MultiplayerSubScreen.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens.Multi +{ + public abstract class MultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen + { + public override bool DisallowExternalBeatmapRulesetChanges => false; + + public virtual string ShortTitle => Title; + + [Resolved(CanBeNull = true)] + protected IRoomManager RoomManager { get; private set; } + + protected MultiplayerSubScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; + } + + public override void OnEntering(IScreen last) + { + this.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + } + + public override bool OnExiting(IScreen next) + { + this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + this.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + + return false; + } + + public override void OnResuming(IScreen last) + { + this.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); + this.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); + } + + public override void OnSuspending(IScreen next) + { + this.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + this.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); + } + + public override string ToString() => Title; + } +} diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs new file mode 100644 index 0000000000..88c6fc5e2e --- /dev/null +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -0,0 +1,121 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Logging; +using osu.Framework.Screens; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Screens.Multi.Ranking; +using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.Multi.Play +{ + public class TimeshiftPlayer : Player + { + public Action Exited; + + [Resolved(typeof(Room), nameof(Room.RoomID))] + private Bindable roomId { get; set; } + + private readonly PlaylistItem playlistItem; + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + public TimeshiftPlayer(PlaylistItem playlistItem) + { + this.playlistItem = playlistItem; + } + + private int? token; + + [BackgroundDependencyLoader] + private void load() + { + token = null; + + bool failed = false; + + // Sanity checks to ensure that TimeshiftPlayer matches the settings for the current PlaylistItem + if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != playlistItem.Beatmap.OnlineBeatmapID) + throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); + + if (ruleset.Value.ID != playlistItem.Ruleset.ID) + throw new InvalidOperationException("Current Ruleset does not match PlaylistItem's Ruleset"); + + if (!playlistItem.RequiredMods.All(m => Mods.Value.Any(m.Equals))) + throw new InvalidOperationException("Current Mods do not match PlaylistItem's RequiredMods"); + + var req = new CreateRoomScoreRequest(roomId.Value ?? 0, playlistItem.ID); + req.Success += r => token = r.ID; + req.Failure += e => + { + failed = true; + + Logger.Error(e, "Failed to retrieve a score submission token."); + + Schedule(() => + { + ValidForResume = false; + this.Exit(); + }); + }; + + api.Queue(req); + + while (!failed && !token.HasValue) + Thread.Sleep(1000); + } + + public override bool OnExiting(IScreen next) + { + if (base.OnExiting(next)) + return true; + + Exited?.Invoke(); + + return false; + } + + protected override ScoreInfo CreateScore() + { + submitScore(); + return base.CreateScore(); + } + + private void submitScore() + { + var score = base.CreateScore(); + + score.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); + + Debug.Assert(token != null); + + var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score); + request.Failure += e => Logger.Error(e, "Failed to submit score"); + api.Queue(request); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + Exited = null; + } + + protected override Results CreateResults(ScoreInfo score) => new MatchResults(score); + } +} diff --git a/osu.Game/Screens/Multi/Ranking/MatchResults.cs b/osu.Game/Screens/Multi/Ranking/MatchResults.cs new file mode 100644 index 0000000000..fe68d7e849 --- /dev/null +++ b/osu.Game/Screens/Multi/Ranking/MatchResults.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Scoring; +using osu.Game.Screens.Multi.Ranking.Types; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Types; + +namespace osu.Game.Screens.Multi.Ranking +{ + public class MatchResults : Results + { + public MatchResults(ScoreInfo score) + : base(score) + { + } + + protected override IEnumerable CreateResultPages() => new IResultPageInfo[] + { + new ScoreOverviewPageInfo(Score, Beatmap.Value), + new LocalLeaderboardPageInfo(Score, Beatmap.Value), + new RoomLeaderboardPageInfo(Score, Beatmap.Value), + }; + } +} diff --git a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs new file mode 100644 index 0000000000..d20b021fc6 --- /dev/null +++ b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs @@ -0,0 +1,135 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Internal; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Lists; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Leaderboards; +using osu.Game.Online.Multiplayer; +using osu.Game.Scoring; +using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.Multi.Ranking.Pages +{ + public class RoomLeaderboardPage : ResultsPage + { + private OsuColour colours; + private TextFlowContainer rankText; + + [Resolved(typeof(Room), nameof(Room.Name))] + private Bindable name { get; set; } + + public RoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap) + : base(score, beatmap) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + this.colours = colours; + + MatchLeaderboard leaderboard; + + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray6, + RelativeSizeAxes = Axes.Both, + }, + new BufferedContainer + { + RelativeSizeAxes = Axes.Both, + BackgroundColour = colours.Gray6, + Child = leaderboard = CreateLeaderboard() + }, + rankText = new TextFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Width = 0.5f, + AutoSizeAxes = Axes.Y, + Y = 50, + TextAnchor = Anchor.TopCentre + }, + }; + + leaderboard.Origin = Anchor.Centre; + leaderboard.Anchor = Anchor.Centre; + leaderboard.RelativeSizeAxes = Axes.Both; + leaderboard.Height = 0.8f; + leaderboard.Y = 55; + leaderboard.ScoresLoaded = scoresLoaded; + } + + private void scoresLoaded(IEnumerable scores) + { + Action gray = s => s.Colour = colours.GrayC; + Action white = s => + { + s.Font = s.Font.With(size: s.Font.Size * 1.4f); + s.Colour = colours.GrayF; + }; + + rankText.AddText(name + "\n", white); + rankText.AddText("You are placed ", gray); + + int index = scores.IndexOf(new APIRoomScoreInfo { User = Score.User }, new FuncEqualityComparer((s1, s2) => s1.User.Id.Equals(s2.User.Id))); + + rankText.AddText($"#{index + 1} ", s => + { + s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold); + s.Colour = colours.YellowDark; + }); + + rankText.AddText("in the room!", gray); + } + + protected virtual MatchLeaderboard CreateLeaderboard() => new ResultsMatchLeaderboard(); + + public class ResultsMatchLeaderboard : MatchLeaderboard + { + protected override bool FadeTop => true; + + protected override LeaderboardScore CreateDrawableScore(APIRoomScoreInfo model, int index) + => new ResultsMatchLeaderboardScore(model, index); + + protected override FillFlowContainer CreateScoreFlow() + { + var flow = base.CreateScoreFlow(); + flow.Padding = new MarginPadding + { + Top = LeaderboardScore.HEIGHT * 2, + Bottom = LeaderboardScore.HEIGHT * 3, + }; + return flow; + } + + private class ResultsMatchLeaderboardScore : MatchLeaderboardScore + { + public ResultsMatchLeaderboardScore(APIRoomScoreInfo score, int rank) + : base(score, rank) + { + } + + [BackgroundDependencyLoader] + private void load() + { + } + } + } + } +} diff --git a/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs new file mode 100644 index 0000000000..dcfad8458f --- /dev/null +++ b/osu.Game/Screens/Multi/Ranking/Types/RoomLeaderboardPageInfo.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Scoring; +using osu.Game.Screens.Multi.Ranking.Pages; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.Multi.Ranking.Types +{ + public class RoomLeaderboardPageInfo : IResultPageInfo + { + private readonly ScoreInfo score; + private readonly WorkingBeatmap beatmap; + + public RoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap) + { + this.score = score; + this.beatmap = beatmap; + } + + public IconUsage Icon => FontAwesome.Solid.Users; + + public string Name => "Room Leaderboard"; + + public virtual ResultsPage CreatePage() => new RoomLeaderboardPage(score, beatmap); + } +} diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs new file mode 100644 index 0000000000..6f473aaafa --- /dev/null +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -0,0 +1,184 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.Online; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Screens.Multi.Lounge.Components; + +namespace osu.Game.Screens.Multi +{ + public class RoomManager : PollingComponent, IRoomManager + { + public event Action RoomsUpdated; + + private readonly BindableList rooms = new BindableList(); + public IBindableList Rooms => rooms; + + private Room joinedRoom; + + [Resolved] + private Bindable currentFilter { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + currentFilter.BindValueChanged(_ => + { + if (IsLoaded) + PollImmediately(); + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + PartRoom(); + } + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) + { + room.Host.Value = api.LocalUser.Value; + + var req = new CreateRoomRequest(room); + + req.Success += result => + { + joinedRoom = room; + + update(room, result); + addRoom(room); + + RoomsUpdated?.Invoke(); + onSuccess?.Invoke(room); + }; + + req.Failure += exception => + { + if (req.Result != null) + onError?.Invoke(req.Result.Error); + else + Logger.Log($"Failed to create the room: {exception}", level: LogLevel.Important); + }; + + api.Queue(req); + } + + private JoinRoomRequest currentJoinRoomRequest; + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) + { + currentJoinRoomRequest?.Cancel(); + currentJoinRoomRequest = null; + + currentJoinRoomRequest = new JoinRoomRequest(room, api.LocalUser.Value); + currentJoinRoomRequest.Success += () => + { + joinedRoom = room; + onSuccess?.Invoke(room); + }; + + currentJoinRoomRequest.Failure += exception => + { + Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); + onError?.Invoke(exception.ToString()); + }; + + api.Queue(currentJoinRoomRequest); + } + + public void PartRoom() + { + if (joinedRoom == null) + return; + + api.Queue(new PartRoomRequest(joinedRoom, api.LocalUser.Value)); + joinedRoom = null; + } + + private GetRoomsRequest pollReq; + + protected override Task Poll() + { + if (!api.IsLoggedIn) + return base.Poll(); + + var tcs = new TaskCompletionSource(); + + pollReq?.Cancel(); + pollReq = new GetRoomsRequest(currentFilter.Value.PrimaryFilter); + + pollReq.Success += result => + { + // Remove past matches + foreach (var r in rooms.ToList()) + { + if (result.All(e => e.RoomID.Value != r.RoomID.Value)) + rooms.Remove(r); + } + + for (int i = 0; i < result.Count; i++) + { + var r = result[i]; + r.Position.Value = i; + + update(r, r); + addRoom(r); + } + + RoomsUpdated?.Invoke(); + tcs.SetResult(true); + }; + + pollReq.Failure += _ => tcs.SetResult(false); + + api.Queue(pollReq); + + return tcs.Task; + } + + /// + /// Updates a local with a remote copy. + /// + /// The local to update. + /// The remote to update with. + private void update(Room local, Room remote) + { + foreach (var pi in remote.Playlist) + pi.MapObjects(beatmaps, rulesets); + + local.CopyFrom(remote); + } + + /// + /// Adds a to the list of available rooms. + /// + /// The to add. + private void addRoom(Room room) + { + var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); + if (existing == null) + rooms.Add(room); + else + existing.CopyFrom(room); + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs b/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs deleted file mode 100644 index 69cb6ad349..0000000000 --- a/osu.Game/Screens/Multi/Screens/Lounge/FilterControl.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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 osuTK.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 deleted file mode 100644 index 0af941a668..0000000000 --- a/osu.Game/Screens/Multi/Screens/Lounge/Lounge.cs +++ /dev/null @@ -1,191 +0,0 @@ -// 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.Events; -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 osuTK; - -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(FocusEvent e) - { - 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.Match(room.Room)); - } - - 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/Match/Header.cs b/osu.Game/Screens/Multi/Screens/Match/Header.cs deleted file mode 100644 index c2de50fa65..0000000000 --- a/osu.Game/Screens/Multi/Screens/Match/Header.cs +++ /dev/null @@ -1,184 +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 osu.Framework.Allocation; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.SearchableList; -using osuTK.Graphics; - -namespace osu.Game.Screens.Multi.Screens.Match -{ - public class Header : Container - { - public const float HEIGHT = 200; - - private readonly Box tabStrip; - private readonly UpdateableBeatmapSetCover cover; - - public readonly PageTabControl Tabs; - - public BeatmapSetInfo BeatmapSet - { - set => cover.BeatmapSet = value; - } - - public Action OnRequestSelectBeatmap; - - public Header() - { - RelativeSizeAxes = Axes.X; - Height = HEIGHT; - - BeatmapSelectButton beatmapButton; - Children = new Drawable[] - { - cover = new UpdateableBeatmapSetCover - { - RelativeSizeAxes = Axes.Both, - Masking = true, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Color4.Black.Opacity(0.5f)), - }, - tabStrip = new Box - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 1, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, - Children = new Drawable[] - { - new Container - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Width = 200, - Padding = new MarginPadding { Vertical = 5 }, - Child = beatmapButton = new BeatmapSelectButton - { - RelativeSizeAxes = Axes.Both, - }, - }, - Tabs = new PageTabControl - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }, - }, - }, - }; - - beatmapButton.Action = () => OnRequestSelectBeatmap?.Invoke(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - tabStrip.Colour = colours.Yellow; - } - - private class BeatmapSelectButton : OsuClickableContainer - { - private const float corner_radius = 5; - private const float bg_opacity = 0.5f; - private const float transition_duration = 100; - - private readonly Box bg; - private readonly Container border; - - public BeatmapSelectButton() - { - Masking = true; - CornerRadius = corner_radius; - - Children = new Drawable[] - { - bg = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = bg_opacity, - }, - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = @"Exo2.0-Bold", - Text = "Select Beatmap", - }, - border = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = corner_radius, - BorderThickness = 4, - Alpha = 0, - Child = new Box // needs a child to show the border - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - border.BorderColour = colours.Yellow; - } - - protected override bool OnHover(HoverEvent e) - { - border.FadeIn(transition_duration); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - base.OnHoverLost(e); - border.FadeOut(transition_duration); - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - bg.FadeTo(0.75f, 1000, Easing.Out); - return base.OnMouseDown(e); - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - bg.FadeTo(bg_opacity, transition_duration); - return base.OnMouseUp(e); - } - } - } - - public enum MatchHeaderPage - { - Settings, - Room, - } -} diff --git a/osu.Game/Screens/Multi/Screens/Match/Info.cs b/osu.Game/Screens/Multi/Screens/Match/Info.cs deleted file mode 100644 index a2c056c8bd..0000000000 --- a/osu.Game/Screens/Multi/Screens/Match/Info.cs +++ /dev/null @@ -1,210 +0,0 @@ -// 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.Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Multiplayer; -using osu.Game.Overlays.SearchableList; -using osu.Game.Screens.Multi.Components; -using osuTK; - -namespace osu.Game.Screens.Multi.Screens.Match -{ - public class Info : Container - { - public const float HEIGHT = 128; - - private readonly OsuSpriteText name, availabilityStatus; - private readonly BeatmapTypeInfo beatmapTypeInfo; - private readonly ReadyButton readyButton; - - private OsuColour colours; - - public Bindable Ready => readyButton.Ready; - - public string Name - { - set { name.Text = value; } - } - - private RoomAvailability availability; - public RoomAvailability Availability - { - set - { - if (value == availability) return; - availability = value; - - if (IsLoaded) - updateAvailabilityStatus(); - } - } - - private RoomStatus status; - public RoomStatus Status - { - set - { - if (value == status) return; - status = value; - - if (IsLoaded) - updateAvailabilityStatus(); - } - } - - public BeatmapInfo Beatmap - { - set { beatmapTypeInfo.Beatmap = value; } - } - - public GameType Type - { - set { beatmapTypeInfo.Type = value; } - } - - public Info() - { - RelativeSizeAxes = Axes.X; - Height = HEIGHT; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"28242d"), - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING }, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - Padding = new MarginPadding { Vertical = 20 }, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - name = new OsuSpriteText - { - TextSize = 30, - }, - availabilityStatus = new OsuSpriteText - { - TextSize = 14, - }, - }, - }, - beatmapTypeInfo = new BeatmapTypeInfo - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }, - }, - }, - readyButton = new ReadyButton - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(200, 1), - Padding = new MarginPadding { Vertical = 10 }, - }, - }, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - this.colours = colours; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - updateAvailabilityStatus(); - } - - private void updateAvailabilityStatus() - { - if (status != null) - { - availabilityStatus.FadeColour(status.GetAppropriateColour(colours), 100); - availabilityStatus.Text = $"{availability.GetDescription()}, {status.Message}"; - } - } - - private class ReadyButton : TriangleButton - { - public readonly Bindable Ready = new Bindable(); - - protected override SpriteText CreateText() => new OsuSpriteText - { - Depth = -1, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Font = @"Exo2.0-Light", - TextSize = 30, - }; - - [BackgroundDependencyLoader] - private void load() - { - BackgroundColour = OsuColour.FromHex(@"1187aa"); - Triangles.ColourLight = OsuColour.FromHex(@"277b9c"); - Triangles.ColourDark = OsuColour.FromHex(@"1f6682"); - Triangles.TriangleScale = 1.5f; - - Container active; - Add(active = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0f, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.15f, - Blending = BlendingMode.Additive, - }, - }); - - Action = () => Ready.Value = !Ready.Value; - - Ready.BindValueChanged(value => - { - if (value) - { - Text = "Not Ready"; - active.FadeIn(200); - } - else - { - Text = "Ready"; - active.FadeOut(200); - } - }, true); - } - } - } -} diff --git a/osu.Game/Screens/Multi/Screens/Match/Match.cs b/osu.Game/Screens/Multi/Screens/Match/Match.cs deleted file mode 100644 index ce3f7825a4..0000000000 --- a/osu.Game/Screens/Multi/Screens/Match/Match.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; -using osu.Game.Online.Multiplayer; -using osu.Game.Screens.Select; -using osu.Game.Users; - -namespace osu.Game.Screens.Multi.Screens.Match -{ - public class Match : MultiplayerScreen - { - private readonly Room room; - private readonly Participants participants; - - private readonly Bindable nameBind = new Bindable(); - private readonly Bindable statusBind = new Bindable(); - private readonly Bindable availabilityBind = new Bindable(); - private readonly Bindable typeBind = new Bindable(); - private readonly Bindable beatmapBind = new Bindable(); - private readonly Bindable maxParticipantsBind = new Bindable(); - private readonly Bindable> participantsBind = new Bindable>(); - - protected override Container TransitionContent => participants; - - public override string Type => "room"; - public override string Title => room.Name.Value; - - public Match(Room room) - { - this.room = room; - Header header; - Info info; - - Children = new Drawable[] - { - header = new Header(), - info = new Info - { - Margin = new MarginPadding { Top = Header.HEIGHT }, - }, - participants = new Participants - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = Header.HEIGHT + Info.HEIGHT }, - }, - }; - - header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect()); - - beatmapBind.BindTo(room.Beatmap); - beatmapBind.BindValueChanged(b => - { - header.BeatmapSet = b?.BeatmapSet; - info.Beatmap = b; - }, true); - - nameBind.BindTo(room.Name); - nameBind.BindValueChanged(n => info.Name = n, true); - - statusBind.BindTo(room.Status); - statusBind.BindValueChanged(s => info.Status = s, true); - - availabilityBind.BindTo(room.Availability); - availabilityBind.BindValueChanged(a => info.Availability = a, true); - - typeBind.BindTo(room.Type); - typeBind.BindValueChanged(t => info.Type = t, true); - - maxParticipantsBind.BindTo(room.MaxParticipants); - maxParticipantsBind.BindValueChanged(m => { participants.Max = m; }, true); - - participantsBind.BindTo(room.Participants); - participantsBind.BindValueChanged(p => participants.Users = p, true); - } - } -} diff --git a/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs b/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs deleted file mode 100644 index 00c2613d54..0000000000 --- a/osu.Game/Screens/Multi/Screens/MultiplayerScreen.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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 - { - 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); - - Content.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - TransitionContent.FadeInFromZero(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(200).MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - } - - protected override bool OnExiting(Screen next) - { - Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - - return base.OnExiting(next); - } - - protected override void OnResuming(Screen last) - { - base.OnResuming(last); - - Content.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint); - } - - protected override void OnSuspending(Screen next) - { - base.OnSuspending(next); - - Content.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - TransitionContent.MoveToX(-200, WaveContainer.DISAPPEAR_DURATION, Easing.OutQuint); - } - } -} diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 00309fbcd5..328631ff9c 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -1,29 +1,31 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; +using System.Collections.Generic; using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Input.Bindings; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; -using osu.Game.Input.Bindings; using osu.Game.Rulesets; using osu.Game.Screens.Menu; -using osuTK; using osu.Game.Overlays; -using osu.Framework.Graphics.Containers; +using osu.Game.Users; +using osu.Game.Online.API; +using osu.Game.Rulesets.Mods; namespace osu.Game.Screens { - public abstract class OsuScreen : Screen, IKeyBindingHandler, IHasDescription + public abstract class OsuScreen : Screen, IOsuScreen, IHasDescription { - public BackgroundScreen Background { get; private set; } + /// + /// The amount of negative padding that should be applied to game background content which touches both the left and right sides of the screen. + /// This allows for the game content to be pushed byt he options/notification overlays without causing black areas to appear. + /// + public const float HORIZONTAL_OVERFLOW_PADDING = 50; /// /// A user-facing title for this screen. @@ -32,200 +34,187 @@ namespace osu.Game.Screens public string Description => Title; - protected virtual bool AllowBackButton => true; + public virtual bool AllowBackButton => true; - /// - /// Override to create a BackgroundMode for the current screen. - /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. - /// - protected virtual BackgroundScreen CreateBackground() => null; - - private Action updateOverlayStates; + public virtual bool AllowExternalScreenChange => false; /// /// Whether all overlays should be hidden when this screen is entered or resumed. /// - protected virtual bool HideOverlaysOnEnter => false; - - protected readonly Bindable OverlayActivationMode = new Bindable(); + public virtual bool HideOverlaysOnEnter => false; /// /// Whether overlays should be able to be opened once this screen is entered or resumed. /// - protected virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; + public virtual OverlayActivation InitialOverlayActivationMode => OverlayActivation.All; - /// - /// Whether this allows the cursor to be displayed. - /// public virtual bool CursorVisible => true; protected new OsuGameBase Game => base.Game as OsuGameBase; - private OsuLogo logo; + /// + /// The to set the user's activity automatically to when this screen is entered + /// This will be automatically set to for this screen on entering unless + /// is manually set before. + /// + protected virtual UserActivity InitialActivity => null; + + private UserActivity activity; /// - /// Whether the beatmap or ruleset should be allowed to be changed by the user or game. - /// Used to mark exclusive areas where this is strongly prohibited, like gameplay. + /// The current for this screen. /// - public virtual bool AllowBeatmapRulesetChange => true; + protected UserActivity Activity + { + get => activity; + set + { + if (value == activity) return; - protected readonly Bindable Beatmap = new Bindable(); + activity = value; + updateActivity(); + } + } - protected virtual float BackgroundParallaxAmount => 1; - - private ParallaxContainer backgroundParallaxContainer; - - protected readonly Bindable Ruleset = new Bindable(); + /// + /// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children). + /// + public virtual bool DisallowExternalBeatmapRulesetChanges => false; private SampleChannel sampleExit; + protected virtual bool PlayResumeSound => true; + + public virtual float BackgroundParallaxAmount => 1; + + public Bindable Beatmap { get; private set; } + + public Bindable Ruleset { get; private set; } + + public Bindable> Mods { get; private set; } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var screenDependencies = new OsuScreenDependencies(DisallowExternalBeatmapRulesetChanges, parent); + + Beatmap = screenDependencies.Beatmap; + Ruleset = screenDependencies.Ruleset; + Mods = screenDependencies.Mods; + + return base.CreateChildDependencies(screenDependencies); + } + + protected BackgroundScreen Background => backgroundStack?.CurrentScreen as BackgroundScreen; + + private BackgroundScreen localBackground; + + [Resolved(canBeNull: true)] + private BackgroundScreenStack backgroundStack { get; set; } + + [Resolved(canBeNull: true)] + private OsuLogo logo { get; set; } + + [Resolved(canBeNull: true)] + private IAPIProvider api { get; set; } + + protected OsuScreen() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + [BackgroundDependencyLoader(true)] - private void load(BindableBeatmap beatmap, OsuGame osu, AudioManager audio, Bindable ruleset) + private void load(OsuGame osu, AudioManager audio) { - Beatmap.BindTo(beatmap); - Ruleset.BindTo(ruleset); - - if (osu != null) - { - OverlayActivationMode.BindTo(osu.OverlayActivationMode); - - updateOverlayStates = () => - { - if (HideOverlaysOnEnter) - osu.CloseAllOverlays(); - else - osu.Toolbar.State = Visibility.Visible; - }; - } - - sampleExit = audio.Sample.Get(@"UI/screen-back"); + sampleExit = audio.Samples.Get(@"UI/screen-back"); } - public virtual bool OnPressed(GlobalAction action) + public override void OnResuming(IScreen last) { - if (!IsCurrentScreen) return false; - - if (action == GlobalAction.Back && AllowBackButton) - { - Exit(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back && AllowBackButton; - - protected override void OnResuming(Screen last) - { - sampleExit?.Play(); + if (PlayResumeSound) + sampleExit?.Play(); applyArrivingDefaults(true); + updateActivity(); + base.OnResuming(last); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); + onSuspendingLogo(); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { - OsuScreen lastOsu = last as OsuScreen; - - BackgroundScreen bg = CreateBackground(); - - if (lastOsu?.Background != null) - { - backgroundParallaxContainer = lastOsu.backgroundParallaxContainer; - - if (bg == null || lastOsu.Background.Equals(bg)) - //we can keep the previous mode's background. - Background = lastOsu.Background; - else - { - lastOsu.Background.Push(Background = bg); - } - } - else if (bg != null) - { - // this makes up for the fact our padding changes when the global toolbar is visible. - bg.Scale = new Vector2(1.06f); - - AddInternal(backgroundParallaxContainer = new ParallaxContainer - { - Depth = float.MaxValue, - Children = new[] - { - Background = bg - } - }); - } - - if ((logo = lastOsu?.logo) == null) - LoadComponentAsync(logo = new OsuLogo { Alpha = 0 }, AddInternal); - applyArrivingDefaults(false); + backgroundStack?.Push(localBackground = CreateBackground()); + + if (activity == null) + Activity = InitialActivity; + base.OnEntering(last); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { if (ValidForResume && logo != null) onExitingLogo(); - OsuScreen nextOsu = next as OsuScreen; - - if (Background != null && !Background.Equals(nextOsu?.Background)) - { - Background.Exit(); - - //We need to use MakeCurrent in case we are jumping up multiple game screens. - nextOsu?.Background?.MakeCurrent(); - } - if (base.OnExiting(next)) return true; - Beatmap.UnbindAll(); + if (localBackground != null && backgroundStack?.CurrentScreen == localBackground) + backgroundStack?.Exit(); + return false; } + private void updateActivity() + { + if (api != null) + api.Activity.Value = activity; + } + /// /// Fired when this screen was entered or resumed and the logo state is required to be adjusted. /// protected virtual void LogoArriving(OsuLogo logo, bool resuming) + { + ApplyLogoArrivingDefaults(logo); + } + + private void applyArrivingDefaults(bool isResuming) + { + logo?.AppendAnimatingAction(() => + { + if (this.IsCurrentScreen()) LogoArriving(logo, isResuming); + }, true); + } + + /// + /// Applies default animations to an arriving logo. + /// Todo: This should not exist. + /// + /// The logo to apply animations to. + public static void ApplyLogoArrivingDefaults(OsuLogo logo) { logo.Action = null; logo.FadeOut(300, Easing.OutQuint); logo.Anchor = Anchor.TopLeft; logo.Origin = Anchor.Centre; - logo.RelativePositionAxes = Axes.None; + logo.RelativePositionAxes = Axes.Both; logo.BeatMatching = true; logo.Triangles = true; logo.Ripple = true; } - private void applyArrivingDefaults(bool isResuming) - { - logo.AppendAnimatingAction(() => - { - if (IsCurrentScreen) LogoArriving(logo, isResuming); - }, true); - - if (backgroundParallaxContainer != null) - backgroundParallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * BackgroundParallaxAmount; - - OverlayActivationMode.Value = InitialOverlayActivationMode; - - updateOverlayStates?.Invoke(); - } - private void onExitingLogo() { - logo.AppendAnimatingAction(() => { LogoExiting(logo); }, false); + logo?.AppendAnimatingAction(() => LogoExiting(logo), false); } /// @@ -237,7 +226,7 @@ namespace osu.Game.Screens private void onSuspendingLogo() { - logo.AppendAnimatingAction(() => { LogoSuspending(logo); }, false); + logo?.AppendAnimatingAction(() => LogoSuspending(logo), false); } /// @@ -246,5 +235,11 @@ namespace osu.Game.Screens protected virtual void LogoSuspending(OsuLogo logo) { } + + /// + /// Override to create a BackgroundMode for the current screen. + /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. + /// + protected virtual BackgroundScreen CreateBackground() => null; } } diff --git a/osu.Game/Screens/OsuScreenDependencies.cs b/osu.Game/Screens/OsuScreenDependencies.cs new file mode 100644 index 0000000000..115f4b7e1a --- /dev/null +++ b/osu.Game/Screens/OsuScreenDependencies.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens +{ + public class OsuScreenDependencies : DependencyContainer + { + public Bindable Beatmap { get; } + + public Bindable Ruleset { get; } + + public Bindable> Mods { get; } + + public OsuScreenDependencies(bool requireLease, IReadOnlyDependencyContainer parent) + : base(parent) + { + if (requireLease) + { + Beatmap = parent.Get>()?.GetBoundCopy(); + + if (Beatmap == null) + Cache(Beatmap = parent.Get>().BeginLease(false)); + + Ruleset = parent.Get>()?.GetBoundCopy(); + + if (Ruleset == null) + Cache(Ruleset = parent.Get>().BeginLease(true)); + + Mods = parent.Get>>()?.GetBoundCopy(); + if (Mods == null) + Cache(Mods = parent.Get>>().BeginLease(true)); + } + else + { + Beatmap = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); + Ruleset = (parent.Get>() ?? parent.Get>()).GetBoundCopy(); + Mods = (parent.Get>>() ?? parent.Get>>()).GetBoundCopy(); + } + } + } +} diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs new file mode 100644 index 0000000000..0844e32d46 --- /dev/null +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Screens +{ + public class OsuScreenStack : ScreenStack + { + [Cached] + private BackgroundScreenStack backgroundScreenStack; + + private ParallaxContainer parallaxContainer; + + protected float ParallaxAmount => parallaxContainer.ParallaxAmount; + + public OsuScreenStack() + { + initializeStack(); + } + + public OsuScreenStack(IScreen baseScreen) + : base(baseScreen) + { + initializeStack(); + } + + private void initializeStack() + { + InternalChild = parallaxContainer = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + Child = backgroundScreenStack = new BackgroundScreenStack { RelativeSizeAxes = Axes.Both }, + }; + + ScreenPushed += onScreenChange; + ScreenExited += onScreenChange; + } + + private void onScreenChange(IScreen prev, IScreen next) + { + parallaxContainer.ParallaxAmount = ParallaxContainer.DEFAULT_PARALLAX_AMOUNT * ((IOsuScreen)next)?.BackgroundParallaxAmount ?? 1.0f; + } + } +} diff --git a/osu.Game/Screens/Play/Break/BlurredIcon.cs b/osu.Game/Screens/Play/Break/BlurredIcon.cs index 6d14c94f13..a88112a0db 100644 --- a/osu.Game/Screens/Play/Break/BlurredIcon.cs +++ b/osu.Game/Screens/Play/Break/BlurredIcon.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -13,10 +14,10 @@ namespace osu.Game.Screens.Play.Break { private readonly SpriteIcon icon; - public FontAwesome Icon + public IconUsage Icon { - set { icon.Icon = value; } - get { return icon.Icon; } + set => icon.Icon = value; + get => icon.Icon; } public override Vector2 Size @@ -27,7 +28,7 @@ namespace osu.Game.Screens.Play.Break base.Size = value + BlurSigma * 2.5f; ForceRedraw(); } - get { return base.Size; } + get => base.Size; } public BlurredIcon() diff --git a/osu.Game/Screens/Play/Break/BreakArrows.cs b/osu.Game/Screens/Play/Break/BreakArrows.cs index d5b710a29e..4b96fa666a 100644 --- a/osu.Game/Screens/Play/Break/BreakArrows.cs +++ b/osu.Game/Screens/Play/Break/BreakArrows.cs @@ -1,8 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osuTK; @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreRight, X = -glow_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -50,7 +51,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, X = glow_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_left, + Icon = FontAwesome.Solid.ChevronLeft, BlurSigma = new Vector2(glow_icon_blur_sigma), Size = new Vector2(glow_icon_size), }, @@ -67,7 +68,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreRight, Alpha = 0.7f, X = -blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_right, + Icon = FontAwesome.Solid.ChevronRight, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, @@ -77,7 +78,7 @@ namespace osu.Game.Screens.Play.Break Origin = Anchor.CentreLeft, Alpha = 0.7f, X = blurred_icon_offscreen_offset, - Icon = Graphics.FontAwesome.fa_chevron_left, + Icon = FontAwesome.Solid.ChevronLeft, BlurSigma = new Vector2(blurred_icon_blur_sigma), Size = new Vector2(blurred_icon_size), }, diff --git a/osu.Game/Screens/Play/Break/BreakInfo.cs b/osu.Game/Screens/Play/Break/BreakInfo.cs index e5dbb6aa83..a3d64d05a3 100644 --- a/osu.Game/Screens/Play/Break/BreakInfo.cs +++ b/osu.Game/Screens/Play/Break/BreakInfo.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK; namespace osu.Game.Screens.Play.Break @@ -29,8 +30,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "current progress".ToUpperInvariant(), - TextSize = 15, - Font = "Exo2.0-Black", + Font = OsuFont.GetFont(weight: FontWeight.Black, size: 15), }, new FillFlowContainer { diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 49b4cb3206..70e7b8f297 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -1,8 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -34,7 +36,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreRight, Text = name, - TextSize = 17, + Font = OsuFont.GetFont(size: 17), Margin = new MarginPadding { Right = margin } }, valueText = new OsuSpriteText @@ -42,8 +44,7 @@ namespace osu.Game.Screens.Play.Break Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, Text = prefix + @"-", - TextSize = 17, - Font = "Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Margin = new MarginPadding { Left = margin } } }; @@ -51,9 +52,9 @@ namespace osu.Game.Screens.Play.Break Current.ValueChanged += currentValueChanged; } - private void currentValueChanged(T newValue) + private void currentValueChanged(ValueChangedEvent e) { - var newText = prefix + Format(newValue); + var newText = prefix + Format(e.NewValue); if (valueText.Text == newText) return; @@ -61,7 +62,13 @@ namespace osu.Game.Screens.Play.Break valueText.Text = newText; } - protected virtual string Format(T count) => count.ToString(); + protected virtual string Format(T count) + { + if (count is Enum countEnum) + return countEnum.GetDescription(); + + return count.ToString(); + } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -73,7 +80,8 @@ namespace osu.Game.Screens.Play.Break public class PercentageBreakInfoLine : BreakInfoLine { - public PercentageBreakInfoLine(string name, string prefix = "") : base(name, prefix) + public PercentageBreakInfoLine(string name, string prefix = "") + : base(name, prefix) { } diff --git a/osu.Game/Screens/Play/Break/GlowIcon.cs b/osu.Game/Screens/Play/Break/GlowIcon.cs index d412f3e103..2810389619 100644 --- a/osu.Game/Screens/Play/Break/GlowIcon.cs +++ b/osu.Game/Screens/Play/Break/GlowIcon.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osuTK; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.Play.Break public override Vector2 Size { - get { return base.Size; } + get => base.Size; set { blurredIcon.Size = spriteIcon.Size = value; @@ -26,14 +27,14 @@ namespace osu.Game.Screens.Play.Break public Vector2 BlurSigma { - get { return blurredIcon.BlurSigma; } - set { blurredIcon.BlurSigma = value; } + get => blurredIcon.BlurSigma; + set => blurredIcon.BlurSigma = value; } - public FontAwesome Icon + public IconUsage Icon { - get { return spriteIcon.Icon; } - set { spriteIcon.Icon = blurredIcon.Icon = value; } + get => spriteIcon.Icon; + set => spriteIcon.Icon = blurredIcon.Icon = value; } public GlowIcon() diff --git a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs index 8bfa0e632a..2d7163b5fe 100644 --- a/osu.Game/Screens/Play/Break/LetterboxOverlay.cs +++ b/osu.Game/Screens/Play/Break/LetterboxOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; diff --git a/osu.Game/Screens/Play/Break/RemainingTimeCounter.cs b/osu.Game/Screens/Play/Break/RemainingTimeCounter.cs index 914809ea40..2f5e43aebf 100644 --- a/osu.Game/Screens/Play/Break/RemainingTimeCounter.cs +++ b/osu.Game/Screens/Play/Break/RemainingTimeCounter.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Play.Break @@ -19,8 +20,7 @@ namespace osu.Game.Screens.Play.Break { Anchor = Anchor.Centre, Origin = Anchor.Centre, - TextSize = 33, - Font = "Venera", + Font = OsuFont.Numeric.With(size: 33), }; } diff --git a/osu.Game/Screens/Play/BreakOverlay.cs b/osu.Game/Screens/Play/BreakOverlay.cs index b2df996d35..d390787090 100644 --- a/osu.Game/Screens/Play/BreakOverlay.cs +++ b/osu.Game/Screens/Play/BreakOverlay.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -40,13 +41,7 @@ namespace osu.Game.Screens.Play private readonly BreakInfo info; private readonly BreakArrows breakArrows; - public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor) - : this(letterboxing) - { - bindProcessor(scoreProcessor); - } - - public BreakOverlay(bool letterboxing) + public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor = null) { RelativeSizeAxes = Axes.Both; Child = fadeContainer = new Container @@ -98,6 +93,14 @@ namespace osu.Game.Screens.Play } } }; + + if (scoreProcessor != null) bindProcessor(scoreProcessor); + } + + [BackgroundDependencyLoader(true)] + private void load(GameplayClock clock) + { + if (clock != null) Clock = clock; } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/ComboEffects.cs b/osu.Game/Screens/Play/ComboEffects.cs new file mode 100644 index 0000000000..1c4ac921f0 --- /dev/null +++ b/osu.Game/Screens/Play/ComboEffects.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Game.Audio; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play +{ + public class ComboEffects : CompositeDrawable + { + private readonly ScoreProcessor processor; + + private SkinnableSound comboBreakSample; + + public ComboEffects(ScoreProcessor processor) + { + this.processor = processor; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = comboBreakSample = new SkinnableSound(new SampleInfo("combobreak")); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + processor.Combo.BindValueChanged(onComboChange, true); + } + + private void onComboChange(ValueChangedEvent combo) + { + if (combo.NewValue == 0 && combo.OldValue > 20) + comboBreakSample?.Play(); + } + } +} diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs new file mode 100644 index 0000000000..a3caffb620 --- /dev/null +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -0,0 +1,113 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Game.Rulesets.UI; +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.MathUtils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects.Drawables; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// Manage the animation to be applied when a player fails. + /// Single file; automatically disposed after use. + /// + public class FailAnimation : Component + { + public Action OnComplete; + + private readonly DrawableRuleset drawableRuleset; + + private readonly BindableDouble trackFreq = new BindableDouble(1); + + private Track track; + + private const float duration = 2500; + + private SampleChannel failSample; + + public FailAnimation(DrawableRuleset drawableRuleset) + { + this.drawableRuleset = drawableRuleset; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, IBindable beatmap) + { + track = beatmap.Value.Track; + failSample = audio.Samples.Get(@"Gameplay/failsound"); + } + + private bool started; + + /// + /// Start the fail animation playing. + /// + /// Thrown if started more than once. + public void Start() + { + if (started) throw new InvalidOperationException("Animation cannot be started more than once."); + + started = true; + + failSample.Play(); + + this.TransformBindableTo(trackFreq, 0, duration).OnComplete(_ => + { + OnComplete?.Invoke(); + Expire(); + }); + + track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); + + applyToPlayfield(drawableRuleset.Playfield); + drawableRuleset.Playfield.HitObjectContainer.FlashColour(Color4.Red, 500); + drawableRuleset.Playfield.HitObjectContainer.FadeOut(duration / 2); + } + + protected override void Update() + { + base.Update(); + + if (!started) + return; + + applyToPlayfield(drawableRuleset.Playfield); + } + + private readonly List appliedObjects = new List(); + + private void applyToPlayfield(Playfield playfield) + { + foreach (var nested in playfield.NestedPlayfields) + applyToPlayfield(nested); + + foreach (DrawableHitObject obj in playfield.HitObjectContainer.AliveObjects) + { + if (appliedObjects.Contains(obj)) + continue; + + obj.RotateTo(RNG.NextSingle(-90, 90), duration); + obj.ScaleTo(obj.Scale * 0.5f, duration); + obj.MoveToOffset(new Vector2(0, 400), duration); + appliedObjects.Add(obj); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + track?.RemoveAdjustment(AdjustableProperty.Frequency, trackFreq); + } + } +} diff --git a/osu.Game/Screens/Play/FailOverlay.cs b/osu.Game/Screens/Play/FailOverlay.cs index 947fa1734a..b67f45c4f2 100644 --- a/osu.Game/Screens/Play/FailOverlay.cs +++ b/osu.Game/Screens/Play/FailOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Graphics; using osuTK.Graphics; diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs new file mode 100644 index 0000000000..b1948d02d5 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Timing; + +namespace osu.Game.Screens.Play +{ + /// + /// A clock which is used for gameplay elements that need to follow audio time 1:1. + /// Exposed via DI by . + /// + /// The main purpose of this clock is to stop components using it from accidentally processing the main + /// , as this should only be done once to ensure accuracy. + /// + /// + public class GameplayClock : IFrameBasedClock + { + private readonly IFrameBasedClock underlyingClock; + + public readonly BindableBool IsPaused = new BindableBool(); + + public GameplayClock(IFrameBasedClock underlyingClock) + { + this.underlyingClock = underlyingClock; + } + + public double CurrentTime => underlyingClock.CurrentTime; + + public double Rate => underlyingClock.Rate; + + public bool IsRunning => underlyingClock.IsRunning; + + public void ProcessFrame() + { + // we do not want to process the underlying clock. + // this is handled by PauseContainer. + } + + public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; + + public double FramesPerSecond => underlyingClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + } +} diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs new file mode 100644 index 0000000000..6a03271b86 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -0,0 +1,191 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Screens.Play +{ + /// + /// Encapsulates gameplay timing logic and provides a for children. + /// + public class GameplayClockContainer : Container + { + private readonly WorkingBeatmap beatmap; + private readonly IReadOnlyList mods; + + /// + /// The original source (usually a 's track). + /// + private readonly IAdjustableClock sourceClock; + + public readonly BindableBool IsPaused = new BindableBool(); + + /// + /// The decoupled clock used for gameplay. Should be used for seeks and clock control. + /// + private readonly DecoupleableInterpolatingFramedClock adjustableClock; + + private readonly double gameplayStartTime; + + public readonly Bindable UserPlaybackRate = new BindableDouble(1) + { + Default = 1, + MinValue = 0.5, + MaxValue = 2, + Precision = 0.1, + }; + + /// + /// The final clock which is exposed to underlying components. + /// + [Cached] + public readonly GameplayClock GameplayClock; + + private Bindable userAudioOffset; + + private readonly FramedOffsetClock userOffsetClock; + + private readonly FramedOffsetClock platformOffsetClock; + + public GameplayClockContainer(WorkingBeatmap beatmap, IReadOnlyList mods, double gameplayStartTime) + { + this.beatmap = beatmap; + this.mods = mods; + this.gameplayStartTime = gameplayStartTime; + + RelativeSizeAxes = Axes.Both; + + sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock(); + (sourceClock as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + + adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; + + // 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. + platformOffsetClock = new FramedOffsetClock(adjustableClock) { Offset = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? 22 : 0 }; + + // the final usable gameplay clock with user-set offsets applied. + userOffsetClock = new FramedOffsetClock(platformOffsetClock); + + // the clock to be exposed via DI to children. + GameplayClock = new GameplayClock(userOffsetClock); + + GameplayClock.IsPaused.BindTo(IsPaused); + } + + private double totalOffset => userOffsetClock.Offset + platformOffsetClock.Offset; + + private readonly BindableDouble pauseFreqAdjust = new BindableDouble(1); + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); + userAudioOffset.BindValueChanged(offset => userOffsetClock.Offset = offset.NewValue, true); + + UserPlaybackRate.ValueChanged += _ => updateRate(); + + Seek(Math.Min(-beatmap.BeatmapInfo.AudioLeadIn, gameplayStartTime)); + } + + public void Restart() + { + Task.Run(() => + { + sourceClock.Reset(); + + Schedule(() => + { + adjustableClock.ChangeSource(sourceClock); + updateRate(); + + if (!IsPaused.Value) + Start(); + }); + }); + } + + public void 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 + Seek(GameplayClock.CurrentTime); + adjustableClock.Start(); + IsPaused.Value = false; + + this.TransformBindableTo(pauseFreqAdjust, 1, 200, Easing.In); + } + + /// + /// Seek to a specific time in gameplay. + /// + /// Adjusts for any offsets which have been applied (so the seek may not be the expected point in time on the underlying audio track). + /// + /// + /// The destination time to seek to. + public void Seek(double time) + { + // remove the offset component here because most of the time we want the seek to be aligned to gameplay, not the audio track. + // we may want to consider reversing the application of offsets in the future as it may feel more correct. + adjustableClock.Seek(time - totalOffset); + + // manually process frame to ensure GameplayClock is correctly updated after a seek. + userOffsetClock.ProcessFrame(); + } + + public void Stop() + { + this.TransformBindableTo(pauseFreqAdjust, 0, 200, Easing.Out).OnComplete(_ => adjustableClock.Stop()); + + IsPaused.Value = true; + } + + public void ResetLocalAdjustments() + { + // In the case of replays, we may have changed the playback rate. + UserPlaybackRate.Value = 1; + } + + protected override void Update() + { + if (!IsPaused.Value) + userOffsetClock.ProcessFrame(); + + base.Update(); + } + + private void updateRate() + { + if (sourceClock == null) return; + + sourceClock.ResetSpeedAdjustments(); + + if (sourceClock is IHasTempoAdjust tempo) + tempo.TempoAdjust = UserPlaybackRate.Value; + else + sourceClock.Rate = UserPlaybackRate.Value; + + foreach (var mod in mods.OfType()) + mod.ApplyToClock(sourceClock); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); + } + } +} diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index d934759474..c7e762714c 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Extensions.Color4Extensions; @@ -18,6 +18,8 @@ using System.Linq; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Input.Bindings; +using Humanizer; +using osu.Framework.Graphics.Effects; namespace osu.Game.Screens.Play { @@ -37,9 +39,15 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.Children.Last().Click(); + protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); + + /// + /// Action that is invoked when is triggered. + /// + protected virtual Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected.Value)?.Click(); public abstract string Header { get; } + public abstract string Description { get; } protected internal FillFlowContainer InternalButtons; @@ -51,7 +59,7 @@ namespace osu.Game.Screens.Play { RelativeSizeAxes = Axes.Both; - StateChanged += s => selectionIndex = -1; + State.ValueChanged += s => selectionIndex = -1; } [BackgroundDependencyLoader] @@ -88,11 +96,10 @@ namespace osu.Game.Screens.Play new OsuSpriteText { Text = Header, - Font = @"Exo2.0-Medium", + Font = OsuFont.GetFont(size: 30), Spacing = new Vector2(5, 0), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - TextSize = 30, Colour = colours.Yellow, Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.25f) @@ -176,7 +183,7 @@ namespace osu.Game.Screens.Play } }; - button.Selected.ValueChanged += s => buttonSelectionChanged(button, s); + button.Selected.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); InternalButtons.Add(button); } @@ -185,7 +192,7 @@ namespace osu.Game.Screens.Play private int selectionIndex { - get { return _selectionIndex; } + get => _selectionIndex; set { if (_selectionIndex == value) @@ -215,6 +222,7 @@ namespace osu.Game.Screens.Play else selectionIndex--; return true; + case Key.Down: if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) selectionIndex = 0; @@ -229,16 +237,31 @@ namespace osu.Game.Screens.Play public bool OnPressed(GlobalAction action) { - if (action == GlobalAction.Back) + switch (action) { - BackAction.Invoke(); - return true; + case GlobalAction.Back: + BackAction.Invoke(); + return true; + + case GlobalAction.Select: + SelectAction.Invoke(); + return true; } return false; } - public bool OnReleased(GlobalAction action) => action == GlobalAction.Back; + public bool OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + case GlobalAction.Select: + return true; + } + + return false; + } private void buttonSelectionChanged(DialogButton button, bool isSelected) { @@ -260,22 +283,21 @@ namespace osu.Game.Screens.Play Text = "You've retried ", Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 + Font = OsuFont.GetFont(size: 18), }, new OsuSpriteText { - Text = $"{retries:n0}", - Font = @"Exo2.0-Bold", + Text = "time".ToQuantity(retries), + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 }, new OsuSpriteText { - Text = $" time{(retries == 1 ? "" : "s")} in this session", + Text = " in this session", Shadow = true, ShadowColour = new Color4(0, 0, 0, 0.25f), - TextSize = 18 + Font = OsuFont.GetFont(size: 18), } }; } @@ -289,15 +311,6 @@ namespace osu.Game.Screens.Play Selected.Value = true; return base.OnMouseMove(e); } - - protected override bool OnKeyDown(KeyDownEvent e) - { - if (e.Repeat || e.Key != Key.Enter || !Selected) - return false; - - Click(); - return true; - } } } } diff --git a/osu.Game/Screens/Play/HUD/ComboCounter.cs b/osu.Game/Screens/Play/HUD/ComboCounter.cs index a6a33702ba..5ac3dac5f7 100644 --- a/osu.Game/Screens/Play/HUD/ComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboCounter.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -63,14 +63,14 @@ namespace osu.Game.Screens.Play.HUD TextSize = 80; - Current.ValueChanged += newValue => updateCount(newValue == 0); + Current.ValueChanged += combo => updateCount(combo.NewValue == 0); } protected override void LoadComplete() { base.LoadComplete(); - DisplayedCountSpriteText.Text = FormatCount(Current); + DisplayedCountSpriteText.Text = FormatCount(Current.Value); DisplayedCountSpriteText.Anchor = Anchor; DisplayedCountSpriteText.Origin = Origin; @@ -78,29 +78,33 @@ namespace osu.Game.Screens.Play.HUD } private int displayedCount; + /// /// Value shown at the current moment. /// public virtual int DisplayedCount { - get { return displayedCount; } + get => displayedCount; protected set { if (displayedCount.Equals(value)) return; + updateDisplayedCount(displayedCount, value, IsRolling); } } private float textSize; + public float TextSize { - get { return textSize; } + get => textSize; set { textSize = value; - DisplayedCountSpriteText.TextSize = TextSize; - PopOutCount.TextSize = TextSize; + + DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(size: TextSize); + PopOutCount.Font = PopOutCount.Font.With(size: TextSize); } } @@ -110,7 +114,7 @@ namespace osu.Game.Screens.Play.HUD /// public void Increment(int amount = 1) { - Current.Value = Current + amount; + Current.Value = Current.Value + amount; } /// @@ -161,7 +165,7 @@ namespace osu.Game.Screens.Play.HUD private void updateCount(bool rolling) { int prev = previousValue; - previousValue = Current; + previousValue = Current.Value; if (!IsLoaded) return; @@ -172,14 +176,14 @@ namespace osu.Game.Screens.Play.HUD IsRolling = false; DisplayedCount = prev; - if (prev + 1 == Current) - OnCountIncrement(prev, Current); + if (prev + 1 == Current.Value) + OnCountIncrement(prev, Current.Value); else - OnCountChange(prev, Current); + OnCountChange(prev, Current.Value); } else { - OnCountRolling(displayedCount, Current); + OnCountRolling(displayedCount, Current.Value); IsRolling = true; } } diff --git a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs index cc24af0394..3f6b1e29e6 100644 --- a/osu.Game/Screens/Play/HUD/ComboResultCounter.cs +++ b/osu.Game/Screens/Play/HUD/ComboResultCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Play.HUD public override void Increment(long amount) { - Current.Value = Current + amount; + Current.Value = Current.Value + amount; } } } diff --git a/osu.Game/Screens/Play/HUD/HealthDisplay.cs b/osu.Game/Screens/Play/HUD/HealthDisplay.cs index fc651fc4b4..acd8656fb2 100644 --- a/osu.Game/Screens/Play/HUD/HealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/HealthDisplay.cs @@ -1,7 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Play.HUD @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play.HUD protected HealthDisplay() { - Current.ValueChanged += newValue => SetHealth((float)newValue); + Current.ValueChanged += health => SetHealth((float)health.NewValue); } protected abstract void SetHealth(float value); diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 9625c4338b..91c14591b1 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -1,12 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -23,6 +25,8 @@ namespace osu.Game.Screens.Play.HUD { public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + public readonly Bindable IsPaused = new Bindable(); + private readonly Button button; public Action Action @@ -42,14 +46,15 @@ namespace osu.Game.Screens.Play.HUD text = new OsuSpriteText { Text = "hold for menu", - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft }, button = new Button { HoverGained = () => text.FadeIn(500, Easing.OutQuint), - HoverLost = () => text.FadeOut(500, Easing.OutQuint) + HoverLost = () => text.FadeOut(500, Easing.OutQuint), + IsPaused = { BindTarget = IsPaused } } }; AutoSizeAxes = Axes.Both; @@ -69,6 +74,11 @@ namespace osu.Game.Screens.Play.HUD return base.OnMouseMove(e); } + public bool PauseOnFocusLost + { + set => button.PauseOnFocusLost = value; + } + protected override void Update() { base.Update(); @@ -87,37 +97,17 @@ namespace osu.Game.Screens.Play.HUD private CircularProgress circularProgress; private Circle overlayCircle; + public readonly Bindable IsPaused = new Bindable(); + protected override bool AllowMultipleFires => true; public Action HoverGained; public Action HoverLost; - public bool OnPressed(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - BeginConfirm(); - return true; - } - - return false; - } - - public bool OnReleased(GlobalAction action) - { - switch (action) - { - case GlobalAction.Back: - AbortConfirm(); - return true; - } - - return false; - } + private readonly IBindable gameActive = new Bindable(true); [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, Framework.Game game) { Size = new Vector2(60); @@ -152,18 +142,26 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(15), - Icon = FontAwesome.fa_close + Icon = FontAwesome.Solid.Times }, } }; bind(); + + gameActive.BindTo(game.IsActive); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + gameActive.BindValueChanged(_ => updateActive(), true); } private void bind() { circularProgress.Current.BindTo(Progress); - Progress.ValueChanged += v => icon.Scale = new Vector2(1 + (float)v * 0.2f); + Progress.ValueChanged += progress => icon.Scale = new Vector2(1 + (float)progress.NewValue * 0.2f); } private bool pendingAnimation; @@ -178,7 +176,7 @@ namespace osu.Game.Screens.Play.HUD // avoid starting a new confirm call until we finish animating. pendingAnimation = true; - Progress.Value = 0; + AbortConfirm(); overlayCircle.ScaleTo(0, 100) .Then().FadeOut().ScaleTo(1).FadeIn(500) @@ -207,6 +205,56 @@ namespace osu.Game.Screens.Play.HUD base.OnHoverLost(e); } + private bool pauseOnFocusLost = true; + + public bool PauseOnFocusLost + { + set + { + if (pauseOnFocusLost == value) + return; + + pauseOnFocusLost = value; + if (IsLoaded) + updateActive(); + } + } + + private void updateActive() + { + if (!pauseOnFocusLost || IsPaused.Value) return; + + if (gameActive.Value) + AbortConfirm(); + else + BeginConfirm(); + } + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + if (!pendingAnimation) + BeginConfirm(); + return true; + } + + return false; + } + + public bool OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.Back: + AbortConfirm(); + return true; + } + + return false; + } + protected override bool OnMouseDown(MouseDownEvent e) { if (!pendingAnimation && e.CurrentState.Mouse.Buttons.Count() == 1) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 9509c7acae..0f3c92a962 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -1,8 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; -using osu.Framework.Configuration; +using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -11,24 +13,41 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osuTK; using osu.Game.Graphics.Containers; -using System.Linq; using osu.Framework.Input.Events; +using osu.Game.Graphics; namespace osu.Game.Screens.Play.HUD { - public class ModDisplay : Container, IHasCurrentValue> + public class ModDisplay : Container, IHasCurrentValue> { private const int fade_duration = 1000; - private readonly Bindable> mods = new Bindable>(); + public bool DisplayUnrankedText = true; - public Bindable> Current => mods; + public bool AllowExpand = true; + + private readonly Bindable> current = new Bindable>(); + + public Bindable> Current + { + get => current; + set + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + current.UnbindBindings(); + current.BindTo(value); + } + } private readonly FillFlowContainer iconsContainer; private readonly OsuSpriteText unrankedText; public ModDisplay() { + AutoSizeAxes = Axes.Both; + Children = new Drawable[] { iconsContainer = new ReverseChildIDFillFlowContainer @@ -41,19 +60,18 @@ namespace osu.Game.Screens.Play.HUD }, unrankedText = new OsuSpriteText { - AlwaysPresent = true, Anchor = Anchor.BottomCentre, Origin = Anchor.TopCentre, Text = @"/ UNRANKED /", - Font = @"Venera", - TextSize = 12, + Font = OsuFont.Numeric.With(size: 12) } }; - mods.ValueChanged += mods => + Current.ValueChanged += mods => { iconsContainer.Clear(); - foreach (Mod mod in mods) + + foreach (Mod mod in mods.NewValue) { iconsContainer.Add(new ModIcon(mod) { Scale = new Vector2(0.6f) }); } @@ -66,30 +84,35 @@ namespace osu.Game.Screens.Play.HUD protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - mods.UnbindAll(); + Current.UnbindAll(); } protected override void LoadComplete() { base.LoadComplete(); + appearTransform(); + iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); } private void appearTransform() { - if (mods.Value.Any(m => !m.Ranked)) + if (DisplayUnrankedText && Current.Value.Any(m => !m.Ranked)) unrankedText.FadeInFromZero(fade_duration, Easing.OutQuint); else unrankedText.Hide(); - iconsContainer.FinishTransforms(); - iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); expand(); + using (iconsContainer.BeginDelayedSequence(1200)) contract(); } - private void expand() => iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + private void expand() + { + if (AllowExpand) + iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + } private void contract() => iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index 2249e743e6..b2c3952f38 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,8 +17,11 @@ namespace osu.Game.Screens.Play.HUD public bool ReplayLoaded; public readonly PlaybackSettings PlaybackSettings; + public readonly VisualSettings VisualSettings; + //public readonly CollectionSettings CollectionSettings; + //public readonly DiscussionSettings DiscussionSettings; public PlayerSettingsOverlay() @@ -43,14 +46,14 @@ namespace osu.Game.Screens.Play.HUD } }; - State = Visibility.Visible; + Show(); } protected override void PopIn() => this.FadeIn(fade_duration); protected override void PopOut() => this.FadeOut(fade_duration); //We want to handle keyboard inputs all the time in order to trigger ToggleVisibility() when not visible - public override bool HandleNonPositionalInput => true; + public override bool PropagateNonPositionalInputSubTree => true; protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Screens/Play/HUD/StandardComboCounter.cs b/osu.Game/Screens/Play/HUD/StandardComboCounter.cs index 3ca4476969..7301300b8d 100644 --- a/osu.Game/Screens/Play/HUD/StandardComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/StandardComboCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs index 9eedde5269..315bc27a79 100644 --- a/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/StandardHealthDisplay.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osu.Game.Rulesets.Judgements; using osuTK; @@ -44,18 +45,20 @@ namespace osu.Game.Screens.Play.HUD public Color4 AccentColour { - get { return fill.Colour; } - set { fill.Colour = value; } + get => fill.Colour; + set => fill.Colour = value; } private Color4 glowColour; + public Color4 GlowColour { - get { return glowColour; } + get => glowColour; set { if (glowColour == value) return; + glowColour = value; fill.EdgeEffect = new EdgeEffectParameters diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 98be0871a1..017bf70133 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -1,17 +1,18 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Timing; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play.HUD; @@ -24,9 +25,7 @@ namespace osu.Game.Screens.Play { private const int duration = 100; - private readonly Container content; - - public readonly KeyCounterCollection KeyCounter; + public readonly KeyCounterDisplay KeyCounter; public readonly RollingCounter ComboCounter; public readonly ScoreCounter ScoreCounter; public readonly RollingCounter AccuracyCounter; @@ -36,65 +35,88 @@ namespace osu.Game.Screens.Play public readonly HoldForMenuButton HoldToQuit; public readonly PlayerSettingsOverlay PlayerSettingsOverlay; + private readonly ScoreProcessor scoreProcessor; + private readonly DrawableRuleset drawableRuleset; + private readonly IReadOnlyList mods; + private Bindable showHud; + private readonly Container visibilityContainer; private readonly BindableBool replayLoaded = new BindableBool(); private static bool hasShownNotificationOnce; - public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, WorkingBeatmap working, IClock offsetClock, IAdjustableClock adjustableClock) + public Action RequestSeek; + + public HUDOverlay(ScoreProcessor scoreProcessor, DrawableRuleset drawableRuleset, IReadOnlyList mods) { + this.scoreProcessor = scoreProcessor; + this.drawableRuleset = drawableRuleset; + this.mods = mods; + RelativeSizeAxes = Axes.Both; - Add(content = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - - Children = new Drawable[] + visibilityContainer = new Container { - ComboCounter = CreateComboCounter(), - ScoreCounter = CreateScoreCounter(), - AccuracyCounter = CreateAccuracyCounter(), - HealthDisplay = CreateHealthDisplay(), - Progress = CreateProgress(), - ModDisplay = CreateModsContainer(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), - new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Container { - KeyCounter = CreateKeyCounter(adjustableClock as IFrameBasedClock), - HoldToQuit = CreateHoldForMenuButton(), - } + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Y = 30, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 200, + AutoSizeEasing = Easing.Out, + Children = new Drawable[] + { + AccuracyCounter = CreateAccuracyCounter(), + ScoreCounter = CreateScoreCounter(), + ComboCounter = CreateComboCounter(), + }, + }, + HealthDisplay = CreateHealthDisplay(), + Progress = CreateProgress(), + ModDisplay = CreateModsContainer(), + } + }, + PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Position = -new Vector2(5, TwoLayerButton.SIZE_RETRACTED.Y), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + KeyCounter = CreateKeyCounter(), + HoldToQuit = CreateHoldForMenuButton(), } } - }); - - BindProcessor(scoreProcessor); - BindRulesetContainer(rulesetContainer); - - Progress.Objects = rulesetContainer.Objects; - Progress.AudioClock = offsetClock; - Progress.AllowSeeking = rulesetContainer.HasReplayLoaded; - Progress.OnSeek = pos => adjustableClock.Seek(pos); - - ModDisplay.Current.BindTo(working.Mods); - - PlayerSettingsOverlay.PlaybackSettings.AdjustableClock = adjustableClock; + }; } [BackgroundDependencyLoader(true)] private void load(OsuConfigManager config, NotificationOverlay notificationOverlay) { + BindProcessor(scoreProcessor); + BindDrawableRuleset(drawableRuleset); + + Progress.Objects = drawableRuleset.Objects; + Progress.AllowSeeking = drawableRuleset.HasReplayLoaded.Value; + Progress.RequestSeek = time => RequestSeek(time); + Progress.ReferenceClock = drawableRuleset.FrameStableClock; + + ModDisplay.Current.Value = mods; + showHud = config.GetBindable(OsuSetting.ShowInterface); - showHud.ValueChanged += hudVisibility => content.FadeTo(hudVisibility ? 1 : 0, duration); + showHud.ValueChanged += visible => visibilityContainer.FadeTo(visible.NewValue ? 1 : 0, duration); showHud.TriggerChange(); - if (!showHud && !hasShownNotificationOnce) + if (!showHud.Value && !hasShownNotificationOnce) { hasShownNotificationOnce = true; @@ -109,15 +131,14 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); - replayLoaded.ValueChanged += replayLoadedValueChanged; - replayLoaded.TriggerChange(); + replayLoaded.BindValueChanged(replayLoadedValueChanged, true); } - private void replayLoadedValueChanged(bool loaded) + private void replayLoadedValueChanged(ValueChangedEvent e) { - PlayerSettingsOverlay.ReplayLoaded = loaded; + PlayerSettingsOverlay.ReplayLoaded = e.NewValue; - if (loaded) + if (e.NewValue) { PlayerSettingsOverlay.Show(); ModDisplay.FadeIn(200); @@ -131,13 +152,13 @@ namespace osu.Game.Screens.Play } } - protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer) + protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - (rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter); + (drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter); - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); - Progress.BindRulestContainer(rulesetContainer); + Progress.BindDrawableRuleset(drawableRuleset); } protected override bool OnKeyDown(KeyDownEvent e) @@ -159,20 +180,27 @@ namespace osu.Game.Screens.Play protected virtual RollingCounter CreateAccuracyCounter() => new PercentageCounter { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopRight, - Position = new Vector2(0, 35), TextSize = 20, - Margin = new MarginPadding { Right = 140 }, + BypassAutoSizeAxes = Axes.X, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopRight, + Margin = new MarginPadding { Top = 5, Right = 20 }, + }; + + protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) + { + TextSize = 40, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, }; protected virtual RollingCounter CreateComboCounter() => new SimpleComboCounter { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopLeft, - Position = new Vector2(0, 35), - Margin = new MarginPadding { Left = 140 }, TextSize = 20, + BypassAutoSizeAxes = Axes.X, + Anchor = Anchor.TopRight, + Origin = Anchor.TopLeft, + Margin = new MarginPadding { Top = 5, Left = 20 }, }; protected virtual HealthDisplay CreateHealthDisplay() => new StandardHealthDisplay @@ -182,21 +210,12 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual KeyCounterCollection CreateKeyCounter(IFrameBasedClock offsetClock) => new KeyCounterCollection + protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { FadeTime = 50, Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, Margin = new MarginPadding(10), - AudioClock = offsetClock - }; - - protected virtual ScoreCounter CreateScoreCounter() => new ScoreCounter(6) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextSize = 40, - Position = new Vector2(0, 30), }; protected virtual SongProgress CreateProgress() => new SongProgress @@ -229,8 +248,7 @@ namespace osu.Game.Screens.Play ComboCounter?.Current.BindTo(processor.Combo); HealthDisplay?.Current.BindTo(processor.Health); - var shd = HealthDisplay as StandardHealthDisplay; - if (shd != null) + if (HealthDisplay is StandardHealthDisplay shd) processor.NewJudgement += shd.Flash; } } diff --git a/osu.Game/Screens/Play/HotkeyExitOverlay.cs b/osu.Game/Screens/Play/HotkeyExitOverlay.cs new file mode 100644 index 0000000000..c18aecda55 --- /dev/null +++ b/osu.Game/Screens/Play/HotkeyExitOverlay.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Play +{ + public class HotkeyExitOverlay : HoldToConfirmOverlay, IKeyBindingHandler + { + public bool OnPressed(GlobalAction action) + { + if (action != GlobalAction.QuickExit) return false; + + BeginConfirm(); + return true; + } + + public bool OnReleased(GlobalAction action) + { + if (action != GlobalAction.QuickExit) return false; + + AbortConfirm(); + return true; + } + } +} diff --git a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs index 926a96eb6c..f1b851f2d5 100644 --- a/osu.Game/Screens/Play/HotkeyRetryOverlay.cs +++ b/osu.Game/Screens/Play/HotkeyRetryOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; using osu.Game.Input.Bindings; diff --git a/osu.Game/Screens/Play/KeyCounter.cs b/osu.Game/Screens/Play/KeyCounter.cs index b494b84ee4..88a62ac8d4 100644 --- a/osu.Game/Screens/Play/KeyCounter.cs +++ b/osu.Game/Screens/Play/KeyCounter.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using System.Linq; @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; using osuTK.Graphics; @@ -26,9 +27,10 @@ namespace osu.Game.Screens.Play public bool IsCounting { get; set; } = true; private int countPresses; + public int CountPresses { - get { return countPresses; } + get => countPresses; private set { if (countPresses != value) @@ -40,15 +42,17 @@ namespace osu.Game.Screens.Play } private bool isLit; + public bool IsLit { - get { return isLit; } + get => isLit; protected set { if (isLit != value) { isLit = value; updateGlowSprite(value); + if (value && IsCounting) { CountPresses++; @@ -68,9 +72,12 @@ namespace osu.Game.Screens.Play Name = name; } - [BackgroundDependencyLoader] - private void load(TextureStore textures) + [BackgroundDependencyLoader(true)] + private void load(TextureStore textures, GameplayClock clock) { + if (clock != null) + Clock = clock; + Children = new Drawable[] { buttonSprite = new Sprite @@ -96,8 +103,7 @@ namespace osu.Game.Screens.Play new OsuSpriteText { Text = Name, - Font = @"Venera", - TextSize = 12, + Font = OsuFont.Numeric.With(size: 12), Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativePositionAxes = Axes.Both, diff --git a/osu.Game/Screens/Play/KeyCounterAction.cs b/osu.Game/Screens/Play/KeyCounterAction.cs index 6fde0734f6..8deac653ad 100644 --- a/osu.Game/Screens/Play/KeyCounterAction.cs +++ b/osu.Game/Screens/Play/KeyCounterAction.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Bindings; @@ -10,7 +10,8 @@ namespace osu.Game.Screens.Play { public T Action { get; } - public KeyCounterAction(T action) : base($"B{(int)(object)action + 1}") + public KeyCounterAction(T action) + : base($"B{(int)(object)action + 1}") { Action = action; } diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs similarity index 83% rename from osu.Game/Screens/Play/KeyCounterCollection.cs rename to osu.Game/Screens/Play/KeyCounterDisplay.cs index ce0e98d032..d5967f5899 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -1,28 +1,27 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Configuration; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Play { - public class KeyCounterCollection : FillFlowContainer + public class KeyCounterDisplay : FillFlowContainer { private const int duration = 100; public readonly Bindable Visible = new Bindable(true); private readonly Bindable configVisibility = new Bindable(); - public KeyCounterCollection() + public KeyCounterDisplay() { Direction = FillDirection.Horizontal; AutoSizeAxes = Axes.Both; @@ -37,9 +36,6 @@ namespace osu.Game.Screens.Play key.FadeTime = FadeTime; key.KeyDownTextColor = KeyDownTextColor; key.KeyUpTextColor = KeyUpTextColor; - // Use the same clock object as SongProgress for saving KeyCounter state - if (AudioClock != null) - key.Clock = AudioClock; } public void ResetCount() @@ -58,9 +54,10 @@ namespace osu.Game.Screens.Play } private bool isCounting = true; + public bool IsCounting { - get { return isCounting; } + get => isCounting; set { if (value == isCounting) return; @@ -72,9 +69,10 @@ namespace osu.Game.Screens.Play } private int fadeTime; + public int FadeTime { - get { return fadeTime; } + get => fadeTime; set { if (value != fadeTime) @@ -87,9 +85,10 @@ namespace osu.Game.Screens.Play } private Color4 keyDownTextColor = Color4.DarkGray; + public Color4 KeyDownTextColor { - get { return keyDownTextColor; } + get => keyDownTextColor; set { if (value != keyDownTextColor) @@ -102,9 +101,10 @@ namespace osu.Game.Screens.Play } private Color4 keyUpTextColor = Color4.White; + public Color4 KeyUpTextColor { - get { return keyUpTextColor; } + get => keyUpTextColor; set { if (value != keyUpTextColor) @@ -121,8 +121,6 @@ namespace osu.Game.Screens.Play public override bool HandleNonPositionalInput => receptor == null; public override bool HandlePositionalInput => receptor == null; - public IFrameBasedClock AudioClock { get; set; } - private Receptor receptor; public Receptor GetReceptor() @@ -140,9 +138,9 @@ namespace osu.Game.Screens.Play public class Receptor : Drawable { - protected readonly KeyCounterCollection Target; + protected readonly KeyCounterDisplay Target; - public Receptor(KeyCounterCollection target) + public Receptor(KeyCounterDisplay target) { RelativeSizeAxes = Axes.Both; Depth = float.MinValue; @@ -161,6 +159,7 @@ namespace osu.Game.Screens.Play case MouseUpEvent _: return Target.Children.Any(c => c.TriggerEvent(e)); } + return base.Handle(e); } } diff --git a/osu.Game/Screens/Play/KeyCounterKeyboard.cs b/osu.Game/Screens/Play/KeyCounterKeyboard.cs index 521f7d4e84..d9b6dca79d 100644 --- a/osu.Game/Screens/Play/KeyCounterKeyboard.cs +++ b/osu.Game/Screens/Play/KeyCounterKeyboard.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Events; using osuTK.Input; @@ -9,7 +9,9 @@ namespace osu.Game.Screens.Play public class KeyCounterKeyboard : KeyCounter { public Key Key { get; } - public KeyCounterKeyboard(Key key) : base(key.ToString()) + + public KeyCounterKeyboard(Key key) + : base(key.ToString()) { Key = key; } diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 3f7aaa6076..95fa58e5c0 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Input.Events; using osuTK.Input; @@ -11,7 +11,8 @@ namespace osu.Game.Screens.Play { public MouseButton Button { get; } - public KeyCounterMouse(MouseButton button) : base(getStringRepresentation(button)) + public KeyCounterMouse(MouseButton button) + : base(getStringRepresentation(button)) { Button = button; } @@ -24,8 +25,10 @@ namespace osu.Game.Screens.Play { default: return button.ToString(); + case MouseButton.Left: return @"M1"; + case MouseButton.Right: return @"M2"; } diff --git a/osu.Game/Screens/Play/KeyCounterState.cs b/osu.Game/Screens/Play/KeyCounterState.cs index e5c0703319..0bceae9f85 100644 --- a/osu.Game/Screens/Play/KeyCounterState.cs +++ b/osu.Game/Screens/Play/KeyCounterState.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Screens.Play { diff --git a/osu.Game/Screens/Play/PauseContainer.cs b/osu.Game/Screens/Play/PauseContainer.cs deleted file mode 100644 index 19a05e8eed..0000000000 --- a/osu.Game/Screens/Play/PauseContainer.cs +++ /dev/null @@ -1,146 +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.Linq; -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; -using osu.Game.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Screens.Play -{ - /// - /// A container which handles pausing children, displaying a pause overlay with choices etc. - /// This alleviates a lot of the intricate pause logic from being in - /// - public class PauseContainer : Container - { - public readonly BindableBool IsPaused = new BindableBool(); - - public Func CheckCanPause; - - private const double pause_cooldown = 1000; - private double lastPauseActionTime; - - private readonly PauseOverlay pauseOverlay; - - private readonly Container content; - - protected override Container Content => content; - - public int Retries { set { pauseOverlay.Retries = value; } } - - public bool CanPause => (CheckCanPause?.Invoke() ?? true) && Time.Current >= lastPauseActionTime + pause_cooldown; - public bool IsResuming { get; private set; } - - public Action OnRetry; - public Action OnQuit; - - private readonly FramedClock framedClock; - private readonly DecoupleableInterpolatingFramedClock decoupledClock; - - /// - /// 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.decoupledClock = decoupledClock; - - RelativeSizeAxes = Axes.Both; - - AddInternal(content = new Container - { - Clock = this.framedClock, - ProcessCustomClock = false, - RelativeSizeAxes = Axes.Both - }); - - AddInternal(pauseOverlay = new PauseOverlay - { - OnResume = () => - { - IsResuming = true; - this.Delay(400).Schedule(Resume); - }, - OnRetry = () => OnRetry(), - OnQuit = () => OnQuit(), - }); - } - - public void Pause(bool force = false) => Schedule(() => // Scheduled to ensure a stable position in execution order, no matter how it was called. - { - if (!CanPause && !force) return; - - if (IsPaused) return; - - // stop the seekable clock (stops the audio eventually) - decoupledClock.Stop(); - IsPaused.Value = true; - - pauseOverlay.Show(); - - lastPauseActionTime = Time.Current; - }); - - public void Resume() - { - if (!IsPaused) return; - - IsPaused.Value = false; - IsResuming = false; - lastPauseActionTime = Time.Current; - - // 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(); - - pauseOverlay.Hide(); - } - - private OsuGameBase game; - - [BackgroundDependencyLoader] - private void load(OsuGameBase game) - { - this.game = game; - } - - protected override void Update() - { - // eagerly pause when we lose window focus (if we are locally playing). - if (!game.IsActive && CanPause) - Pause(); - - if (!IsPaused) - framedClock.ProcessFrame(); - - base.Update(); - } - - public class PauseOverlay : GameplayMenuOverlay - { - public Action OnResume; - - public override string Header => "paused"; - public override string Description => "you're not going to do what i think you're going to do, are ya?"; - - protected override Action BackAction => () => InternalButtons.Children.First().Click(); - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AddButton("Continue", colours.Green, () => OnResume?.Invoke()); - AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); - AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); - } - } - } -} diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs new file mode 100644 index 0000000000..6cc6027a03 --- /dev/null +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + public class PauseOverlay : GameplayMenuOverlay + { + public Action OnResume; + + public override string Header => "paused"; + public override string Description => "you're not going to do what i think you're going to do, are ya?"; + + protected override Action BackAction => () => InternalButtons.Children.First().Click(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddButton("Continue", colours.Green, () => OnResume?.Invoke()); + AddButton("Retry", colours.YellowDark, () => OnRetry?.Invoke()); + AddButton("Quit", new Color4(170, 27, 39, 255), () => OnQuit?.Invoke()); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a220a05971..30a3bc2fc6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1,109 +1,224 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osu.Game.Storyboards.Drawables; +using osu.Game.Users; namespace osu.Game.Screens.Play { - public class Player : ScreenWithBeatmapBackground, IProvideCursor + public class Player : ScreenWithBeatmapBackground { - protected override bool AllowBackButton => false; // handled by HoldForMenuButton + public override bool AllowBackButton => false; // handled by HoldForMenuButton - protected override float BackgroundParallaxAmount => 0.1f; + protected override UserActivity InitialActivity => new UserActivity.SoloGame(Beatmap.Value.BeatmapInfo, Ruleset.Value); - protected override bool HideOverlaysOnEnter => true; + public override float BackgroundParallaxAmount => 0.1f; - protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + public override bool HideOverlaysOnEnter => true; + + public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; + + /// + /// Whether gameplay should pause when the game window focus is lost. + /// + protected virtual bool PauseOnFocusLost => true; public Action RestartRequested; public bool HasFailed { get; private set; } - public bool AllowPause { get; set; } = true; - public bool AllowLeadIn { get; set; } = true; - public bool AllowResults { get; set; } = true; - private Bindable mouseWheelDisabled; - private Bindable userAudioOffset; + + private readonly Bindable storyboardReplacesBackground = new Bindable(); public int RestartCount; - public CursorContainer Cursor => RulesetContainer.Cursor; - public bool ProvidingUserCursor => RulesetContainer?.Cursor != null && !RulesetContainer.HasReplayLoaded.Value; - - private IAdjustableClock sourceClock; - - /// - /// The decoupled clock used for gameplay. Should be used for seeks and clock control. - /// - private DecoupleableInterpolatingFramedClock adjustableClock; - - private PauseContainer pauseContainer; + [Resolved] + private ScoreManager scoreManager { get; set; } private RulesetInfo ruleset; - private APIAccess api; + private IAPIProvider api; private SampleChannel sampleRestart; - protected ScoreProcessor ScoreProcessor; - protected RulesetContainer RulesetContainer; + protected ScoreProcessor ScoreProcessor { get; private set; } + protected DrawableRuleset DrawableRuleset { get; private set; } - private HUDOverlay hudOverlay; - private FailOverlay failOverlay; + protected HUDOverlay HUDOverlay { get; private set; } - private DrawableStoryboard storyboard; - private Container storyboardContainer; + public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; - public bool LoadedBeatmapSuccessfully => RulesetContainer?.Objects.Any() == true; + protected GameplayClockContainer GameplayClockContainer { get; private set; } + + [Cached] + [Cached(Type = typeof(IBindable>))] + protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); + + private readonly bool allowPause; + private readonly bool showResults; + + /// + /// Create a new player instance. + /// + /// Whether pausing should be allowed. If not allowed, attempting to pause will quit. + /// Whether results screen should be pushed on completion. + public Player(bool allowPause = true, bool showResults = true) + { + this.allowPause = allowPause; + this.showResults = showResults; + } [BackgroundDependencyLoader] - private void load(AudioManager audio, APIAccess api, OsuConfigManager config) + private void load(AudioManager audio, IAPIProvider api, OsuConfigManager config) { this.api = api; - WorkingBeatmap working = Beatmap.Value; - if (working is DummyWorkingBeatmap) + Mods.Value = base.Mods.Value.Select(m => m.CreateCopy()).ToArray(); + + WorkingBeatmap working = loadBeatmap(); + + if (working == null) return; - sampleRestart = audio.Sample.Get(@"Gameplay/restart"); + sampleRestart = audio.Samples.Get(@"Gameplay/restart"); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - userAudioOffset = config.GetBindable(OsuSetting.AudioOffset); + showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - IBeatmap beatmap; + ScoreProcessor = DrawableRuleset.CreateScoreProcessor(); + ScoreProcessor.Mods.BindTo(Mods); + + if (!ScoreProcessor.Mode.Disabled) + config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); + + InternalChild = GameplayClockContainer = new GameplayClockContainer(working, Mods.Value, DrawableRuleset.GameplayStartTime); + + GameplayClockContainer.Children = new[] + { + StoryboardContainer = CreateStoryboardContainer(), + new ScalingContainer(ScalingMode.Gameplay) + { + Child = new LocalSkinOverrideContainer(working.Skin) + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + DrawableRuleset, + new ComboEffects(ScoreProcessor) + } + } + }, + new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Breaks = working.Beatmap.Breaks + }, + // display the cursor above some HUD elements. + DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), + HUDOverlay = new HUDOverlay(ScoreProcessor, DrawableRuleset, Mods.Value) + { + HoldToQuit = + { + Action = performUserRequestedExit, + IsPaused = { BindTarget = GameplayClockContainer.IsPaused } + }, + PlayerSettingsOverlay = { PlaybackSettings = { UserPlaybackRate = { BindTarget = GameplayClockContainer.UserPlaybackRate } } }, + KeyCounter = { Visible = { BindTarget = DrawableRuleset.HasReplayLoaded } }, + RequestSeek = GameplayClockContainer.Seek, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + new SkipOverlay(DrawableRuleset.GameplayStartTime) + { + RequestSeek = GameplayClockContainer.Seek + }, + FailOverlay = new FailOverlay + { + OnRetry = Restart, + OnQuit = performUserRequestedExit, + }, + PauseOverlay = new PauseOverlay + { + OnResume = Resume, + Retries = RestartCount, + OnRetry = Restart, + OnQuit = performUserRequestedExit, + }, + new HotkeyRetryOverlay + { + Action = () => + { + if (!this.IsCurrentScreen()) return; + + fadeOut(true); + Restart(); + }, + }, + new HotkeyExitOverlay + { + Action = () => + { + if (!this.IsCurrentScreen()) return; + + fadeOut(true); + performImmediateExit(); + }, + }, + failAnimation = new FailAnimation(DrawableRuleset) { OnComplete = onFailComplete, } + }; + + DrawableRuleset.HasReplayLoaded.BindValueChanged(e => HUDOverlay.HoldToQuit.PauseOnFocusLost = !e.NewValue && PauseOnFocusLost, true); + + // bind clock into components that require it + DrawableRuleset.IsPaused.BindTo(GameplayClockContainer.IsPaused); + + // load storyboard as part of player's load if we can + initializeStoryboard(false); + + // Bind ScoreProcessor to ourselves + ScoreProcessor.AllJudged += onCompletion; + ScoreProcessor.Failed += onFail; + + foreach (var mod in Mods.Value.OfType()) + mod.ApplyToScoreProcessor(ScoreProcessor); + } + + private WorkingBeatmap loadBeatmap() + { + WorkingBeatmap working = Beatmap.Value; + if (working is DummyWorkingBeatmap) + return null; try { - beatmap = working.Beatmap; + var beatmap = working.Beatmap; if (beatmap == null) throw new InvalidOperationException("Beatmap was not loaded"); @@ -113,145 +228,62 @@ namespace osu.Game.Screens.Play try { - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(working); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(working, Mods.Value); } catch (BeatmapInvalidForRulesetException) { - // we may fail to create a RulesetContainer if the beatmap cannot be loaded with the user's preferred ruleset + // we may fail to create a DrawableRuleset if the beatmap cannot be loaded with the user's preferred ruleset // let's try again forcing the beatmap's ruleset. ruleset = beatmap.BeatmapInfo.Ruleset; rulesetInstance = ruleset.CreateInstance(); - RulesetContainer = rulesetInstance.CreateRulesetContainerWith(Beatmap.Value); + DrawableRuleset = rulesetInstance.CreateDrawableRulesetWith(Beatmap.Value, Mods.Value); } - if (!RulesetContainer.Objects.Any()) + if (!DrawableRuleset.Objects.Any()) { Logger.Log("Beatmap contains no hit objects!", level: LogLevel.Error); - return; + return null; } } catch (Exception e) { Logger.Error(e, "Could not load beatmap sucessfully!"); //couldn't load, hard abort! - return; + return null; } - sourceClock = (IAdjustableClock)working.Track ?? new StopwatchClock(); - adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - - adjustableClock.Seek(AllowLeadIn - ? Math.Min(0, RulesetContainer.GameplayStartTime - beatmap.BeatmapInfo.AudioLeadIn) - : RulesetContainer.GameplayStartTime); - - 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(platformOffsetClock); - - userAudioOffset.ValueChanged += v => offsetClock.Offset = v; - userAudioOffset.TriggerChange(); - - ScoreProcessor = RulesetContainer.CreateScoreProcessor(); - if (!ScoreProcessor.Mode.Disabled) - config.BindWith(OsuSetting.ScoreDisplayMode, ScoreProcessor.Mode); - - Children = new Drawable[] - { - pauseContainer = new PauseContainer(offsetClock, adjustableClock) - { - Retries = RestartCount, - OnRetry = Restart, - OnQuit = Exit, - CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, - Children = new[] - { - storyboardContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - new LocalSkinOverrideContainer(working.Skin) - { - RelativeSizeAxes = Axes.Both, - Child = RulesetContainer - }, - new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - ProcessCustomClock = false, - Breaks = beatmap.Breaks - }, - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), - hudOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) - { - Clock = Clock, // hud overlay doesn't want to use the audio clock directly - ProcessCustomClock = false, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - new SkipOverlay(RulesetContainer.GameplayStartTime) - { - Clock = Clock, // skip button doesn't want to use the audio clock directly - ProcessCustomClock = false, - AdjustableClock = adjustableClock, - FramedClock = offsetClock, - }, - } - }, - failOverlay = new FailOverlay - { - OnRetry = Restart, - OnQuit = Exit, - }, - new HotkeyRetryOverlay - { - Action = () => - { - if (!IsCurrentScreen) return; - - fadeOut(true); - Restart(); - }, - } - }; - - hudOverlay.HoldToQuit.Action = Exit; - hudOverlay.KeyCounter.Visible.BindTo(RulesetContainer.HasReplayLoaded); - - RulesetContainer.IsPaused.BindTo(pauseContainer.IsPaused); - - if (ShowStoryboard) - initializeStoryboard(false); - - // Bind ScoreProcessor to ourselves - ScoreProcessor.AllJudged += onCompletion; - ScoreProcessor.Failed += onFail; - - foreach (var mod in Beatmap.Value.Mods.Value.OfType()) - mod.ApplyToScoreProcessor(ScoreProcessor); + return working; } - private void applyRateFromMods() + private void performImmediateExit() { - if (sourceClock == null) return; + // if a restart has been requested, cancel any pending completion (user has shown intent to restart). + onCompletionEvent = null; - sourceClock.Rate = 1; - foreach (var mod in Beatmap.Value.Mods.Value.OfType()) - mod.ApplyToClock(sourceClock); + ValidForResume = false; + + performUserRequestedExit(); + } + + private void performUserRequestedExit() + { + if (!this.IsCurrentScreen()) return; + + this.Exit(); } public void Restart() { + if (!this.IsCurrentScreen()) return; + sampleRestart?.Play(); + + // if a restart has been requested, cancel any pending completion (user has shown intent to restart). + onCompletionEvent = null; + ValidForResume = false; RestartRequested?.Invoke(); - Exit(); + this.Exit(); } private ScheduledDelegate onCompletionEvent; @@ -264,119 +296,64 @@ namespace osu.Game.Screens.Play ValidForResume = false; - if (!AllowResults) return; + if (!showResults) return; using (BeginDelayedSequence(1000)) { onCompletionEvent = Schedule(delegate { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; - var score = new Score - { - Beatmap = Beatmap.Value.BeatmapInfo, - Ruleset = ruleset - }; - ScoreProcessor.PopulateScore(score); - score.User = RulesetContainer.Replay?.User ?? api.LocalUser.Value; - Push(new Results(score)); + var score = CreateScore(); + if (DrawableRuleset.ReplayScore == null) + scoreManager.Import(score).Wait(); + + this.Push(CreateResults(score)); onCompletionEvent = null; }); } } - private bool onFail() + protected virtual ScoreInfo CreateScore() { - if (Beatmap.Value.Mods.Value.OfType().Any(m => !m.AllowFail)) - return false; - - adjustableClock.Stop(); - - HasFailed = true; - failOverlay.Retries = RestartCount; - failOverlay.Show(); - return true; - } - - protected override void OnEntering(Screen last) - { - base.OnEntering(last); - - if (!LoadedBeatmapSuccessfully) - return; - - Content.Alpha = 0; - Content - .ScaleTo(0.7f) - .ScaleTo(1, 750, Easing.OutQuint) - .Delay(250) - .FadeIn(250); - - Task.Run(() => + var score = DrawableRuleset.ReplayScore?.ScoreInfo ?? new ScoreInfo { - sourceClock.Reset(); + Beatmap = Beatmap.Value.BeatmapInfo, + Ruleset = ruleset, + Mods = Mods.Value.ToArray(), + User = api.LocalUser.Value, + }; - Schedule(() => - { - adjustableClock.ChangeSource(sourceClock); - applyRateFromMods(); + ScoreProcessor.PopulateScore(score); - this.Delay(750).Schedule(() => - { - if (!pauseContainer.IsPaused) - { - adjustableClock.Start(); - } - }); - }); - }); - - pauseContainer.Alpha = 0; - pauseContainer.FadeIn(750, Easing.OutQuint); + return score; } - protected override void OnSuspending(Screen next) + protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; + + protected virtual Results CreateResults(ScoreInfo score) => new SoloResults(score); + + #region Storyboard + + private DrawableStoryboard storyboard; + protected UserDimContainer StoryboardContainer { get; private set; } + + protected virtual UserDimContainer CreateStoryboardContainer() => new UserDimContainer(true) { - fadeOut(); - base.OnSuspending(next); - } + RelativeSizeAxes = Axes.Both, + Alpha = 1, + EnableUserDim = { Value = true } + }; - protected override bool OnExiting(Screen next) - { - if (onCompletionEvent != null) - { - // Proceed to result screen if beatmap already finished playing - onCompletionEvent.RunTask(); - return true; - } - - if ((!AllowPause || HasFailed || !ValidForResume || pauseContainer?.IsPaused != false || RulesetContainer?.HasReplayLoaded != false) && (!pauseContainer?.IsResuming ?? true)) - { - // In the case of replays, we may have changed the playback rate. - applyRateFromMods(); - - fadeOut(); - return base.OnExiting(next); - } - - if (LoadedBeatmapSuccessfully) - pauseContainer?.Pause(); - - return true; - } - - private void fadeOut(bool instant = false) - { - float fadeOutDuration = instant ? 0 : 250; - Content.FadeOut(fadeOutDuration); - } - - protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !pauseContainer.IsPaused; + private Bindable showStoryboard; private void initializeStoryboard(bool asyncLoad) { - if (storyboardContainer == null) + if (StoryboardContainer == null || storyboard != null) + return; + + if (!showStoryboard.Value) return; var beatmap = Beatmap.Value; @@ -385,29 +362,191 @@ namespace osu.Game.Screens.Play storyboard.Masking = true; if (asyncLoad) - LoadComponentAsync(storyboard, storyboardContainer.Add); + LoadComponentAsync(storyboard, StoryboardContainer.Add); else - storyboardContainer.Add(storyboard); + StoryboardContainer.Add(storyboard); } - protected override void UpdateBackgroundElements() + #endregion + + #region Fail Logic + + protected FailOverlay FailOverlay { get; private set; } + + private FailAnimation failAnimation; + + private bool onFail() { - if (!IsCurrentScreen) return; + if (Mods.Value.OfType().Any(m => !m.AllowFail)) + return false; - base.UpdateBackgroundElements(); + HasFailed = true; - if (ShowStoryboard && storyboard == null) - initializeStoryboard(true); + // There is a chance that we could be in a paused state as the ruleset's internal clock (see FrameStabilityContainer) + // could process an extra frame after the GameplayClock is stopped. + // In such cases we want the fail state to precede a user triggered pause. + if (PauseOverlay.State.Value == Visibility.Visible) + PauseOverlay.Hide(); - var beatmap = Beatmap.Value; - var storyboardVisible = ShowStoryboard && beatmap.Storyboard.HasDrawable; - - storyboardContainer? - .FadeColour(OsuColour.Gray(BackgroundOpacity), BACKGROUND_FADE_DURATION, Easing.OutQuint) - .FadeTo(storyboardVisible && BackgroundOpacity > 0 ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint); - - if (storyboardVisible && beatmap.Storyboard.ReplacesBackground) - Background?.FadeTo(0, BACKGROUND_FADE_DURATION, Easing.OutQuint); + failAnimation.Start(); + return true; } + + // Called back when the transform finishes + private void onFailComplete() + { + GameplayClockContainer.Stop(); + + FailOverlay.Retries = RestartCount; + FailOverlay.Show(); + } + + #endregion + + #region Pause Logic + + public bool IsResuming { get; private set; } + + /// + /// The amount of gameplay time after which a second pause is allowed. + /// + private const double pause_cooldown = 1000; + + protected PauseOverlay PauseOverlay { get; private set; } + + private double? lastPauseActionTime; + + private bool canPause => + // must pass basic screen conditions (beatmap loaded, instance allows pause) + LoadedBeatmapSuccessfully && allowPause && ValidForResume + // replays cannot be paused and exit immediately + && !DrawableRuleset.HasReplayLoaded.Value + // cannot pause if we are already in a fail state + && !HasFailed + // cannot pause if already paused (or in a cooldown state) unless we are in a resuming state. + && (IsResuming || (GameplayClockContainer.IsPaused.Value == false && !pauseCooldownActive)); + + private bool pauseCooldownActive => + lastPauseActionTime.HasValue && GameplayClockContainer.GameplayClock.CurrentTime < lastPauseActionTime + pause_cooldown; + + private bool canResume => + // cannot resume from a non-paused state + GameplayClockContainer.IsPaused.Value + // cannot resume if we are already in a fail state + && !HasFailed + // already resuming + && !IsResuming; + + public void Pause() + { + if (!canPause) return; + + IsResuming = false; + GameplayClockContainer.Stop(); + PauseOverlay.Show(); + lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; + } + + public void Resume() + { + if (!canResume) return; + + IsResuming = true; + PauseOverlay.Hide(); + + // breaks and time-based conditions may allow instant resume. + double time = GameplayClockContainer.GameplayClock.CurrentTime; + if (Beatmap.Value.Beatmap.Breaks.Any(b => b.Contains(time)) || time < Beatmap.Value.Beatmap.HitObjects.First().StartTime) + completeResume(); + else + DrawableRuleset.RequestResume(completeResume); + + void completeResume() + { + GameplayClockContainer.Start(); + IsResuming = false; + } + } + + #endregion + + #region Screen Logic + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + if (!LoadedBeatmapSuccessfully) + return; + + Alpha = 0; + this + .ScaleTo(0.7f) + .ScaleTo(1, 750, Easing.OutQuint) + .Delay(250) + .FadeIn(250); + + showStoryboard.ValueChanged += _ => initializeStoryboard(true); + + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; + + Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); + StoryboardContainer.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); + + storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; + + GameplayClockContainer.Restart(); + GameplayClockContainer.FadeInFromZero(750, Easing.OutQuint); + } + + public override void OnSuspending(IScreen next) + { + fadeOut(); + base.OnSuspending(next); + } + + public override bool OnExiting(IScreen next) + { + if (onCompletionEvent != null) + { + // Proceed to result screen if beatmap already finished playing + onCompletionEvent.RunTask(); + return true; + } + + if (canPause) + { + Pause(); + return true; + } + + if (pauseCooldownActive && !GameplayClockContainer.IsPaused.Value) + // still want to block if we are within the cooldown period and not already paused. + return true; + + if (HasFailed && ValidForResume && !FailOverlay.IsPresent) + // ValidForResume is false when restarting + { + failAnimation.FinishTransforms(true); + return true; + } + + GameplayClockContainer.ResetLocalAdjustments(); + + fadeOut(); + return base.OnExiting(next); + } + + private void fadeOut(bool instant = false) + { + float fadeOutDuration = instant ? 0 : 250; + this.FadeOut(fadeOutDuration); + + Background.EnableUserDim.Value = false; + storyboardReplacesBackground.Value = false; + } + + #endregion } } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index bf44a60473..681ce701d0 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -1,22 +1,28 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; +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.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -24,59 +30,78 @@ namespace osu.Game.Screens.Play { public class PlayerLoader : ScreenWithBeatmapBackground { - private static readonly Vector2 background_blur = new Vector2(15); + protected const float BACKGROUND_BLUR = 15; + + private readonly Func createPlayer; private Player player; + private LogoTrackingContainer content; + private BeatmapMetadataDisplay info; private bool hideOverlays; - protected override bool HideOverlaysOnEnter => hideOverlays; + public override bool HideOverlaysOnEnter => hideOverlays; + + protected override UserActivity InitialActivity => null; //shows the previous screen status + + public override bool DisallowExternalBeatmapRulesetChanges => true; + + protected override bool PlayResumeSound => false; private Task loadTask; - public PlayerLoader(Player player) - { - this.player = player; + private InputManager inputManager; - player.RestartRequested = () => - { - hideOverlays = true; - ValidForResume = true; - }; + public PlayerLoader(Func createPlayer) + { + this.createPlayer = createPlayer; + } + + private void restartRequested() + { + hideOverlays = true; + ValidForResume = true; } [BackgroundDependencyLoader] private void load() { - Add(info = new BeatmapMetadataDisplay(Beatmap.Value) + InternalChild = (content = new LogoTrackingContainer { - Alpha = 0, Anchor = Anchor.Centre, Origin = Anchor.Centre, - }); - - Add(new FillFlowContainer + RelativeSizeAxes = Axes.Both, + }).WithChildren(new Drawable[] { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 20), - Margin = new MarginPadding(25), - Children = new PlayerSettingsGroup[] + info = new BeatmapMetadataDisplay(Beatmap.Value, Mods.Value, content.LogoFacade) { - visualSettings = new VisualSettings(), - new InputSettings() + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 20), + Margin = new MarginPadding(25), + Children = new PlayerSettingsGroup[] + { + VisualSettings = new VisualSettings(), + new InputSettings() + } } }); - loadTask = LoadComponentAsync(player, playerLoaded); + loadNewPlayer(); } private void playerLoaded(Player player) => info.Loading = false; - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); @@ -85,32 +110,43 @@ namespace osu.Game.Screens.Play info.Loading = true; //we will only be resumed if the player has requested a re-run (see ValidForResume setting above) - loadTask = LoadComponentAsync(player = new Player - { - RestartCount = player.RestartCount + 1, - RestartRequested = player.RestartRequested, - }, playerLoaded); + loadNewPlayer(); this.Delay(400).Schedule(pushWhenLoaded); } + private void loadNewPlayer() + { + var restartCount = player?.RestartCount + 1 ?? 0; + + player = createPlayer(); + player.RestartCount = restartCount; + player.RestartRequested = restartRequested; + + loadTask = LoadComponentAsync(player, playerLoaded); + } + private void contentIn() { - Content.ScaleTo(1, 650, Easing.OutQuint); - Content.FadeInFromZero(400); + content.ScaleTo(1, 650, Easing.OutQuint); + content.FadeInFromZero(400); } private void contentOut() { - Content.ScaleTo(0.7f, 300, Easing.InQuint); - Content.FadeOut(250); + // Ensure the logo is no longer tracking before we scale the content + content.StopTracking(); + + content.ScaleTo(0.7f, 300, Easing.InQuint); + content.FadeOut(250); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - Content.ScaleTo(0.7f); + content.ScaleTo(0.7f); + Background?.FadeColour(Color4.White, 800, Easing.OutQuint); contentIn(); @@ -122,46 +158,46 @@ namespace osu.Game.Screens.Play { base.LogoArriving(logo, resuming); - logo.RelativePositionAxes = Axes.Both; + const double duration = 300; - logo.ScaleTo(new Vector2(0.15f), 300, Easing.In); - logo.MoveTo(new Vector2(0.5f), 300, Easing.In); + if (!resuming) + { + logo.MoveTo(new Vector2(0.5f), duration, Easing.In); + } + + logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); logo.FadeIn(350); - logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); + Scheduler.AddDelayed(() => + { + if (this.IsCurrentScreen()) + content.StartTracking(logo, resuming ? 0 : 500, Easing.InOutExpo); + }, resuming ? 0 : 500); + } + + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + content.StopTracking(); + } + + protected override void LoadComplete() + { + inputManager = GetContainingInputManager(); + base.LoadComplete(); } private ScheduledDelegate pushDebounce; - private VisualSettings visualSettings; + protected VisualSettings VisualSettings; + + // Here because IsHovered will not update unless we do so. + public override bool HandlePositionalInput => true; private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && GetContainingInputManager()?.DraggedDrawable == null; - protected override bool OnHover(HoverEvent e) - { - // restore our screen defaults - InitializeBackgroundElements(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - if (GetContainingInputManager().HoveredDrawables.Contains(visualSettings)) - { - // show user setting preview - UpdateBackgroundElements(); - } - base.OnHoverLost(e); - } - - protected override void InitializeBackgroundElements() - { - Background?.FadeTo(1, BACKGROUND_FADE_DURATION, Easing.OutQuint); - Background?.BlurTo(background_blur, BACKGROUND_FADE_DURATION, Easing.OutQuint); - } - private void pushWhenLoaded() { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; try { @@ -182,7 +218,7 @@ namespace osu.Game.Screens.Play this.Delay(250).Schedule(() => { - if (!IsCurrentScreen) return; + if (!this.IsCurrentScreen()) return; loadTask = null; @@ -191,9 +227,9 @@ namespace osu.Game.Screens.Play ValidForResume = false; if (player.LoadedBeatmapSuccessfully) - Push(player); + this.Push(player); else - Exit(); + this.Exit(); }); }, 500); } @@ -209,18 +245,22 @@ namespace osu.Game.Screens.Play pushDebounce = null; } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { + BackgroundBrightnessReduction = false; base.OnSuspending(next); cancelLoad(); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { - Content.ScaleTo(0.7f, 150, Easing.InQuint); + content.ScaleTo(0.7f, 150, Easing.InQuint); this.FadeOut(150); cancelLoad(); + Background.EnableUserDim.Value = false; + BackgroundBrightnessReduction = false; + return base.OnExiting(next); } @@ -235,6 +275,49 @@ namespace osu.Game.Screens.Play } } + private bool backgroundBrightnessReduction; + + protected bool BackgroundBrightnessReduction + { + get => backgroundBrightnessReduction; + set + { + if (value == backgroundBrightnessReduction) + return; + + backgroundBrightnessReduction = value; + + Background.FadeColour(OsuColour.Gray(backgroundBrightnessReduction ? 0.8f : 1), 200); + } + } + + protected override void Update() + { + base.Update(); + + if (!this.IsCurrentScreen()) + return; + + // We need to perform this check here rather than in OnHover as any number of children of VisualSettings + // may also be handling the hover events. + if (inputManager.HoveredDrawables.Contains(VisualSettings)) + { + // Preview user-defined background dim and blur when hovered on the visual settings panel. + Background.EnableUserDim.Value = true; + Background.BlurAmount.Value = 0; + + BackgroundBrightnessReduction = false; + } + else + { + // Returns background dim and blur to the values specified by PlayerLoader. + Background.EnableUserDim.Value = false; + Background.BlurAmount.Value = BACKGROUND_BLUR; + + BackgroundBrightnessReduction = true; + } + } + private class BeatmapMetadataDisplay : Container { private class MetadataLine : Container @@ -264,6 +347,8 @@ namespace osu.Game.Screens.Play } private readonly WorkingBeatmap beatmap; + private readonly IReadOnlyList mods; + private readonly Drawable facade; private LoadingAnimation loading; private Sprite backgroundSprite; @@ -284,15 +369,17 @@ namespace osu.Game.Screens.Play } } - public BeatmapMetadataDisplay(WorkingBeatmap beatmap) + public BeatmapMetadataDisplay(WorkingBeatmap beatmap, IReadOnlyList mods, Drawable facade) { this.beatmap = beatmap; + this.mods = mods; + this.facade = facade; } [BackgroundDependencyLoader] private void load() { - var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); + var metadata = beatmap.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); AutoSizeAxes = Axes.Both; Children = new Drawable[] @@ -303,21 +390,25 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Direction = FillDirection.Vertical, - Children = new Drawable[] + Children = new[] { + facade.With(d => + { + d.Anchor = Anchor.TopCentre; + d.Origin = Anchor.TopCentre; + }), new OsuSpriteText { Text = new LocalisedString((metadata.TitleUnicode, metadata.Title)), - TextSize = 36, - Font = @"Exo2.0-MediumItalic", + Font = OsuFont.GetFont(size: 36, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, + Margin = new MarginPadding { Top = 15 }, }, new OsuSpriteText { Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist)), - TextSize = 26, - Font = @"Exo2.0-MediumItalic", + Font = OsuFont.GetFont(size: 26, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, }, @@ -345,8 +436,7 @@ namespace osu.Game.Screens.Play new OsuSpriteText { Text = beatmap?.BeatmapInfo?.Version, - TextSize = 26, - Font = @"Exo2.0-MediumItalic", + Font = OsuFont.GetFont(size: 26, italics: true), Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, Margin = new MarginPadding @@ -364,6 +454,14 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, }, + new ModDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 20 }, + Current = { Value = mods } + } }, } }; diff --git a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs index da47de8a9b..d3570a8d2d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/CollectionSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs b/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs index 31d390effe..bb4eea47ca 100644 --- a/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/DiscussionSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Play.PlayerSettings new PlayerCheckbox { LabelText = "Show floating comments", - Bindable = config.GetBindable(OsuSetting.FloatingComments) + Current = config.GetBindable(OsuSetting.FloatingComments) }, new FocusedTextBox { diff --git a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs index 755ba468cc..7a8696e27c 100644 --- a/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/InputSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -25,6 +25,6 @@ namespace osu.Game.Screens.Play.PlayerSettings } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) => mouseButtonsCheckbox.Bindable = config.GetBindable(OsuSetting.MouseDisableButtons); + private void load(OsuConfigManager config) => mouseButtonsCheckbox.Current = config.GetBindable(OsuSetting.MouseDisableButtons); } } diff --git a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs index 13e403e899..c691d161ed 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlaybackSettings.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Play.PlayerSettings @@ -15,14 +15,20 @@ namespace osu.Game.Screens.Play.PlayerSettings protected override string Title => @"playback"; - public IAdjustableClock AdjustableClock { set; get; } + public readonly Bindable UserPlaybackRate = new BindableDouble(1) + { + Default = 1, + MinValue = 0.5, + MaxValue = 2, + Precision = 0.1, + }; - private readonly PlayerSliderBar sliderbar; + private readonly PlayerSliderBar rateSlider; + + private readonly OsuSpriteText multiplierText; public PlaybackSettings() { - OsuSpriteText multiplierText; - Children = new Drawable[] { new Container @@ -42,35 +48,18 @@ namespace osu.Game.Screens.Play.PlayerSettings { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), } }, }, - sliderbar = new PlayerSliderBar - { - Bindable = new BindableDouble(1) - { - Default = 1, - MinValue = 0.5, - MaxValue = 2, - Precision = 0.1, - }, - } + rateSlider = new PlayerSliderBar { Bindable = UserPlaybackRate } }; - - sliderbar.Bindable.ValueChanged += rateMultiplier => multiplierText.Text = $"{sliderbar.Bar.TooltipText}x"; - sliderbar.Bindable.TriggerChange(); } protected override void LoadComplete() { base.LoadComplete(); - - if (AdjustableClock == null) - return; - - var clockRate = AdjustableClock.Rate; - sliderbar.Bindable.ValueChanged += rateMultiplier => AdjustableClock.Rate = clockRate * rateMultiplier; + rateSlider.Bindable.BindValueChanged(multiplier => multiplierText.Text = $"{multiplier.NewValue:0.0}x", true); } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs index 2a9da9c6e1..8a4acacb24 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerCheckbox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Game.Graphics; diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 833a22556e..90424ec007 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -1,10 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -34,10 +35,11 @@ namespace osu.Game.Screens.Play.PlayerSettings public bool Expanded { - get { return expanded; } + get => expanded; set { if (expanded == value) return; + expanded = value; content.ClearTransforms(); @@ -94,8 +96,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, Text = Title.ToUpperInvariant(), - TextSize = 17, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Margin = new MarginPadding { Left = 10 }, }, button = new IconButton @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Origin = Anchor.Centre, Anchor = Anchor.CentreRight, Position = new Vector2(-15, 0), - Icon = FontAwesome.fa_bars, + Icon = FontAwesome.Solid.Bars, Scale = new Vector2(0.75f), Action = () => Expanded = !Expanded, }, diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs index d2c7677f7c..28fe1f35ca 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSliderBar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; diff --git a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs index f762597e81..1c8628f704 100644 --- a/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/VisualSettings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.PlayerSettings }, showStoryboardToggle = new PlayerCheckbox { LabelText = "Storyboards" }, beatmapSkinsToggle = new PlayerCheckbox { LabelText = "Beatmap skins" }, - beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hit sounds" } + beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = "Beatmap hitsounds" } }; } @@ -47,9 +47,9 @@ namespace osu.Game.Screens.Play.PlayerSettings { dimSliderBar.Bindable = config.GetBindable(OsuSetting.DimLevel); blurSliderBar.Bindable = config.GetBindable(OsuSetting.BlurLevel); - showStoryboardToggle.Bindable = config.GetBindable(OsuSetting.ShowStoryboard); - beatmapSkinsToggle.Bindable = config.GetBindable(OsuSetting.BeatmapSkins); - beatmapHitsoundsToggle.Bindable = config.GetBindable(OsuSetting.BeatmapHitsounds); + showStoryboardToggle.Current = config.GetBindable(OsuSetting.ShowStoryboard); + beatmapSkinsToggle.Current = config.GetBindable(OsuSetting.BeatmapSkins); + beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); } } } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 6e2a2e4c9c..a9c0ee3a15 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,23 +1,26 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; namespace osu.Game.Screens.Play { public class ReplayPlayer : Player { - public Replay Replay; + private readonly Score score; - public ReplayPlayer(Replay replay) + public ReplayPlayer(Score score, bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) { - Replay = replay; + this.score = score; } protected override void LoadComplete() { base.LoadComplete(); - RulesetContainer.SetReplay(Replay); + DrawableRuleset?.SetReplayScore(score); } + + protected override ScoreInfo CreateScore() => score.ScoreInfo; } } diff --git a/osu.Game/Screens/Play/ResumeOverlay.cs b/osu.Game/Screens/Play/ResumeOverlay.cs new file mode 100644 index 0000000000..2ef76069c2 --- /dev/null +++ b/osu.Game/Screens/Play/ResumeOverlay.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// An overlay which can be used to require further user actions before gameplay is resumed. + /// + public abstract class ResumeOverlay : OverlayContainer + { + public CursorContainer GameplayCursor { get; set; } + + /// + /// The action to be performed to complete resuming. + /// + public Action ResumeAction { private get; set; } + + public virtual CursorContainer LocalCursor => null; + + protected const float TRANSITION_TIME = 500; + + protected override bool BlockPositionalInput => false; + + protected abstract string Message { get; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + + protected ResumeOverlay() + { + RelativeSizeAxes = Axes.Both; + } + + protected void Resume() + { + ResumeAction?.Invoke(); + Hide(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AddRange(new Drawable[] + { + new OsuSpriteText + { + RelativePositionAxes = Axes.Both, + Y = 0.4f, + Text = Message, + Font = OsuFont.GetFont(size: 30), + Spacing = new Vector2(5, 0), + Origin = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Colour = colours.Yellow, + Shadow = true, + ShadowColour = new Color4(0, 0, 0, 0.25f) + } + }); + } + + protected override void PopIn() => this.FadeIn(TRANSITION_TIME, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(TRANSITION_TIME, Easing.OutQuint); + } +} diff --git a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs index 3846b45d2f..d7d2c97598 100644 --- a/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs +++ b/osu.Game/Screens/Play/ScreenWithBeatmapBackground.cs @@ -1,13 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Screens; -using osu.Game.Configuration; using osu.Game.Screens.Backgrounds; -using osuTK; namespace osu.Game.Screens.Play { @@ -16,59 +10,5 @@ namespace osu.Game.Screens.Play protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); protected new BackgroundScreenBeatmap Background => (BackgroundScreenBeatmap)base.Background; - - public override bool AllowBeatmapRulesetChange => false; - - protected const float BACKGROUND_FADE_DURATION = 800; - - protected float BackgroundOpacity => 1 - (float)DimLevel; - - #region User Settings - - protected Bindable DimLevel; - protected Bindable BlurLevel; - protected Bindable ShowStoryboard; - - #endregion - - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - DimLevel = config.GetBindable(OsuSetting.DimLevel); - BlurLevel = config.GetBindable(OsuSetting.BlurLevel); - ShowStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); - } - - protected override void OnEntering(Screen last) - { - base.OnEntering(last); - DimLevel.ValueChanged += _ => UpdateBackgroundElements(); - BlurLevel.ValueChanged += _ => UpdateBackgroundElements(); - ShowStoryboard.ValueChanged += _ => UpdateBackgroundElements(); - InitializeBackgroundElements(); - } - - protected override void OnResuming(Screen last) - { - base.OnResuming(last); - InitializeBackgroundElements(); - } - - /// - /// Called once on entering screen. By Default, performs a full call. - /// - protected virtual void InitializeBackgroundElements() => UpdateBackgroundElements(); - - /// - /// Called wen background elements require updates, usually due to a user changing a setting. - /// - /// - protected virtual void UpdateBackgroundElements() - { - if (!IsCurrentScreen) return; - - Background?.FadeTo(BackgroundOpacity, BACKGROUND_FADE_DURATION, Easing.OutQuint); - Background?.BlurTo(new Vector2((float)BlurLevel.Value * 25), BACKGROUND_FADE_DURATION, Easing.OutQuint); - } } } diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index e5a6dd2db1..38dd179f25 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework; @@ -9,13 +9,13 @@ using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Screens.Ranking; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -27,8 +27,7 @@ namespace osu.Game.Screens.Play { private readonly double startTime; - public IAdjustableClock AdjustableClock; - public IFrameBasedClock FramedClock; + public Action RequestSeek; private Button button; private Box remainingTimeBox; @@ -39,31 +38,32 @@ namespace osu.Game.Screens.Play public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; protected override bool BlockPositionalInput => false; + /// + /// Displays a skip overlay, giving the user the ability to skip forward. + /// + /// The time at which gameplay begins to appear. public SkipOverlay(double startTime) { this.startTime = startTime; - State = Visibility.Visible; + Show(); RelativePositionAxes = Axes.Both; - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; Position = new Vector2(0.5f, 0.7f); - Size = new Vector2(1, 0.14f); + Size = new Vector2(1, 100); Origin = Anchor.Centre; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { var baseClock = Clock; - if (FramedClock != null) - { - Clock = FramedClock; - ProcessCustomClock = false; - } + if (clock != null) + Clock = clock; Children = new Drawable[] { @@ -91,16 +91,21 @@ namespace osu.Game.Screens.Play }; } - private const double skip_required_cutoff = 3000; + /// + /// Duration before gameplay start time required before skip button displays. + /// + private const double skip_buffer = 1000; + private const double fade_time = 300; - private double beginFadeTime => startTime - skip_required_cutoff - fade_time; + private double beginFadeTime => startTime - fade_time; protected override void LoadComplete() { base.LoadComplete(); - if (startTime < skip_required_cutoff) + // skip is not required if there is no extra "empty" time to skip. + if (Clock.CurrentTime > beginFadeTime - skip_buffer) { Alpha = 0; Expire(); @@ -111,7 +116,7 @@ namespace osu.Game.Screens.Play using (BeginAbsoluteSequence(beginFadeTime)) this.FadeOut(fade_time); - button.Action = () => AdjustableClock?.Seek(startTime - skip_required_cutoff - fade_time); + button.Action = () => RequestSeek?.Invoke(beginFadeTime); displayTime = Time.Current; @@ -131,7 +136,7 @@ namespace osu.Game.Screens.Play protected override bool OnMouseMove(MouseMoveEvent e) { if (!e.HasAnyButtonPressed) - fadeContainer.State = Visibility.Visible; + fadeContainer.Show(); return base.OnMouseMove(e); } @@ -158,7 +163,7 @@ namespace osu.Game.Screens.Play public Visibility State { - get { return state; } + get => state; set { bool stateChanged = value != state; @@ -176,8 +181,9 @@ namespace osu.Game.Screens.Play if (!IsHovered && !IsDragged) using (BeginDelayedSequence(1000)) - scheduledHide = Schedule(() => State = Visibility.Hidden); + scheduledHide = Schedule(Hide); break; + case Visibility.Hidden: this.FadeOut(1000, Easing.OutExpo); break; @@ -190,7 +196,7 @@ namespace osu.Game.Screens.Play protected override void LoadComplete() { base.LoadComplete(); - State = Visibility.Visible; + Show(); } protected override bool OnMouseDown(MouseDownEvent e) @@ -201,9 +207,13 @@ namespace osu.Game.Screens.Play protected override bool OnMouseUp(MouseUpEvent e) { - State = Visibility.Visible; + Show(); return base.OnMouseUp(e); } + + public override void Hide() => State = Visibility.Hidden; + + public override void Show() => State = Visibility.Visible; } private class Button : OsuClickableContainer @@ -228,7 +238,7 @@ namespace osu.Game.Screens.Play colourNormal = colours.Yellow; colourHover = colours.YellowDark; - sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection"); + sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); Children = new Drawable[] { @@ -263,9 +273,9 @@ namespace osu.Game.Screens.Play Direction = FillDirection.Horizontal, Children = new[] { - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, - new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_right }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, + new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.Solid.ChevronRight }, } }, new OsuSpriteText @@ -273,8 +283,7 @@ namespace osu.Game.Screens.Play Anchor = Anchor.TopCentre, RelativePositionAxes = Axes.Y, Y = 0.7f, - TextSize = 12, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), Origin = Anchor.Centre, Text = @"SKIP", }, @@ -313,7 +322,7 @@ namespace osu.Game.Screens.Play protected override bool OnClick(ClickEvent e) { - if (!Enabled) + if (!Enabled.Value) return false; sampleConfirm.Play(); diff --git a/osu.Game/Screens/Play/SoloResults.cs b/osu.Game/Screens/Play/SoloResults.cs new file mode 100644 index 0000000000..2b9aec257c --- /dev/null +++ b/osu.Game/Screens/Play/SoloResults.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Types; + +namespace osu.Game.Screens.Play +{ + public class SoloResults : Results + { + public SoloResults(ScoreInfo score) + : base(score) + { + } + + protected override IEnumerable CreateResultPages() => new IResultPageInfo[] + { + new ScoreOverviewPageInfo(Score, Beatmap.Value), + new LocalLeaderboardPageInfo(Score, Beatmap.Value) + }; + } +} diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/SongProgress.cs index 336f9cc430..6642efdf8b 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/SongProgress.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -9,7 +9,7 @@ using System.Collections.Generic; using osu.Game.Graphics; using osu.Framework.Allocation; using System.Linq; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -29,14 +29,11 @@ namespace osu.Game.Screens.Play private readonly SongProgressGraph graph; private readonly SongProgressInfo info; - public Action OnSeek; + public Action RequestSeek; public override bool HandleNonPositionalInput => AllowSeeking; public override bool HandlePositionalInput => AllowSeeking; - private IClock audioClock; - public IClock AudioClock { set { audioClock = info.AudioClock = value; } } - private double lastHitTime => ((objects.Last() as IHasEndTime)?.EndTime ?? objects.Last().StartTime) + 1; private double firstHitTime => objects.First().StartTime; @@ -59,9 +56,16 @@ namespace osu.Game.Screens.Play private readonly BindableBool replayLoaded = new BindableBool(); - [BackgroundDependencyLoader] - private void load(OsuColour colours) + public IClock ReferenceClock; + + private IClock gameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) { + if (clock != null) + gameplayClock = clock; + graph.FillColour = bar.FillColour = colours.BlueLighter; } @@ -94,34 +98,30 @@ namespace osu.Game.Screens.Play { Alpha = 0, Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - OnSeek = position => OnSeek?.Invoke(position), + Origin = Anchor.BottomLeft, + OnSeek = time => RequestSeek?.Invoke(time), }, }; } protected override void LoadComplete() { - State = Visibility.Visible; + Show(); - replayLoaded.ValueChanged += v => AllowSeeking = v; + replayLoaded.ValueChanged += loaded => AllowSeeking = loaded.NewValue; replayLoaded.TriggerChange(); } - public void BindRulestContainer(RulesetContainer rulesetContainer) + public void BindDrawableRuleset(DrawableRuleset drawableRuleset) { - replayLoaded.BindTo(rulesetContainer.HasReplayLoaded); + replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); } private bool allowSeeking; public bool AllowSeeking { - get - { - return allowSeeking; - } - + get => allowSeeking; set { if (allowSeeking == value) return; @@ -157,14 +157,13 @@ namespace osu.Game.Screens.Play if (objects == null) return; - double position = audioClock?.CurrentTime ?? Time.Current; - double progress = (position - firstHitTime) / (lastHitTime - firstHitTime); + double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime; - if (progress < 1) - { - bar.CurrentTime = position; - graph.Progress = (int)(graph.ColumnCount * progress); - } + double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); + + bar.CurrentTime = gameplayTime; + graph.Progress = (int)(graph.ColumnCount * progress); } } } diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 1f0c4936a5..dd7b5826d5 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Framework.MathUtils; namespace osu.Game.Screens.Play { @@ -20,22 +21,22 @@ namespace osu.Game.Screens.Play public Color4 FillColour { - set { fill.Colour = value; } + set => fill.Colour = value; } public double StartTime { - set { CurrentNumber.MinValue = value; } + set => CurrentNumber.MinValue = value; } public double EndTime { - set { CurrentNumber.MaxValue = value; } + set => CurrentNumber.MaxValue = value; } public double CurrentTime { - set { CurrentNumber.Value = value; } + set => CurrentNumber.Value = value; } public SongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) @@ -107,11 +108,19 @@ namespace osu.Game.Screens.Play protected override void UpdateValue(float value) { - var xFill = value * UsableWidth; - fill.Width = xFill; - handleBase.X = xFill; + // handled in update } - protected override void OnUserChange() => OnSeek?.Invoke(Current); + protected override void Update() + { + base.Update(); + + float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, MathHelper.Clamp(Time.Elapsed / 40, 0, 1)); + + fill.Width = newX; + handleBase.X = newX; + } + + protected override void OnUserChange(double value) => OnSeek?.Invoke(value); } } diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/SongProgressGraph.cs index 6b83c82033..e480c5b502 100644 --- a/osu.Game/Screens/Play/SongProgressGraph.cs +++ b/osu.Game/Screens/Play/SongProgressGraph.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using System.Collections.Generic; diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/SongProgressInfo.cs index 3156a646db..7441c335d2 100644 --- a/osu.Game/Screens/Play/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/SongProgressInfo.cs @@ -1,10 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System; @@ -27,14 +26,24 @@ namespace osu.Game.Screens.Play private const int margin = 10; - public IClock AudioClock; - - public double StartTime { set { startTime = value; } } - public double EndTime { set { endTime = value; } } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) + public double StartTime { + set => startTime = value; + } + + public double EndTime + { + set => endTime = value; + } + + private GameplayClock gameplayClock; + + [BackgroundDependencyLoader(true)] + private void load(OsuColour colours, GameplayClock clock) + { + if (clock != null) + gameplayClock = clock; + Children = new Drawable[] { timeCurrent = new OsuSpriteText @@ -42,7 +51,7 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, Colour = colours.BlueLighter, - Font = @"Venera", + Font = OsuFont.Numeric, Margin = new MarginPadding { Left = margin, @@ -53,14 +62,14 @@ namespace osu.Game.Screens.Play Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, Colour = colours.BlueLighter, - Font = @"Venera", + Font = OsuFont.Numeric, }, timeLeft = new OsuSpriteText { Origin = Anchor.BottomRight, Anchor = Anchor.BottomRight, Colour = colours.BlueLighter, - Font = @"Venera", + Font = OsuFont.Numeric, Margin = new MarginPadding { Right = margin, @@ -73,7 +82,9 @@ namespace osu.Game.Screens.Play { base.Update(); - double songCurrentTime = AudioClock.CurrentTime - startTime; + var time = gameplayClock?.CurrentTime ?? Time.Current; + + double songCurrentTime = time - startTime; int currentPercent = Math.Max(0, Math.Min(100, (int)(songCurrentTime / songLength * 100))); int currentSecond = (int)Math.Floor(songCurrentTime / 1000.0); @@ -86,7 +97,7 @@ namespace osu.Game.Screens.Play if (currentSecond != previousSecond && songCurrentTime < songLength) { timeCurrent.Text = formatTime(TimeSpan.FromSeconds(currentSecond)); - timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - AudioClock.CurrentTime)); + timeLeft.Text = formatTime(TimeSpan.FromMilliseconds(endTime - time)); previousSecond = currentSecond; } diff --git a/osu.Game/Screens/Play/SquareGraph.cs b/osu.Game/Screens/Play/SquareGraph.cs index c0ee0079b9..5b7a9574b6 100644 --- a/osu.Game/Screens/Play/SquareGraph.cs +++ b/osu.Game/Screens/Play/SquareGraph.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Framework; using osu.Framework.Caching; using osu.Framework.Extensions.Color4Extensions; @@ -12,22 +13,26 @@ using osu.Framework.Graphics.Containers; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Allocation; +using osu.Framework.Threading; namespace osu.Game.Screens.Play { - public class SquareGraph : BufferedContainer + public class SquareGraph : Container { - private Column[] columns = { }; + private BufferedContainer columns; - public int ColumnCount => columns.Length; + public int ColumnCount => columns?.Children.Count ?? 0; private int progress; + public int Progress { - get { return progress; } + get => progress; set { if (value == progress) return; + progress = value; redrawProgress(); } @@ -36,36 +41,33 @@ namespace osu.Game.Screens.Play private float[] calculatedValues = { }; // values but adjusted to fit the amount of columns private int[] values; + public int[] Values { - get { return values; } + get => values; set { if (value == values) return; + values = value; layout.Invalidate(); } } private Color4 fillColour; + public Color4 FillColour { - get { return fillColour; } + get => fillColour; set { if (value == fillColour) return; + fillColour = value; redrawFilled(); } } - public SquareGraph() - { - CacheDrawnFrameBuffer = true; - } - - private Cached layout = new Cached(); - public override bool Invalidate(Invalidation invalidation = Invalidation.All, Drawable source = null, bool shallPropagate = true) { if ((invalidation & Invalidation.DrawSize) > 0) @@ -73,25 +75,70 @@ namespace osu.Game.Screens.Play return base.Invalidate(invalidation, source, shallPropagate); } + private Cached layout = new Cached(); + private ScheduledDelegate scheduledCreate; + protected override void Update() { base.Update(); - if (!layout.IsValid) + if (values != null && !layout.IsValid) { - recreateGraph(); + columns?.FadeOut(500, Easing.OutQuint).Expire(); + + scheduledCreate?.Cancel(); + scheduledCreate = Scheduler.AddDelayed(RecreateGraph, 500); + layout.Validate(); } } + private CancellationTokenSource cts; + + /// + /// Recreates the entire graph. + /// + protected virtual void RecreateGraph() + { + var newColumns = new BufferedContainer + { + CacheDrawnFrameBuffer = true, + RelativeSizeAxes = Axes.Both, + }; + + for (float x = 0; x < DrawWidth; x += Column.WIDTH) + { + newColumns.Add(new Column(DrawHeight) + { + LitColour = fillColour, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Position = new Vector2(x, 0), + State = ColumnState.Dimmed, + }); + } + + cts?.Cancel(); + + LoadComponentAsync(newColumns, c => + { + Child = columns = c; + columns.FadeInFromZero(500, Easing.OutQuint); + + recalculateValues(); + redrawFilled(); + redrawProgress(); + }, (cts = new CancellationTokenSource()).Token); + } + /// /// Redraws all the columns to match their lit/dimmed state. /// private void redrawProgress() { - for (int i = 0; i < columns.Length; i++) + for (int i = 0; i < ColumnCount; i++) columns[i].State = i <= progress ? ColumnState.Lit : ColumnState.Dimmed; - ForceRedraw(); + columns?.ForceRedraw(); } /// @@ -101,7 +148,7 @@ namespace osu.Game.Screens.Play { for (int i = 0; i < ColumnCount; i++) columns[i].Filled = calculatedValues.ElementAtOrDefault(i); - ForceRedraw(); + columns?.ForceRedraw(); } /// @@ -122,6 +169,7 @@ namespace osu.Game.Screens.Play var max = values.Max(); float step = values.Length / (float)ColumnCount; + for (float i = 0; i < values.Length; i += step) { newValues.Add((float)values[(int)i] / max); @@ -130,34 +178,6 @@ namespace osu.Game.Screens.Play calculatedValues = newValues.ToArray(); } - /// - /// Recreates the entire graph. - /// - private void recreateGraph() - { - var newColumns = new List(); - - for (float x = 0; x < DrawWidth; x += Column.WIDTH) - { - newColumns.Add(new Column - { - LitColour = fillColour, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Height = DrawHeight, - Position = new Vector2(x, 0), - State = ColumnState.Dimmed, - }); - } - - columns = newColumns.ToArray(); - Children = columns; - - recalculateValues(); - redrawFilled(); - redrawProgress(); - } - public class Column : Container, IStateful { protected readonly Color4 EmptyColour = Color4.White.Opacity(20); @@ -174,14 +194,15 @@ namespace osu.Game.Screens.Play private readonly List drawableRows = new List(); private float filled; + public float Filled { - get { return filled; } + get => filled; set { if (value == filled) return; - filled = value; + filled = value; fillActive(); } } @@ -190,12 +211,12 @@ namespace osu.Game.Screens.Play public ColumnState State { - get { return state; } + get => state; set { if (value == state) return; - state = value; + state = value; if (IsLoaded) fillActive(); @@ -203,27 +224,30 @@ namespace osu.Game.Screens.Play } } - public Column() + public Column(float height) { Width = WIDTH; + Height = height; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - for (int r = 0; r < cubeCount; r++) + drawableRows.AddRange(Enumerable.Range(0, (int)cubeCount).Select(r => new Box { - drawableRows.Add(new Box - { - Size = new Vector2(cube_size), - Position = new Vector2(0, r * WIDTH + padding), - }); - } + Size = new Vector2(cube_size), + Position = new Vector2(0, r * WIDTH + padding), + })); Children = drawableRows; // Reverse drawableRows so when iterating through them they start at the bottom drawableRows.Reverse(); + } + protected override void LoadComplete() + { + base.LoadComplete(); fillActive(); } diff --git a/osu.Game/Screens/Ranking/AspectContainer.cs b/osu.Game/Screens/Ranking/AspectContainer.cs index 48b40e7ebe..84329d2a04 100644 --- a/osu.Game/Screens/Ranking/AspectContainer.cs +++ b/osu.Game/Screens/Ranking/AspectContainer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Screens/Ranking/IResultPageInfo.cs b/osu.Game/Screens/Ranking/IResultPageInfo.cs new file mode 100644 index 0000000000..cc86e7441a --- /dev/null +++ b/osu.Game/Screens/Ranking/IResultPageInfo.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Screens.Ranking +{ + public interface IResultPageInfo + { + IconUsage Icon { get; } + + string Name { get; } + + ResultsPage CreatePage(); + } +} diff --git a/osu.Game/Screens/Ranking/ResultsPageRanking.cs b/osu.Game/Screens/Ranking/Pages/LocalLeaderboardPage.cs similarity index 63% rename from osu.Game/Screens/Ranking/ResultsPageRanking.cs rename to osu.Game/Screens/Ranking/Pages/LocalLeaderboardPage.cs index 3509d536b2..c997dd6d30 100644 --- a/osu.Game/Screens/Ranking/ResultsPageRanking.cs +++ b/osu.Game/Screens/Ranking/Pages/LocalLeaderboardPage.cs @@ -1,20 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osuTK; -using osu.Framework.Graphics.Shapes; -namespace osu.Game.Screens.Ranking +namespace osu.Game.Screens.Ranking.Pages { - public class ResultsPageRanking : ResultsPage + public class LocalLeaderboardPage : ResultsPage { - public ResultsPageRanking(Score score, WorkingBeatmap beatmap = null) : base(score, beatmap) + public LocalLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap = null) + : base(score, beatmap) { } @@ -25,10 +26,10 @@ namespace osu.Game.Screens.Ranking { new Box { - Colour = colours.GrayE, + Colour = colours.Gray6, RelativeSizeAxes = Axes.Both, }, - new Leaderboard + new BeatmapLeaderboard { Origin = Anchor.Centre, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs similarity index 89% rename from osu.Game/Screens/Ranking/ResultsPageScore.cs rename to osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 7f152aaf14..a82156e34e 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -1,38 +1,42 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Leaderboards; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Screens.Play; -using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Extensions; -using osu.Framework.Localisation; +using osuTK; +using osuTK.Graphics; -namespace osu.Game.Screens.Ranking +namespace osu.Game.Screens.Ranking.Pages { - public class ResultsPageScore : ResultsPage + public class ScoreResultsPage : ResultsPage { private Container scoreContainer; private ScoreCounter scoreCounter; - public ResultsPageScore(Score score, WorkingBeatmap beatmap) : base(score, beatmap) { } + public ScoreResultsPage(ScoreInfo score, WorkingBeatmap beatmap) + : base(score, beatmap) + { + } private FillFlowContainer statisticsContainer; @@ -70,7 +74,7 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.X, Height = user_header_height, }, - new DrawableRank(Score.Rank) + new UpdateableRank(Score.Rank) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -107,8 +111,7 @@ namespace osu.Game.Screens.Ranking Origin = Anchor.TopCentre, Colour = colours.PinkDarker, Shadow = false, - Font = @"Exo2.0-Bold", - TextSize = 16, + Font = OsuFont.GetFont(weight: FontWeight.Bold), Text = "total score", Margin = new MarginPadding { Bottom = 15 }, }, @@ -177,6 +180,7 @@ namespace osu.Game.Screens.Ranking scoreCounter.Increment(Score.TotalScore); int delay = 0; + foreach (var s in statisticsContainer.Children) { s.FadeOut() @@ -195,9 +199,9 @@ namespace osu.Game.Screens.Ranking private class DrawableScoreStatistic : Container { - private readonly KeyValuePair statistic; + private readonly KeyValuePair statistic; - public DrawableScoreStatistic(KeyValuePair statistic) + public DrawableScoreStatistic(KeyValuePair statistic) { this.statistic = statistic; @@ -210,17 +214,19 @@ namespace osu.Game.Screens.Ranking { Children = new Drawable[] { - new OsuSpriteText { + new OsuSpriteText + { Text = statistic.Value.ToString().PadLeft(4, '0'), Colour = colours.Gray7, - TextSize = 30, + Font = OsuFont.GetFont(size: 30), Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - new OsuSpriteText { + new OsuSpriteText + { Text = statistic.Key.GetDescription(), Colour = colours.Gray7, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), Y = 26, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, @@ -303,24 +309,21 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Shadow = false, - TextSize = 24, - Font = @"Exo2.0-BoldItalic", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 24, italics: true), }, artist = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Shadow = false, - TextSize = 20, - Font = @"Exo2.0-BoldItalic", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 20, italics: true), }, versionMapper = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Shadow = false, - TextSize = 16, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), }, } } @@ -334,7 +337,9 @@ namespace osu.Game.Screens.Ranking versionMapper.Colour = colours.Gray8; var creator = beatmap.Metadata.Author?.Username; - if (!string.IsNullOrEmpty(creator)) { + + if (!string.IsNullOrEmpty(creator)) + { versionMapper.Text = $"mapped by {creator}"; if (!string.IsNullOrEmpty(beatmap.Version)) @@ -365,11 +370,10 @@ namespace osu.Game.Screens.Ranking }, new OsuSpriteText { - Font = @"Exo2.0-RegularItalic", - Text = user.Username, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - TextSize = 30, + Text = user.Username, + Font = OsuFont.GetFont(size: 30, weight: FontWeight.Regular, italics: true), Padding = new MarginPadding { Bottom = 10 }, } }; @@ -389,10 +393,11 @@ namespace osu.Game.Screens.Ranking protected override Easing RollingEasing => Easing.OutPow10; - public SlowScoreCounter(uint leading = 0) : base(leading) + public SlowScoreCounter(uint leading = 0) + : base(leading) { DisplayedCountSpriteText.Shadow = false; - DisplayedCountSpriteText.Font = @"Venera-Light"; + DisplayedCountSpriteText.Font = DisplayedCountSpriteText.Font.With(Typeface.Venera, weight: FontWeight.Light); UseCommaSeparator = true; } } diff --git a/osu.Game/Screens/Ranking/ResultMode.cs b/osu.Game/Screens/Ranking/ResultMode.cs deleted file mode 100644 index 4742864b83..0000000000 --- a/osu.Game/Screens/Ranking/ResultMode.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Ranking -{ - public enum ResultMode - { - Summary, - Ranking, - Share - } -} diff --git a/osu.Game/Screens/Ranking/ResultModeButton.cs b/osu.Game/Screens/Ranking/ResultModeButton.cs index 5df3ff3afb..1383511241 100644 --- a/osu.Game/Screens/Ranking/ResultModeButton.cs +++ b/osu.Game/Screens/Ranking/ResultModeButton.cs @@ -1,39 +1,33 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens.Ranking { - public class ResultModeButton : TabItem + public class ResultModeButton : TabItem, IHasTooltip { - private readonly FontAwesome icon; + private readonly IconUsage icon; private Color4 activeColour; private Color4 inactiveColour; private CircularContainer colouredPart; - public ResultModeButton(ResultMode mode) : base(mode) + public ResultModeButton(IResultPageInfo mode) + : base(mode) { - switch (mode) - { - case ResultMode.Summary: - icon = FontAwesome.fa_asterisk; - break; - case ResultMode.Ranking: - icon = FontAwesome.fa_list; - break; - case ResultMode.Share: - icon = FontAwesome.fa_camera; - break; - } + icon = mode.Icon; + TooltipText = mode.Name; } [BackgroundDependencyLoader] @@ -95,5 +89,7 @@ namespace osu.Game.Screens.Ranking protected override void OnActivated() => colouredPart.FadeColour(activeColour, 200, Easing.OutQuint); protected override void OnDeactivated() => colouredPart.FadeColour(inactiveColour, 200, Easing.OutQuint); + + public string TooltipText { get; private set; } } } diff --git a/osu.Game/Screens/Ranking/ResultModeTabControl.cs b/osu.Game/Screens/Ranking/ResultModeTabControl.cs index e5c6115085..b0d94a4be6 100644 --- a/osu.Game/Screens/Ranking/ResultModeTabControl.cs +++ b/osu.Game/Screens/Ranking/ResultModeTabControl.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; @@ -7,7 +7,7 @@ using osuTK; namespace osu.Game.Screens.Ranking { - public class ResultModeTabControl : TabControl + public class ResultModeTabControl : TabControl { public ResultModeTabControl() { @@ -19,9 +19,9 @@ namespace osu.Game.Screens.Ranking TabContainer.Padding = new MarginPadding(5); } - protected override Dropdown CreateDropdown() => null; + protected override Dropdown CreateDropdown() => null; - protected override TabItem CreateTabItem(ResultMode value) => new ResultModeButton(value) + protected override TabItem CreateTabItem(IResultPageInfo value) => new ResultModeButton(value) { Anchor = TabContainer.Anchor, Origin = TabContainer.Origin diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 57bb229517..f53fb75e1a 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -1,13 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Scoring; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Graphics.Containers; @@ -15,15 +16,16 @@ using osu.Game.Screens.Backgrounds; using osuTK; using osuTK.Graphics; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; +using osu.Game.Scoring; namespace osu.Game.Screens.Ranking { - public class Results : OsuScreen + public abstract class Results : OsuScreen { - private readonly Score score; + protected const float BACKGROUND_BLUR = 20; + private Container circleOuterBackground; private Container circleOuter; private Container circleInner; @@ -32,31 +34,31 @@ namespace osu.Game.Screens.Ranking private ResultModeTabControl modeChangeButtons; - public override bool AllowBeatmapRulesetChange => false; + public override bool DisallowExternalBeatmapRulesetChanges => true; + + protected readonly ScoreInfo Score; private Container currentPage; - private static readonly Vector2 background_blur = new Vector2(20); - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); private const float overscan = 1.3f; private const float circle_outer_scale = 0.96f; - public Results(Score score) + protected Results(ScoreInfo score) { - this.score = score; + Score = score; } private const float transition_time = 800; private IEnumerable allCircles => new Drawable[] { circleOuterBackground, circleInner, circleOuter }; - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - (Background as BackgroundScreenBeatmap)?.BlurTo(background_blur, 2500, Easing.OutQuint); + ((BackgroundScreenBeatmap)Background).BlurAmount.Value = BACKGROUND_BLUR; Background.ScaleTo(1.1f, transition_time, Easing.OutQuint); allCircles.ForEach(c => @@ -67,7 +69,7 @@ namespace osu.Game.Screens.Ranking backgroundParallax.FadeOut(); modeChangeButtons.FadeOut(); - currentPage.FadeOut(); + currentPage?.FadeOut(); circleOuterBackground .FadeIn(transition_time, Easing.OutQuint) @@ -90,13 +92,13 @@ namespace osu.Game.Screens.Ranking using (BeginDelayedSequence(transition_time * 0.4f, true)) { modeChangeButtons.FadeIn(transition_time, Easing.OutQuint); - currentPage.FadeIn(transition_time, Easing.OutQuint); + currentPage?.FadeIn(transition_time, Easing.OutQuint); } } } } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { allCircles.ForEach(c => { @@ -105,7 +107,7 @@ namespace osu.Game.Screens.Ranking Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint); - Content.FadeOut(transition_time / 4); + this.FadeOut(transition_time / 4); return base.OnExiting(next); } @@ -113,7 +115,7 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load(OsuColour colours) { - Children = new Drawable[] + InternalChildren = new Drawable[] { new AspectContainer { @@ -188,45 +190,43 @@ namespace osu.Game.Screens.Ranking }, new OsuSpriteText { - Text = $"{score.MaxCombo}x", - TextSize = 40, - RelativePositionAxes = Axes.X, - Font = @"Exo2.0-Bold", - X = 0.1f, - Colour = colours.BlueDarker, Anchor = Anchor.CentreLeft, Origin = Anchor.BottomCentre, + Text = $"{Score.MaxCombo}x", + RelativePositionAxes = Axes.X, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), + X = 0.1f, + Colour = colours.BlueDarker, }, new OsuSpriteText { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, Text = "max combo", - TextSize = 20, + Font = OsuFont.GetFont(size: 20), RelativePositionAxes = Axes.X, X = 0.1f, Colour = colours.Gray6, - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopCentre, }, new OsuSpriteText { - Text = $"{score.Accuracy:P2}", - TextSize = 40, - RelativePositionAxes = Axes.X, - Font = @"Exo2.0-Bold", - X = 0.9f, - Colour = colours.BlueDarker, Anchor = Anchor.CentreLeft, Origin = Anchor.BottomCentre, + Text = $"{Score.Accuracy:P2}", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), + RelativePositionAxes = Axes.X, + X = 0.9f, + Colour = colours.BlueDarker, }, new OsuSpriteText { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, Text = "accuracy", - TextSize = 20, + Font = OsuFont.GetFont(size: 20), RelativePositionAxes = Axes.X, X = 0.9f, Colour = colours.Gray6, - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopCentre, }, } }, @@ -253,39 +253,25 @@ namespace osu.Game.Screens.Ranking } } } - }, - new BackButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Action = Exit - }, + } }; - modeChangeButtons.AddItem(ResultMode.Summary); - modeChangeButtons.AddItem(ResultMode.Ranking); - //modeChangeButtons.AddItem(ResultMode.Share); + foreach (var t in CreateResultPages()) + modeChangeButtons.AddItem(t); + modeChangeButtons.Current.Value = modeChangeButtons.Items.FirstOrDefault(); - modeChangeButtons.Current.ValueChanged += mode => + modeChangeButtons.Current.BindValueChanged(page => { currentPage?.FadeOut(); currentPage?.Expire(); - switch (mode) - { - case ResultMode.Summary: - currentPage = new ResultsPageScore(score, Beatmap.Value); - break; - case ResultMode.Ranking: - currentPage = new ResultsPageRanking(score, Beatmap.Value); - break; - } + currentPage = page.NewValue?.CreatePage(); if (currentPage != null) - circleInner.Add(currentPage); - }; - - modeChangeButtons.Current.TriggerChange(); + LoadComponentAsync(currentPage, circleInner.Add); + }, true); } + + protected abstract IEnumerable CreateResultPages(); } } diff --git a/osu.Game/Screens/Ranking/ResultsPage.cs b/osu.Game/Screens/Ranking/ResultsPage.cs index f73bcba553..8776c599dd 100644 --- a/osu.Game/Screens/Ranking/ResultsPage.cs +++ b/osu.Game/Screens/Ranking/ResultsPage.cs @@ -1,29 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; namespace osu.Game.Screens.Ranking { - public class ResultsPage : Container + public abstract class ResultsPage : Container { - protected readonly Score Score; + protected readonly ScoreInfo Score; protected readonly WorkingBeatmap Beatmap; private CircularContainer content; private Box fill; protected override Container Content => content; - public ResultsPage(Score score, WorkingBeatmap beatmap) + protected ResultsPage(ScoreInfo score, WorkingBeatmap beatmap) { Score = score; Beatmap = beatmap; diff --git a/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs new file mode 100644 index 0000000000..fe183c5f89 --- /dev/null +++ b/osu.Game/Screens/Ranking/Types/LocalLeaderboardPageInfo.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Pages; + +namespace osu.Game.Screens.Ranking.Types +{ + public class LocalLeaderboardPageInfo : IResultPageInfo + { + private readonly ScoreInfo score; + private readonly WorkingBeatmap beatmap; + + public LocalLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap) + { + this.score = score; + this.beatmap = beatmap; + } + + public IconUsage Icon => FontAwesome.Solid.User; + + public string Name => @"Local Leaderboard"; + + public ResultsPage CreatePage() => new LocalLeaderboardPage(score, beatmap); + } +} diff --git a/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs new file mode 100644 index 0000000000..424dbff6f6 --- /dev/null +++ b/osu.Game/Screens/Ranking/Types/ScoreOverviewPageInfo.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Pages; + +namespace osu.Game.Screens.Ranking.Types +{ + public class ScoreOverviewPageInfo : IResultPageInfo + { + public IconUsage Icon => FontAwesome.Solid.Asterisk; + + public string Name => "Overview"; + private readonly ScoreInfo score; + private readonly WorkingBeatmap beatmap; + + public ScoreOverviewPageInfo(ScoreInfo score, WorkingBeatmap beatmap) + { + this.score = score; + this.beatmap = beatmap; + } + + public ResultsPage CreatePage() + { + return new ScoreResultsPage(score, beatmap); + } + } +} diff --git a/osu.Game/Screens/ScreenWhiteBox.cs b/osu.Game/Screens/ScreenWhiteBox.cs index 1131152d5f..5648dd997b 100644 --- a/osu.Game/Screens/ScreenWhiteBox.cs +++ b/osu.Game/Screens/ScreenWhiteBox.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -14,13 +14,12 @@ using osuTK.Graphics; using osu.Game.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Screens { public class ScreenWhiteBox : OsuScreen { - private readonly BackButton popButton; - private const double transition_time = 1000; protected virtual IEnumerable PossibleChildren => null; @@ -30,59 +29,55 @@ namespace osu.Game.Screens protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2"); - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - //only show the pop button if we are entered form another screen. - if (last != null) - popButton.Alpha = 1; - - Content.Alpha = 0; + Alpha = 0; textContainer.Position = new Vector2(DrawSize.X / 16, 0); boxContainer.ScaleTo(0.2f); boxContainer.RotateTo(-20); - using (Content.BeginDelayedSequence(300, true)) + using (BeginDelayedSequence(300, true)) { boxContainer.ScaleTo(1, transition_time, Easing.OutElastic); boxContainer.RotateTo(0, transition_time / 2, Easing.OutQuint); textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); - Content.FadeIn(transition_time, Easing.OutExpo); + this.FadeIn(transition_time, Easing.OutExpo); } } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { textContainer.MoveTo(new Vector2(DrawSize.X / 16, 0), transition_time, Easing.OutExpo); - Content.FadeOut(transition_time, Easing.OutExpo); + this.FadeOut(transition_time, Easing.OutExpo); return base.OnExiting(next); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { base.OnSuspending(next); textContainer.MoveTo(new Vector2(-(DrawSize.X / 16), 0), transition_time, Easing.OutExpo); - Content.FadeOut(transition_time, Easing.OutExpo); + this.FadeOut(transition_time, Easing.OutExpo); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { base.OnResuming(last); textContainer.MoveTo(Vector2.Zero, transition_time, Easing.OutExpo); - Content.FadeIn(transition_time, Easing.OutExpo); + this.FadeIn(transition_time, Easing.OutExpo); } public ScreenWhiteBox() { FillFlowContainer childModeButtons; - Children = new Drawable[] + InternalChildren = new Drawable[] { boxContainer = new Container { @@ -112,44 +107,37 @@ namespace osu.Game.Screens { new SpriteIcon { - Icon = FontAwesome.fa_universal_access, + Icon = FontAwesome.Solid.UniversalAccess, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Size = new Vector2(50), }, new OsuSpriteText { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Text = GetType().Name, Colour = getColourFor(GetType()).Lighten(0.8f), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - TextSize = 50, + Font = OsuFont.GetFont(size: 50), }, new OsuSpriteText { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Text = "is not yet ready for use!", - TextSize = 20, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + Font = OsuFont.GetFont(size: 20), }, new OsuSpriteText { - Text = "please check back a bit later.", - TextSize = 14, Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, + Text = "please check back a bit later.", + Font = OsuFont.GetFont(size: 14), }, } }, } }, - popButton = new BackButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Alpha = 0, - Action = Exit - }, childModeButtons = new FillFlowContainer { Direction = FillDirection.Vertical, @@ -169,10 +157,7 @@ namespace osu.Game.Screens Text = $@"{t.Name}", BackgroundColour = getColourFor(t), HoverColour = getColourFor(t).Lighten(0.2f), - Action = delegate - { - Push(Activator.CreateInstance(t) as Screen); - } + Action = delegate { this.Push(Activator.CreateInstance(t) as Screen); } }); } } @@ -191,7 +176,7 @@ namespace osu.Game.Screens { public ChildModeButton() { - Icon = FontAwesome.fa_osu_right_o; + Icon = OsuIcon.RightCircle; Anchor = Anchor.BottomRight; Origin = Anchor.BottomRight; } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 348394c1f5..a6fbb1ff94 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -13,9 +13,9 @@ using osu.Framework.MathUtils; using System.Diagnostics; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Caching; using osu.Framework.Threading; -using osu.Framework.Configuration; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select /// public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap; - private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State == CarouselItemState.Selected); + private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected); /// /// The currently selected beatmap set. @@ -55,9 +55,9 @@ namespace osu.Game.Screens.Select public override bool HandlePositionalInput => AllowSelection; /// - /// Used to avoid firing null selections before the initial beatmaps have been loaded via . + /// Whether carousel items have completed asynchronously loaded. /// - private bool initialLoadComplete; + public bool BeatmapSetsLoaded { get; private set; } private IEnumerable beatmapSets => root.Children.OfType(); @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select Schedule(() => { BeatmapSetsChanged?.Invoke(); - initialLoadComplete = true; + BeatmapSetsLoaded = true; }); })); } @@ -101,7 +101,6 @@ namespace osu.Game.Screens.Select private readonly Container scrollableContent; - public Bindable RightClickScrollingEnabled = new Bindable(); public Bindable RandomAlgorithm = new Bindable(); @@ -131,7 +130,7 @@ namespace osu.Game.Screens.Select config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm); config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled); - RightClickScrollingEnabled.ValueChanged += v => RightMouseScrollbar = v; + RightClickScrollingEnabled.ValueChanged += enabled => RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); } @@ -149,37 +148,37 @@ namespace osu.Game.Screens.Select }); } - public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) + public void UpdateBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { - Schedule(() => + int? previouslySelectedID = null; + CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); + + // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required + if (existingSet?.State?.Value == CarouselItemState.Selected) + previouslySelectedID = selectedBeatmap?.Beatmap.ID; + + var newSet = createCarouselSet(beatmapSet); + + if (existingSet != null) + root.RemoveChild(existingSet); + + if (newSet == null) { - CarouselBeatmapSet existingSet = beatmapSets.FirstOrDefault(b => b.BeatmapSet.ID == beatmapSet.ID); - - bool hadSelection = existingSet?.State?.Value == CarouselItemState.Selected; - - var newSet = createCarouselSet(beatmapSet); - - if (existingSet != null) - root.RemoveChild(existingSet); - - if (newSet == null) - { - itemsCache.Invalidate(); - return; - } - - root.AddChild(newSet); - - applyActiveCriteria(false, false); - - //check if we can/need to maintain our current selection. - if (hadSelection) - select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == selectedBeatmap?.Beatmap.ID) ?? newSet); - itemsCache.Invalidate(); - Schedule(() => BeatmapSetsChanged?.Invoke()); - }); - } + return; + } + + root.AddChild(newSet); + + applyActiveCriteria(false, false); + + //check if we can/need to maintain our current selection. + if (previouslySelectedID != null) + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == previouslySelectedID) ?? newSet); + + itemsCache.Invalidate(); + Schedule(() => BeatmapSetsChanged?.Invoke()); + }); /// /// Selects a given beatmap on the carousel. @@ -197,7 +196,7 @@ namespace osu.Game.Screens.Select foreach (CarouselBeatmapSet set in beatmapSets) { - if (!bypassFilters && set.Filtered) + if (!bypassFilters && set.Filtered.Value) continue; var item = set.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); @@ -206,9 +205,9 @@ namespace osu.Game.Screens.Select // The beatmap that needs to be selected doesn't exist in this set continue; - if (!bypassFilters && item.Filtered) + if (!bypassFilters && item.Filtered.Value) // The beatmap exists in this set but is filtered, so look for the first unfiltered map in the set - item = set.Beatmaps.FirstOrDefault(b => !b.Filtered); + item = set.Beatmaps.FirstOrDefault(b => !b.Filtered.Value); if (item != null) { @@ -227,7 +226,7 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - var visibleItems = Items.Where(s => !s.Item.Filtered).ToList(); + var visibleItems = Items.Where(s => !s.Item.Filtered.Value).ToList(); if (!visibleItems.Any()) return; @@ -249,19 +248,21 @@ namespace osu.Game.Screens.Select { var item = visibleItems[currentIndex].Item; - if (item.Filtered || item.State == CarouselItemState.Selected) continue; + if (item.Filtered.Value || item.State.Value == CarouselItemState.Selected) continue; switch (item) { case CarouselBeatmap beatmap: if (skipDifficulties) continue; + select(beatmap); return; + case CarouselBeatmapSet set: if (skipDifficulties) select(set); else - select(direction > 0 ? set.Beatmaps.First(b => !b.Filtered) : set.Beatmaps.Last(b => !b.Filtered)); + select(direction > 0 ? set.Beatmaps.First(b => !b.Filtered.Value) : set.Beatmaps.Last(b => !b.Filtered.Value)); return; } } @@ -273,7 +274,7 @@ namespace osu.Game.Screens.Select /// True if a selection could be made, else False. public bool SelectNextRandom() { - var visibleSets = beatmapSets.Where(s => !s.Filtered).ToList(); + var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); if (!visibleSets.Any()) return false; @@ -289,9 +290,10 @@ namespace osu.Game.Screens.Select CarouselBeatmapSet set; - if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) + if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) { var notYetVisitedSets = visibleSets.Except(previouslyVisitedRandomSets).ToList(); + if (!notYetVisitedSets.Any()) { previouslyVisitedRandomSets.RemoveAll(s => visibleSets.Contains(s)); @@ -304,7 +306,7 @@ namespace osu.Game.Screens.Select else set = visibleSets.ElementAt(RNG.Next(visibleSets.Count)); - var visibleBeatmaps = set.Beatmaps.Where(s => !s.Filtered).ToList(); + var visibleBeatmaps = set.Beatmaps.Where(s => !s.Filtered.Value).ToList(); select(visibleBeatmaps[RNG.Next(visibleBeatmaps.Count)]); return true; } @@ -315,9 +317,9 @@ namespace osu.Game.Screens.Select { var beatmap = randomSelectedBeatmaps.Pop(); - if (!beatmap.Filtered) + if (!beatmap.Filtered.Value) { - if (RandomAlgorithm == RandomSelectAlgorithm.RandomPermutation) + if (RandomAlgorithm.Value == RandomSelectAlgorithm.RandomPermutation) previouslyVisitedRandomSets.Remove(selectedBeatmapSet); select(beatmap); break; @@ -327,7 +329,11 @@ namespace osu.Game.Screens.Select private void select(CarouselItem item) { + if (!AllowSelection) + return; + if (item == null) return; + item.State.Value = CarouselItemState.Selected; } @@ -390,13 +396,16 @@ namespace osu.Game.Screens.Select case Key.Up: direction = -1; break; + case Key.Down: direction = 1; break; + case Key.Left: direction = -1; skipDifficulties = true; break; + case Key.Right: direction = 1; skipDifficulties = true; @@ -461,8 +470,10 @@ namespace osu.Game.Screens.Select case LoadState.NotLoaded: LoadComponentAsync(item); break; + case LoadState.Loading: break; + default: scrollableContent.Add(item); break; @@ -510,9 +521,9 @@ namespace osu.Game.Screens.Select foreach (var c in set.Beatmaps) { - c.State.ValueChanged += v => + c.State.ValueChanged += state => { - if (v == CarouselItemState.Selected) + if (state.NewValue == CarouselItemState.Selected) { selectedBeatmapSet = set; SelectionChanged?.Invoke(c.Beatmap); @@ -550,9 +561,10 @@ namespace osu.Game.Screens.Select case DrawableCarouselBeatmapSet set: lastSet = set; - set.MoveToX(set.Item.State == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); + set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo); set.MoveToY(currentY, 750, Easing.OutExpo); break; + case DrawableCarouselBeatmap beatmap: if (beatmap.Item.State.Value == CarouselItemState.Selected) scrollTarget = currentY + beatmap.DrawHeight / 2 - DrawHeight / 2; @@ -560,7 +572,7 @@ namespace osu.Game.Screens.Select void performMove(float y, float? startY = null) { if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value)); - beatmap.MoveToX(beatmap.Item.State == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); + beatmap.MoveToX(beatmap.Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo); beatmap.MoveToY(y, 750, Easing.OutExpo); } @@ -576,7 +588,7 @@ namespace osu.Game.Screens.Select else { float y = currentY; - d.OnLoadComplete = _ => performMove(y, setY); + d.OnLoadComplete += _ => performMove(y, setY); } break; @@ -592,7 +604,7 @@ namespace osu.Game.Screens.Select currentY += DrawHeight / 2; scrollableContent.Height = currentY; - if (initialLoadComplete && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) + if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected)) { selectedBeatmapSet = null; SelectionChanged?.Invoke(null); diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs new file mode 100644 index 0000000000..c9b6ca7bb3 --- /dev/null +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Overlays.Dialog; +using osu.Game.Scoring; +using System; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Graphics.Sprites; + +namespace osu.Game.Screens.Select +{ + public class BeatmapClearScoresDialog : PopupDialog + { + private ScoreManager scoreManager; + + public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion) + { + BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; + Icon = FontAwesome.Solid.Eraser; + HeaderText = @"Clearing all local scores. Are you sure?"; + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Yes. Please.", + Action = () => + { + Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == beatmap.ID).ToList())) + .ContinueWith(_ => onCompletion); + } + }, + new PopupDialogCancelButton + { + Text = @"No, I'm still attached.", + }, + }; + } + + [BackgroundDependencyLoader] + private void load(ScoreManager scoreManager) + { + this.scoreManager = scoreManager; + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index bf7ed73f39..5fb72e4151 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; - Icon = FontAwesome.fa_trash_o; + Icon = FontAwesome.Regular.TrashAlt; HeaderText = @"Confirm deletion of"; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index a6fbd201d0..477037355c 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; @@ -17,15 +17,13 @@ namespace osu.Game.Screens.Select protected override Container Content => content; public readonly BeatmapDetails Details; - public readonly Leaderboard Leaderboard; + public readonly BeatmapLeaderboard Leaderboard; private WorkingBeatmap beatmap; + public WorkingBeatmap Beatmap { - get - { - return beatmap; - } + get => beatmap; set { beatmap = value; @@ -52,7 +50,7 @@ namespace osu.Game.Screens.Select default: Details.Hide(); - Leaderboard.Scope = (LeaderboardScope)tab - 1; + Leaderboard.Scope = (BeatmapLeaderboardScope)tab - 1; Leaderboard.Show(); break; } @@ -73,7 +71,7 @@ namespace osu.Game.Screens.Select Alpha = 0, Margin = new MarginPadding { Top = details_padding }, }, - Leaderboard = new Leaderboard + Leaderboard = new BeatmapLeaderboard { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs index e18b70a0e0..f66cd2b29a 100644 --- a/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs +++ b/osu.Game/Screens/Select/BeatmapDetailAreaTabControl.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Select private void invokeOnFilter() { - OnFilter?.Invoke(tabs.Current, modsCheckbox.Current); + OnFilter?.Invoke(tabs.Current.Value, modsCheckbox.Current.Value); } [BackgroundDependencyLoader] @@ -69,8 +69,8 @@ namespace osu.Game.Screens.Select }, }; - tabs.Current.ValueChanged += item => invokeOnFilter(); - modsCheckbox.Current.ValueChanged += item => invokeOnFilter(); + tabs.Current.ValueChanged += _ => invokeOnFilter(); + modsCheckbox.Current.ValueChanged += _ => invokeOnFilter(); } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 9b87bbaa7b..23dd87d8ea 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -10,13 +10,15 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using System.Linq; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Framework.Threading; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Screens.Select.Details; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; namespace osu.Game.Screens.Select { @@ -29,23 +31,28 @@ namespace osu.Game.Screens.Select private readonly AdvancedStats advanced; private readonly DetailBox ratingsContainer; private readonly UserRatings ratings; - private readonly ScrollContainer metadataScroll; + private readonly OsuScrollContainer metadataScroll; private readonly MetadataSection description, source, tags; private readonly Container failRetryContainer; private readonly FailRetryGraph failRetryGraph; private readonly DimmedLoadingAnimation loading; - private APIAccess api; + private IAPIProvider api; private ScheduledDelegate pendingBeatmapSwitch; + [Resolved] + private RulesetStore rulesets { get; set; } + private BeatmapInfo beatmap; + public BeatmapInfo Beatmap { - get { return beatmap; } + get => beatmap; set { if (value == beatmap) return; + beatmap = value; pendingBeatmapSwitch?.Cancel(); @@ -104,7 +111,7 @@ namespace osu.Game.Screens.Select }, }, }, - metadataScroll = new ScrollContainer + metadataScroll = new OsuScrollContainer { RelativeSizeAxes = Axes.X, Width = 0.5f, @@ -115,6 +122,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, LayoutDuration = transition_duration, + LayoutEasing = Easing.OutQuad, Spacing = new Vector2(spacing * 2), Margin = new MarginPadding { Top = spacing * 2 }, Children = new[] @@ -137,8 +145,7 @@ namespace osu.Game.Screens.Select new OsuSpriteText { Text = "Points of Failure", - Font = @"Exo2.0-Bold", - TextSize = 14, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), }, failRetryGraph = new FailRetryGraph { @@ -157,7 +164,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(APIAccess api) + private void load(IAPIProvider api) { this.api = api; } @@ -172,104 +179,98 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - if (Beatmap == null) + advanced.Beatmap = Beatmap; + description.Text = Beatmap?.Version; + source.Text = Beatmap?.Metadata?.Source; + tags.Text = Beatmap?.Metadata?.Tags; + + // metrics may have been previously fetched + if (Beatmap?.BeatmapSet?.Metrics != null && Beatmap?.Metrics != null) { - clearStats(); + updateMetrics(); return; } - ratingsContainer.FadeIn(transition_duration); - advanced.Beatmap = Beatmap; - description.Text = Beatmap.Version; - source.Text = Beatmap.Metadata.Source; - tags.Text = Beatmap.Metadata.Tags; + // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). + if (Beatmap?.OnlineBeatmapID == null) + { + updateMetrics(); + return; + } var requestedBeatmap = Beatmap; - if (requestedBeatmap.Metrics == null) + + var lookup = new GetBeatmapRequest(requestedBeatmap); + + lookup.Success += res => { - var lookup = new GetBeatmapDetailsRequest(requestedBeatmap); - lookup.Success += res => + Schedule(() => { if (beatmap != requestedBeatmap) //the beatmap has been changed since we started the lookup. return; - requestedBeatmap.Metrics = res; - Schedule(() => displayMetrics(res)); - }; - lookup.Failure += e => Schedule(() => displayMetrics(null)); + var b = res.ToBeatmap(rulesets); - api.Queue(lookup); - loading.Show(); - } + if (requestedBeatmap.BeatmapSet == null) + requestedBeatmap.BeatmapSet = b.BeatmapSet; + else + requestedBeatmap.BeatmapSet.Metrics = b.BeatmapSet.Metrics; - displayMetrics(requestedBeatmap.Metrics, false); + requestedBeatmap.Metrics = b.Metrics; + + updateMetrics(); + }); + }; + + lookup.Failure += e => + { + Schedule(() => + { + if (beatmap != requestedBeatmap) + //the beatmap has been changed since we started the lookup. + return; + + updateMetrics(); + }); + }; + + api.Queue(lookup); + loading.Show(); } - private void displayMetrics(BeatmapMetrics metrics, bool failOnMissing = true) + private void updateMetrics() { - var hasRatings = metrics?.Ratings?.Any() ?? false; - var hasRetriesFails = (metrics?.Retries?.Any() ?? false) && (metrics.Fails?.Any() ?? false); - - if (failOnMissing) loading.Hide(); + var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; + var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) && (beatmap?.Metrics.Fails?.Any() ?? false); if (hasRatings) { - ratings.Metrics = metrics; - ratings.FadeIn(transition_duration); - } - else if (failOnMissing) - { - ratings.Metrics = new BeatmapMetrics - { - Ratings = new int[10], - }; + ratings.Metrics = beatmap.BeatmapSet.Metrics; + ratingsContainer.FadeIn(transition_duration); } else { - ratings.FadeTo(0.25f, transition_duration); + ratings.Metrics = new BeatmapSetMetrics { Ratings = new int[10] }; + ratingsContainer.FadeTo(0.25f, transition_duration); } if (hasRetriesFails) { - failRetryGraph.Metrics = metrics; + failRetryGraph.Metrics = beatmap.Metrics; failRetryContainer.FadeIn(transition_duration); } - else if (failOnMissing) + else { failRetryGraph.Metrics = new BeatmapMetrics { Fails = new int[100], Retries = new int[100], }; + failRetryContainer.FadeOut(transition_duration); } - else - { - failRetryContainer.FadeTo(0.25f, transition_duration); - } - } - - private void clearStats() - { - description.Text = null; - source.Text = null; - tags.Text = null; - - advanced.Beatmap = new BeatmapInfo - { - StarDifficulty = 0, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 0, - DrainRate = 0, - OverallDifficulty = 0, - ApproachRate = 0, - }, - }; loading.Hide(); - ratingsContainer.FadeOut(transition_duration); - failRetryContainer.FadeOut(transition_duration); } private class DetailBox : Container @@ -323,8 +324,7 @@ namespace osu.Game.Screens.Select Child = new OsuSpriteText { Text = title, - Font = @"Exo2.0-Bold", - TextSize = 14, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), }, }, }, @@ -337,17 +337,19 @@ namespace osu.Game.Screens.Select { if (string.IsNullOrEmpty(value)) { - textContainer.FadeOut(transition_duration); + this.FadeOut(transition_duration); return; } + this.FadeIn(transition_duration); + setTextAsync(value); } } private void setTextAsync(string text) { - LoadComponentAsync(new OsuTextFlowContainer(s => s.TextSize = 14) + LoadComponentAsync(new OsuTextFlowContainer(s => s.Font = s.Font.With(size: 14)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -384,13 +386,13 @@ namespace osu.Game.Screens.Select protected override void PopIn() { this.FadeIn(transition_duration, Easing.OutQuint); - loading.State = Visibility.Visible; + loading.Show(); } protected override void PopOut() { this.FadeOut(transition_duration, Easing.OutQuint); - loading.State = Visibility.Hidden; + loading.Hide(); } } } diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index c72701d3cb..fa9ffd0706 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -8,7 +8,7 @@ using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -22,8 +22,11 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; namespace osu.Game.Screens.Select @@ -97,7 +100,7 @@ namespace osu.Game.Screens.Select { void removeOldInfo() { - State = beatmap == null ? Visibility.Hidden : Visibility.Visible; + State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; Info?.FadeOut(250); Info?.Expire(); @@ -203,9 +206,8 @@ namespace osu.Game.Screens.Select { VersionLabel = new OsuSpriteText { - Font = @"Exo2.0-MediumItalic", Text = beatmapInfo.Version, - TextSize = 24, + Font = OsuFont.GetFont(size: 24, italics: true), }, } }, @@ -240,13 +242,11 @@ namespace osu.Game.Screens.Select { TitleLabel = new OsuSpriteText { - Font = @"Exo2.0-MediumItalic", - TextSize = 28, + Font = OsuFont.GetFont(size: 28, italics: true), }, ArtistLabel = new OsuSpriteText { - Font = @"Exo2.0-MediumItalic", - TextSize = 17, + Font = OsuFont.GetFont(size: 17, italics: true), }, MapperContainer = new FillFlowContainer { @@ -266,8 +266,8 @@ namespace osu.Game.Screens.Select } }; - titleBinding.BindValueChanged(value => setMetadata(metadata.Source)); - artistBinding.BindValueChanged(value => setMetadata(metadata.Source), true); + titleBinding.BindValueChanged(_ => setMetadata(metadata.Source)); + artistBinding.BindValueChanged(_ => setMetadata(metadata.Source), true); // no difficulty means it can't have a status to show if (beatmapInfo.Version == null) @@ -295,14 +295,14 @@ namespace osu.Game.Screens.Select labels.Add(new InfoLabel(new BeatmapStatistic { Name = "Length", - Icon = FontAwesome.fa_clock_o, + Icon = FontAwesome.Regular.Clock, Content = TimeSpan.FromMilliseconds(endTime - b.HitObjects.First().StartTime).ToString(@"m\:ss"), })); labels.Add(new InfoLabel(new BeatmapStatistic { Name = "BPM", - Icon = FontAwesome.fa_circle, + Icon = FontAwesome.Regular.Circle, Content = getBPMRange(b), })); @@ -311,12 +311,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset); + playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); } labels.AddRange(playableBeatmap.GetStatistics().Select(s => new InfoLabel(s))); @@ -345,16 +345,13 @@ namespace osu.Game.Screens.Select { new OsuSpriteText { - Font = @"Exo2.0-Medium", Text = "mapped by ", - TextSize = 15, + Font = OsuFont.GetFont(size: 15), }, new OsuSpriteText { - Font = @"Exo2.0-Bold", - // ReSharper disable once PossibleNullReferenceException (resharper broken?) Text = metadata.Author.Username, - TextSize = 15, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 15), } }; } @@ -383,7 +380,7 @@ namespace osu.Game.Screens.Select Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"441288"), - Icon = FontAwesome.fa_square, + Icon = FontAwesome.Solid.Square, Rotation = 45, }, new SpriteIcon @@ -402,10 +399,9 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Colour = new Color4(255, 221, 85, 255), - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Margin = new MarginPadding { Left = 30 }, Text = statistic.Content, - TextSize = 17, } }; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 272332f1ce..712ab7b571 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -24,12 +24,12 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps; + bool match = criteria.Ruleset == null || Beatmap.RulesetID == criteria.Ruleset.ID || (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); - if (!string.IsNullOrEmpty(criteria.SearchText)) + foreach (var criteriaTerm in criteria.SearchTerms) match &= - Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0) || - Beatmap.Version.IndexOf(criteria.SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0; + Beatmap.Metadata.SearchableTerms.Any(term => term.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0) || + Beatmap.Version.IndexOf(criteriaTerm, StringComparison.InvariantCultureIgnoreCase) >= 0; Filtered.Value = !match; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 246e634b74..f1951e27ab 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -38,10 +38,16 @@ namespace osu.Game.Screens.Select.Carousel default: case SortMode.Artist: return string.Compare(BeatmapSet.Metadata.Artist, otherSet.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Title: return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase); + case SortMode.Author: return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.InvariantCultureIgnoreCase); + + case SortMode.DateAdded: + return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); + case SortMode.Difficulty: return BeatmapSet.MaxStarDifficulty.CompareTo(otherSet.BeatmapSet.MaxStarDifficulty); } @@ -50,7 +56,7 @@ namespace osu.Game.Screens.Select.Carousel public override void Filter(FilterCriteria criteria) { base.Filter(criteria); - Filtered.Value = InternalChildren.All(i => i.Filtered); + Filtered.Value = InternalChildren.All(i => i.Filtered.Value); } public override string ToString() => BeatmapSet.ToString(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index ea461e7bd5..6ebd2d41cc 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel public virtual void AddChild(CarouselItem i) { - i.State.ValueChanged += v => ChildItemStateChanged(i, v); + i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue); i.ChildID = ++currentChildID; InternalChildren.Add(i); } @@ -58,18 +58,19 @@ namespace osu.Game.Screens.Select.Carousel { if (items != null) InternalChildren = items; - State.ValueChanged += v => + State.ValueChanged += state => { - switch (v) + switch (state.NewValue) { case CarouselItemState.Collapsed: case CarouselItemState.NotSelected: InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); break; + case CarouselItemState.Selected: InternalChildren.ForEach(c => { - if (c.State == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; + if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; }); break; } @@ -96,6 +97,7 @@ namespace osu.Game.Screens.Select.Carousel foreach (var b in InternalChildren) { if (item == b) continue; + b.State.Value = CarouselItemState.NotSelected; } diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 0ac869f484..045c682dc3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Linq; @@ -13,9 +13,9 @@ namespace osu.Game.Screens.Select.Carousel { public CarouselGroupEagerSelect() { - State.ValueChanged += v => + State.ValueChanged += state => { - if (v == CarouselItemState.Selected) + if (state.NewValue == CarouselItemState.Selected) attemptSelection(); }; } @@ -69,6 +69,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.Selected: updateSelected(item); break; + case CarouselItemState.NotSelected: case CarouselItemState.Collapsed: attemptSelection(); @@ -81,10 +82,10 @@ namespace osu.Game.Screens.Select.Carousel if (filteringChildren) return; // we only perform eager selection if we are a currently selected group. - if (State != CarouselItemState.Selected) return; + if (State.Value != CarouselItemState.Selected) return; // we only perform eager selection if none of our children are in a selected state already. - if (Children.Any(i => i.State == CarouselItemState.Selected)) return; + if (Children.Any(i => i.State.Value == CarouselItemState.Selected)) return; PerformSelection(); } @@ -92,8 +93,8 @@ namespace osu.Game.Screens.Select.Carousel protected virtual void PerformSelection() { CarouselItem nextToSelect = - Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered) ?? - Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered); + Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ?? + Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value); if (nextToSelect != null) nextToSelect.State.Value = CarouselItemState.Selected; diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index c0781f09c0..79c1a4cb6b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -1,9 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; -using osu.Framework.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Screens.Select.Carousel { @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select.Carousel /// /// This item is not in a hidden state. /// - public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered; + public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered.Value; public virtual List Drawables { @@ -31,15 +31,13 @@ namespace osu.Game.Screens.Select.Carousel } } - private int creationOrder; - protected CarouselItem() { DrawableRepresentation = new Lazy(CreateDrawableRepresentation); - Filtered.ValueChanged += v => + Filtered.ValueChanged += filtered => { - if (v && State == CarouselItemState.Selected) + if (filtered.NewValue && State.Value == CarouselItemState.Selected) State.Value = CarouselItemState.NotSelected; }; } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 8216fc0e33..0a20f2aa6d 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -38,7 +39,8 @@ namespace osu.Game.Screens.Select.Carousel private BeatmapSetOverlay beatmapOverlay; - public DrawableCarouselBeatmap(CarouselBeatmap panel) : base(panel) + public DrawableCarouselBeatmap(CarouselBeatmap panel) + : base(panel) { beatmap = panel.Beatmap; Height *= 0.60f; @@ -100,25 +102,21 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Font = @"Exo2.0-Medium", Text = beatmap.Version, - TextSize = 20, + Font = OsuFont.GetFont(size: 20), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft }, new OsuSpriteText { - Font = @"Exo2.0-Medium", Text = "mapped by", - TextSize = 16, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft }, new OsuSpriteText { - Font = @"Exo2.0-MediumItalic", Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author.Username}", - TextSize = 16, + Font = OsuFont.GetFont(italics: true), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft }, @@ -157,7 +155,7 @@ namespace osu.Game.Screens.Select.Carousel protected override bool OnClick(ClickEvent e) { - if (Item.State == CarouselItemState.Selected) + if (Item.State.Value == CarouselItemState.Selected) startRequested?.Invoke(beatmap); return base.OnClick(e); @@ -171,15 +169,22 @@ namespace osu.Game.Screens.Select.Carousel base.ApplyState(); } - public MenuItem[] ContextMenuItems => new MenuItem[] + public MenuItem[] ContextMenuItems { - new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), - new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), - new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), - new OsuMenuItem("Details", MenuItemType.Standard, () => + get { - if (beatmap.OnlineBeatmapID.HasValue) beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); - }), - }; + List items = new List + { + new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested?.Invoke(beatmap)), + new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested?.Invoke(beatmap)), + new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested?.Invoke(beatmap)), + }; + + if (beatmap.OnlineBeatmapID.HasValue) + items.Add(new OsuMenuItem("Details", MenuItemType.Standard, () => beatmapOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + + return items.ToArray(); + } + } } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 2e5f01fe29..4ceb82d4cc 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -15,6 +15,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; 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; @@ -48,10 +49,15 @@ namespace osu.Game.Screens.Select.Carousel Children = new Drawable[] { new DelayedLoadUnloadWrapper(() => - new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) { - RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(1000, Easing.OutQuint), + var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault())) + { + RelativeSizeAxes = Axes.Both, + }; + + background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint); + + return background; }, 300, 5000 ), new FillFlowContainer @@ -63,16 +69,14 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Font = @"Exo2.0-BoldItalic", Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)), - TextSize = 22, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true), Shadow = true, }, new OsuSpriteText { - Font = @"Exo2.0-SemiBoldItalic", Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)), - TextSize = 17, + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true), Shadow = true, }, new FillFlowContainer @@ -109,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel { List items = new List(); - if (Item.State == CarouselItemState.NotSelected) + if (Item.State.Value == CarouselItemState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); if (beatmapSet.OnlineBeatmapSetID != null) @@ -139,10 +143,10 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.Centre, FillMode = FillMode.Fill, }, - new FillFlowContainer + // Todo: This should be a fill flow, but has invalidation issues (see https://github.com/ppy/osu-framework/issues/223) + new Container { Depth = -1, - Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.Both, // This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle Shear = new Vector2(0.8f, 0), @@ -153,6 +157,7 @@ namespace osu.Game.Screens.Select.Carousel new Box { RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, Colour = Color4.Black, Width = 0.4f, }, @@ -160,20 +165,26 @@ namespace osu.Game.Screens.Select.Carousel new Box { RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)), Width = 0.05f, + X = 0.4f, }, new Box { RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)), Width = 0.2f, + X = 0.45f, }, new Box { RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)), Width = 0.05f, + X = 0.65f, }, } }, @@ -189,7 +200,7 @@ namespace osu.Game.Screens.Select.Carousel : base(item.Beatmap) { filtered.BindTo(item.Filtered); - filtered.ValueChanged += v => Schedule(() => this.FadeTo(v ? 0.1f : 1, 100)); + filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); filtered.TriggerChange(); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index 3ec980602d..b906bd935c 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Audio; @@ -7,6 +7,7 @@ using osu.Framework.Audio.Sample; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.MathUtils; @@ -68,7 +69,7 @@ namespace osu.Game.Screens.Select.Carousel } }; - sampleHover = audio.Sample.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); + sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}"); hoverLayer.Colour = colours.Blue.Opacity(0.1f); } @@ -106,6 +107,7 @@ namespace osu.Game.Screens.Select.Carousel case CarouselItemState.NotSelected: Deselected(); break; + case CarouselItemState.Selected: Selected(); break; diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 88fd9ca032..52a57dd506 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; @@ -20,12 +20,14 @@ namespace osu.Game.Screens.Select.Details private readonly StatisticRow firstValue, hpDrain, accuracy, approachRate, starDifficulty; private BeatmapInfo beatmap; + public BeatmapInfo Beatmap { - get { return beatmap; } + get => beatmap; set { if (value == beatmap) return; + beatmap = value; //mania specific @@ -83,14 +85,15 @@ namespace osu.Game.Screens.Select.Details public string Title { - get { return name.Text; } - set { name.Text = value; } + get => name.Text; + set => name.Text = value; } private float difficultyValue; + public float Value { - get { return difficultyValue; } + get => difficultyValue; set { difficultyValue = value; @@ -101,8 +104,8 @@ namespace osu.Game.Screens.Select.Details public Color4 AccentColour { - get { return bar.AccentColour; } - set { bar.AccentColour = value; } + get => bar.AccentColour; + set => bar.AccentColour = value; } public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) @@ -120,7 +123,7 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Child = name = new OsuSpriteText { - TextSize = 13, + Font = OsuFont.GetFont(size: 13) }, }, bar = new Bar @@ -142,7 +145,7 @@ namespace osu.Game.Screens.Select.Details { Anchor = Anchor.Centre, Origin = Anchor.Centre, - TextSize = 13, + Font = OsuFont.GetFont(size: 13) }, }, }; diff --git a/osu.Game/Screens/Select/Details/FailRetryGraph.cs b/osu.Game/Screens/Select/Details/FailRetryGraph.cs index b5a6a26807..34297d89a4 100644 --- a/osu.Game/Screens/Select/Details/FailRetryGraph.cs +++ b/osu.Game/Screens/Select/Details/FailRetryGraph.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Allocation; @@ -17,12 +17,14 @@ namespace osu.Game.Screens.Select.Details private readonly BarGraph retryGraph, failGraph; private BeatmapMetrics metrics; + public BeatmapMetrics Metrics { - get { return metrics; } + get => metrics; set { if (value == metrics) return; + metrics = value; var retries = Metrics?.Retries ?? new int[0]; diff --git a/osu.Game/Screens/Select/Details/UserRatings.cs b/osu.Game/Screens/Select/Details/UserRatings.cs index 787b22f965..c1e01e3572 100644 --- a/osu.Game/Screens/Select/Details/UserRatings.cs +++ b/osu.Game/Screens/Select/Details/UserRatings.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -20,14 +20,15 @@ namespace osu.Game.Screens.Select.Details private readonly Container graphContainer; private readonly BarGraph graph; - private BeatmapMetrics metrics; + private BeatmapSetMetrics metrics; - public BeatmapMetrics Metrics + public BeatmapSetMetrics Metrics { - get { return metrics; } + get => metrics; set { if (value == metrics) return; + metrics = value; const int rating_range = 10; @@ -70,7 +71,7 @@ namespace osu.Game.Screens.Select.Details Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "User Rating", - TextSize = 13, + Font = OsuFont.GetFont(size: 13) }, ratingsBar = new Bar { @@ -87,14 +88,14 @@ namespace osu.Game.Screens.Select.Details negativeRatings = new OsuSpriteText { Text = "0", - TextSize = 13, + Font = OsuFont.GetFont(size: 13) }, positiveRatings = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Text = @"0", - TextSize = 13, + Font = OsuFont.GetFont(size: 13) }, }, }, @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Select.Details Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Text = "Rating Spread", - TextSize = 13, + Font = OsuFont.GetFont(size: 13), Margin = new MarginPadding { Top = 10, Bottom = 5 }, }, }, diff --git a/osu.Game/Screens/Select/EditSongSelect.cs b/osu.Game/Screens/Select/EditSongSelect.cs index e1d71fdd05..bdf5f905fe 100644 --- a/osu.Game/Screens/Select/EditSongSelect.cs +++ b/osu.Game/Screens/Select/EditSongSelect.cs @@ -1,5 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Screens; namespace osu.Game.Screens.Select { @@ -9,7 +11,7 @@ namespace osu.Game.Screens.Select protected override bool OnStart() { - Exit(); + this.Exit(); return true; } } diff --git a/osu.Game/Screens/Select/Filter/GroupMode.cs b/osu.Game/Screens/Select/Filter/GroupMode.cs index b3bd73ee59..d794c215a3 100644 --- a/osu.Game/Screens/Select/Filter/GroupMode.cs +++ b/osu.Game/Screens/Select/Filter/GroupMode.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; @@ -9,32 +9,46 @@ namespace osu.Game.Screens.Select.Filter { [Description("All")] All, + [Description("Artist")] Artist, + [Description("Author")] Author, + [Description("BPM")] BPM, + [Description("Collections")] Collections, + [Description("Date Added")] DateAdded, + [Description("Difficulty")] Difficulty, + [Description("Favourites")] Favourites, + [Description("Length")] Length, + [Description("My Maps")] MyMaps, + [Description("No Grouping")] NoGrouping, + [Description("Rank Achieved")] RankAchieved, + [Description("Ranked Status")] RankedStatus, + [Description("Recently Played")] RecentlyPlayed, + [Description("Title")] Title } diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index f38b0c89ef..be76fbc3ba 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel; @@ -9,18 +9,25 @@ namespace osu.Game.Screens.Select.Filter { [Description("Artist")] Artist, + [Description("Author")] Author, + [Description("BPM")] BPM, + [Description("Date Added")] DateAdded, + [Description("Difficulty")] Difficulty, + [Description("Length")] Length, + [Description("Rank Achieved")] RankAchieved, + [Description("Title")] Title } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index faffdbf31a..bd0b0dd5cd 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Select public SortMode Sort { - get { return sort; } + get => sort; set { if (sort != value) @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Select public GroupMode Group { - get { return group; } + get => group; set { if (group != value) @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Select Group = group, Sort = sort, SearchText = searchTextBox.Text, - AllowConvertedBeatmaps = showConverted, + AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value }; @@ -146,12 +146,12 @@ namespace osu.Game.Screens.Select } }; - searchTextBox.Current.ValueChanged += t => FilterChanged?.Invoke(CreateCriteria()); + searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria()); groupTabs.PinItem(GroupMode.All); groupTabs.PinItem(GroupMode.RecentlyPlayed); - groupTabs.Current.ValueChanged += val => Group = val; - sortTabs.Current.ValueChanged += val => Sort = val; + groupTabs.Current.ValueChanged += group => Group = group.NewValue; + sortTabs.Current.ValueChanged += sort => Sort = sort.NewValue; } public void Deactivate() @@ -178,7 +178,7 @@ namespace osu.Game.Screens.Select sortTabs.AccentColour = colours.GreenLight; showConverted = config.GetBindable(OsuSetting.ShowConvertedBeatmaps); - showConverted.ValueChanged += val => updateCriteria(); + showConverted.ValueChanged += _ => updateCriteria(); ruleset.BindTo(parentRuleset); ruleset.BindValueChanged(_ => updateCriteria(), true); diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index bea806f00f..140010ff54 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -1,6 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -10,8 +12,22 @@ namespace osu.Game.Screens.Select { public GroupMode Group; public SortMode Sort; - public string SearchText; + + public string[] SearchTerms = Array.Empty(); + public RulesetInfo Ruleset; public bool AllowConvertedBeatmaps; + + private string searchText; + + public string SearchText + { + get => searchText; + set + { + searchText = value; + SearchTerms = searchText.Split(',', ' ', '!').Where(s => !string.IsNullOrEmpty(s)).ToArray(); + } + } } } diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index c4123884e0..0680711f1c 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -1,12 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; -using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,70 +18,44 @@ namespace osu.Game.Screens.Select { private readonly Box modeLight; - private const float play_song_select_button_width = 100; - private const float play_song_select_button_height = 50; - public const float HEIGHT = 50; public const int TRANSITION_LENGTH = 300; private const float padding = 80; - public Action OnBack; - private readonly FillFlowContainer buttons; - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. - /// Action the button does. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, Action action, Key? hotkey = null, float depth = 0) - { - var button = new FooterButton - { - Text = text, - Height = play_song_select_button_height, - Width = play_song_select_button_width, - Depth = depth, - SelectedColour = colour, - DeselectedColour = colour.Opacity(0.5f), - Hotkey = hotkey, - Hovered = updateModeLight, - HoverLost = updateModeLight, - Action = action, - }; - - buttons.Add(button); - buttons.SetLayoutPosition(button, -depth); - } - private readonly List overlays = new List(); - /// Text on the button. - /// Colour of the button. - /// Hotkey of the button. + /// THe button to be added. /// The to be toggled by this button. - /// - /// Higher depth to be put on the left, and lower to be put on the right. - /// Notice this is different to ! - /// - public void AddButton(string text, Color4 colour, OverlayContainer overlay, Key? hotkey = null, float depth = 0) + public void AddButton(FooterButton button, OverlayContainer overlay) { overlays.Add(overlay); - AddButton(text, colour, () => + button.Action = () => showOverlay(overlay); + + AddButton(button); + } + + /// Button to be added. + public void AddButton(FooterButton button) + { + button.Hovered = updateModeLight; + button.HoverLost = updateModeLight; + + buttons.Add(button); + } + + private void showOverlay(OverlayContainer overlay) + { + foreach (var o in overlays) { - foreach (var o in overlays) - { - if (o == overlay) - o.ToggleVisibility(); - else - o.Hide(); - } - }, hotkey, depth); + if (o == overlay) + o.ToggleVisibility(); + else + o.Hide(); + } } private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); @@ -108,12 +80,6 @@ namespace osu.Game.Screens.Select Height = 3, Position = new Vector2(0, -3), }, - new BackButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Action = () => OnBack?.Invoke() - }, new FillFlowContainer { Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Select/FooterButton.cs b/osu.Game/Screens/Select/FooterButton.cs index 3c32c18f05..e18a086a10 100644 --- a/osu.Game/Screens/Select/FooterButton.cs +++ b/osu.Game/Screens/Select/FooterButton.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; using osuTK.Graphics; using osuTK.Input; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -20,18 +21,19 @@ namespace osu.Game.Screens.Select public string Text { - get { return spriteText?.Text; } + get => SpriteText?.Text; set { - if (spriteText != null) - spriteText.Text = value; + if (SpriteText != null) + SpriteText.Text = value; } } private Color4 deselectedColour; + public Color4 DeselectedColour { - get { return deselectedColour; } + get => deselectedColour; set { deselectedColour = value; @@ -41,9 +43,10 @@ namespace osu.Game.Screens.Select } private Color4 selectedColour; + public Color4 SelectedColour { - get { return selectedColour; } + get => selectedColour; set { selectedColour = value; @@ -51,7 +54,8 @@ namespace osu.Game.Screens.Select } } - private readonly SpriteText spriteText; + protected readonly Container TextContainer; + protected readonly SpriteText SpriteText; private readonly Box box; private readonly Box light; @@ -59,8 +63,18 @@ namespace osu.Game.Screens.Select public FooterButton() { + AutoSizeAxes = Axes.Both; Children = new Drawable[] { + TextContainer = new Container + { + Size = new Vector2(100, 50), + Child = SpriteText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }, box = new Box { RelativeSizeAxes = Axes.Both, @@ -76,11 +90,6 @@ namespace osu.Game.Screens.Select EdgeSmoothness = new Vector2(2, 0), RelativeSizeAxes = Axes.X, }, - spriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } }; } diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs new file mode 100644 index 0000000000..c96c5022c0 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -0,0 +1,60 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens.Play.HUD; +using osu.Game.Rulesets.Mods; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonMods : FooterButton + { + public FooterButtonMods(Bindable> mods) + { + FooterModDisplay modDisplay; + + Add(new Container + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = modDisplay = new FooterModDisplay + { + DisplayUnrankedText = false, + Scale = new Vector2(0.8f) + }, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Left = 70 } + }); + + if (mods != null) + modDisplay.Current = mods; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Yellow; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"mods"; + Hotkey = Key.F1; + } + + private class FooterModDisplay : ModDisplay + { + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; + + public FooterModDisplay() + { + AllowExpand = false; + } + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonOptions.cs b/osu.Game/Screens/Select/FooterButtonOptions.cs new file mode 100644 index 0000000000..c000d8a8c8 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonOptions.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Game.Graphics; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonOptions : FooterButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Blue; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"options"; + Hotkey = Key.F3; + } + } +} diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs new file mode 100644 index 0000000000..14c9eb2035 --- /dev/null +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK.Input; + +namespace osu.Game.Screens.Select +{ + public class FooterButtonRandom : FooterButton + { + private readonly SpriteText secondaryText; + private bool secondaryActive; + + public FooterButtonRandom() + { + TextContainer.Add(secondaryText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = @"rewind", + Alpha = 0 + }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + SelectedColour = colours.Green; + DeselectedColour = SelectedColour.Opacity(0.5f); + Text = @"random"; + Hotkey = Key.F2; + } + + protected override bool OnKeyDown(KeyDownEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyDown(e); + } + + protected override bool OnKeyUp(KeyUpEvent e) + { + secondaryActive = e.ShiftPressed; + updateText(); + return base.OnKeyUp(e); + } + + private void updateText() + { + if (secondaryActive) + { + SpriteText.FadeOut(120, Easing.InQuad); + secondaryText.FadeIn(120, Easing.InQuad); + } + else + { + SpriteText.FadeIn(120, Easing.InQuad); + secondaryText.FadeOut(120, Easing.InQuad); + } + } + } +} diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index a52b5bab2a..54e4c096f6 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Overlays.Dialog; namespace osu.Game.Screens.Select @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Select HeaderText = @"You have no beatmaps!"; BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps (and skins)?"; - Icon = FontAwesome.fa_plane; + Icon = FontAwesome.Solid.Plane; Buttons = new PopupDialogButton[] { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs new file mode 100644 index 0000000000..aafa6bb0eb --- /dev/null +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -0,0 +1,87 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Select.Leaderboards +{ + public class BeatmapLeaderboard : Leaderboard + { + public Action ScoreSelected; + + private BeatmapInfo beatmap; + + public BeatmapInfo Beatmap + { + get => beatmap; + set + { + if (beatmap == value) + return; + + beatmap = value; + Scores = null; + + UpdateScores(); + } + } + + [Resolved] + private ScoreManager scoreManager { get; set; } + + [Resolved] + private IBindable ruleset { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + ruleset.ValueChanged += _ => UpdateScores(); + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + if (Scope == BeatmapLeaderboardScope.Local) + { + Scores = scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID).OrderByDescending(s => s.TotalScore).ToArray(); + PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; + return null; + } + + if (Beatmap?.OnlineBeatmapID == null) + { + PlaceholderState = PlaceholderState.Unavailable; + return null; + } + + if (Scope != BeatmapLeaderboardScope.Global && !api.LocalUser.Value.IsSupporter) + { + PlaceholderState = PlaceholderState.NotSupporter; + return null; + } + + var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope); + + req.Success += r => scoresCallback?.Invoke(r.Scores); + + return req; + } + + protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index) + { + Action = () => ScoreSelected?.Invoke(model) + }; + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs new file mode 100644 index 0000000000..9e480b61c6 --- /dev/null +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs @@ -0,0 +1,13 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Screens.Select.Leaderboards +{ + public enum BeatmapLeaderboardScope + { + Local, + Country, + Global, + Friend, + } +} diff --git a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs b/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs deleted file mode 100644 index 0cf1e60304..0000000000 --- a/osu.Game/Screens/Select/Leaderboards/DrawableRank.cs +++ /dev/null @@ -1,57 +0,0 @@ -// 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.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Extensions; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Screens.Select.Leaderboards -{ - public class DrawableRank : Container - { - private readonly Sprite rankSprite; - private TextureStore textures; - - public ScoreRank Rank { get; private set; } - - public DrawableRank(ScoreRank rank) - { - Rank = rank; - - Children = new Drawable[] - { - rankSprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fit - }, - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures) - { - this.textures = textures; - updateTexture(); - } - - private void updateTexture() - { - rankSprite.Texture = textures.Get($@"Grades/{Rank.GetDescription()}"); - } - - public void UpdateRank(ScoreRank newRank) - { - Rank = newRank; - - if (LoadState >= LoadState.Ready) - updateTexture(); - } - } -} diff --git a/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs deleted file mode 100644 index 761f53a5e8..0000000000 --- a/osu.Game/Screens/Select/Leaderboards/LeaderboardScope.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Select.Leaderboards -{ - public enum LeaderboardScope - { - Local, - Country, - Global, - Friend, - } -} diff --git a/osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs b/osu.Game/Screens/Select/Leaderboards/PlaceholderState.cs deleted file mode 100644 index 33a56540f3..0000000000 --- a/osu.Game/Screens/Select/Leaderboards/PlaceholderState.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 - -namespace osu.Game.Screens.Select.Leaderboards -{ - public enum PlaceholderState - { - Successful, - Retrieving, - NetworkFailure, - Unavailable, - NoScores, - NotLoggedIn, - NotSupporter, - } -} diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index 339392d5cf..c5fa9e2396 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -1,14 +1,82 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Humanizer; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Multi; namespace osu.Game.Screens.Select { - public class MatchSongSelect : SongSelect + public class MatchSongSelect : SongSelect, IMultiplayerSubScreen { + public Action Selected; + + public string ShortTitle => "song selection"; + public override string Title => ShortTitle.Humanize(); + + [Resolved(typeof(Room))] + protected Bindable CurrentItem { get; private set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + public MatchSongSelect() + { + Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; + } + protected override bool OnStart() { - if (IsCurrentScreen) Exit(); + var item = new PlaylistItem + { + Beatmap = Beatmap.Value.BeatmapInfo, + Ruleset = Ruleset.Value, + RulesetID = Ruleset.Value.ID ?? 0 + }; + + item.RequiredMods.AddRange(Mods.Value); + + Selected?.Invoke(item); + + if (this.IsCurrentScreen()) + this.Exit(); + return true; } + + public override bool OnExiting(IScreen next) + { + if (base.OnExiting(next)) + return true; + + if (CurrentItem.Value != null) + { + Ruleset.Value = CurrentItem.Value.Ruleset; + Beatmap.Value = beatmaps.GetWorkingBeatmap(CurrentItem.Value.Beatmap); + Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty(); + } + + Beatmap.Disabled = true; + Ruleset.Disabled = true; + Mods.Disabled = true; + + return false; + } + + public override void OnEntering(IScreen last) + { + base.OnEntering(last); + + Beatmap.Disabled = false; + Ruleset.Disabled = false; + Mods.Disabled = false; + } } } diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index 9659d537f1..a8b5bbbd00 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -1,10 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -28,26 +30,26 @@ namespace osu.Game.Screens.Select.Options public Color4 ButtonColour { - get { return background.Colour; } - set { background.Colour = value; } + get => background.Colour; + set => background.Colour = value; } - public FontAwesome Icon + public IconUsage Icon { - get { return iconText.Icon; } - set { iconText.Icon = value; } + get => iconText.Icon; + set => iconText.Icon = value; } public string FirstLineText { - get { return firstLine.Text; } - set { firstLine.Text = value; } + get => firstLine.Text; + set => firstLine.Text = value; } public string SecondLineText { - get { return secondLine.Text; } - set { secondLine.Text = value; } + get => secondLine.Text; + set => secondLine.Text = value; } public Key? HotKey; @@ -140,7 +142,7 @@ namespace osu.Game.Screens.Select.Options Anchor = Anchor.TopCentre, Size = new Vector2(30), Shadow = true, - Icon = FontAwesome.fa_close, + Icon = FontAwesome.Solid.TimesCircle, Margin = new MarginPadding { Bottom = 5, @@ -150,14 +152,14 @@ namespace osu.Game.Screens.Select.Options { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), Text = @"", }, secondLine = new OsuSpriteText { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, - Font = @"Exo2.0-Bold", + Font = OsuFont.GetFont(weight: FontWeight.Bold), Text = @"", }, }, diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs index dcadefebae..669264cef0 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsOverlay.cs @@ -1,12 +1,12 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Sprites; using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Select.Options /// Lower depth to be put on the left, and higher to be put on the right. /// Notice this is different to ! /// - public void AddButton(string firstLine, string secondLine, FontAwesome icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0) + public void AddButton(string firstLine, string secondLine, IconUsage icon, Color4 colour, Action action, Key? hotkey = null, float depth = 0) { var button = new BeatmapOptionsButton { diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 1a405190e7..4df6e6a3f3 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -1,145 +1,50 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Linq; -using osuTK.Input; 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.Sprites; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Overlays; -using osu.Game.Overlays.Mods; -using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play; -using osu.Game.Screens.Ranking; -using osu.Game.Skinning; +using osu.Game.Users; +using osuTK.Input; namespace osu.Game.Screens.Select { public class PlaySongSelect : SongSelect { - private OsuScreen player; - private readonly ModSelectOverlay modSelect; - protected readonly BeatmapDetailArea BeatmapDetails; private bool removeAutoModOnResume; + private OsuScreen player; - public PlaySongSelect() + public override bool AllowExternalScreenChange => true; + + protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); + + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - FooterPanels.Add(modSelect = new ModSelectOverlay - { - RelativeSizeAxes = Axes.X, - Origin = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - }); - - LeftContent.Add(BeatmapDetails = new BeatmapDetailArea - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 10, Right = 5 }, - }); - - BeatmapDetails.Leaderboard.ScoreSelected += s => Push(new Results(s)); - } - - private SampleChannel sampleConfirm; - - [Cached] - [Cached(Type = typeof(IBindable>))] - private readonly Bindable> selectedMods = new Bindable>(new Mod[] { }); - - [BackgroundDependencyLoader(true)] - private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, SkinManager skins, DialogOverlay dialogOverlay, Bindable> selectedMods) - { - if (selectedMods != null) this.selectedMods.BindTo(selectedMods); - - sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection"); - - Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue); - - BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1); - BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, colours.Purple, null, Key.Number2); - BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.fa_pencil, colours.Yellow, () => + BeatmapOptions.AddButton(@"Edit", @"beatmap", FontAwesome.Solid.PencilAlt, colours.Yellow, () => { ValidForResume = false; Edit(); }, Key.Number3); - - if (dialogOverlay != null) - { - Schedule(() => - { - // if we have no beatmaps but osu-stable is found, let's prompt the user to import. - if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable) - dialogOverlay.Push(new ImportFromStablePopup(() => - { - beatmaps.ImportFromStableAsync(); - skins.ImportFromStableAsync(); - })); - }); - } } - protected override void UpdateBeatmap(WorkingBeatmap beatmap) - { - beatmap.Mods.BindTo(selectedMods); - - base.UpdateBeatmap(beatmap); - - BeatmapDetails.Beatmap = beatmap; - - if (beatmap.Track != null) - beatmap.Track.Looping = true; - } - - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { player = null; if (removeAutoModOnResume) { var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod().GetType(); - modSelect.DeselectTypes(new[] { autoType }, true); + ModSelect.DeselectTypes(new[] { autoType }, true); removeAutoModOnResume = false; } - Beatmap.Value.Track.Looping = true; - base.OnResuming(last); } - protected override void OnSuspending(Screen next) - { - modSelect.Hide(); - - base.OnSuspending(next); - } - - protected override bool OnExiting(Screen next) - { - if (modSelect.State == Visibility.Visible) - { - modSelect.Hide(); - return true; - } - - if (base.OnExiting(next)) - return true; - - if (Beatmap.Value.Track != null) - Beatmap.Value.Track.Looping = false; - - selectedMods.UnbindAll(); - Beatmap.Value.Mods.Value = new Mod[] { }; - - return false; - } - protected override bool OnStart() { if (player != null) return false; @@ -150,22 +55,22 @@ namespace osu.Game.Screens.Select var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); var autoType = auto.GetType(); - var mods = selectedMods.Value; + var mods = Mods.Value; + if (mods.All(m => m.GetType() != autoType)) { - selectedMods.Value = mods.Append(auto); + Mods.Value = mods.Append(auto).ToArray(); removeAutoModOnResume = true; } } Beatmap.Value.Track.Looping = false; - Beatmap.Disabled = true; - sampleConfirm?.Play(); + SampleConfirm?.Play(); - LoadComponentAsync(player = new PlayerLoader(new Player()), l => + LoadComponentAsync(player = new PlayerLoader(() => new Player()), l => { - if (IsCurrentScreen) Push(player); + if (this.IsCurrentScreen()) this.Push(player); }); return true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ff897094ee..7c62a49a97 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1,19 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; -using System.Linq; -using osuTK; -using osuTK.Input; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Logging; using osu.Framework.Input.Events; +using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -21,19 +17,32 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using osu.Game.Overlays; +using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; using osu.Game.Screens.Select.Options; +using osu.Game.Skinning; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; namespace osu.Game.Screens.Select { - public abstract class SongSelect : OsuScreen + public abstract class SongSelect : OsuScreen, IKeyBindingHandler { private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 245); - private static readonly Vector2 background_blur = new Vector2(20); + + protected const float BACKGROUND_BLUR = 20; private const float left_area_padding = 20; public readonly FilterControl FilterControl; @@ -56,42 +65,38 @@ namespace osu.Game.Screens.Select /// protected readonly Container FooterPanels; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(); - - protected Container LeftContent; + protected override BackgroundScreen CreateBackground() + { + var background = new BackgroundScreenBeatmap(); + return background; + } protected readonly BeatmapCarousel Carousel; private readonly BeatmapInfoWedge beatmapInfoWedge; private DialogOverlay dialogOverlay; private BeatmapManager beatmaps; + protected readonly ModSelectOverlay ModSelect; + + protected SampleChannel SampleConfirm; private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeBeatmap; - protected new readonly Bindable Ruleset = new Bindable(); + protected readonly BeatmapDetailArea BeatmapDetails; - private DependencyContainer dependencies; + private readonly Bindable decoupledRuleset = new Bindable(); - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(this); - dependencies.CacheAs(Ruleset); - dependencies.CacheAs>(Ruleset); - - return dependencies; - } + [Cached] + [Cached(Type = typeof(IBindable>))] + private readonly Bindable> mods = new Bindable>(Array.Empty()); // Bound to the game's mods, but is not reset on exiting protected SongSelect() { - const float carousel_width = 640; - const float filter_height = 100; - - AddRange(new Drawable[] + AddRangeInternal(new Drawable[] { new ParallaxContainer { - Padding = new MarginPadding { Top = filter_height }, + Masking = true, ParallaxAmount = 0.005f, RelativeSizeAxes = Axes.Both, Children = new[] @@ -99,11 +104,12 @@ namespace osu.Game.Screens.Select new WedgeBackground { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = carousel_width * 0.76f }, + Padding = new MarginPadding { Right = -150 }, + Size = new Vector2(wedged_container_size.X, 1), } } }, - LeftContent = new Container + new Container { Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, @@ -115,6 +121,11 @@ namespace osu.Game.Screens.Select Top = wedged_container_size.Y + left_area_padding, Left = left_area_padding, Right = left_area_padding * 2, + }, + Child = BeatmapDetails = new BeatmapDetailArea + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 10, Right = 5 }, } }, new Container @@ -135,8 +146,8 @@ namespace osu.Game.Screens.Select Carousel = new BeatmapCarousel { Masking = false, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(carousel_width, 1), + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1 - wedged_container_size.X, 1), Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, SelectionChanged = updateSelectedBeatmap, @@ -145,13 +156,13 @@ namespace osu.Game.Screens.Select FilterControl = new FilterControl { RelativeSizeAxes = Axes.X, - Height = filter_height, + Height = 100, FilterChanged = c => Carousel.Filter(c), Background = { Width = 2 }, Exit = () => { - if (IsCurrentScreen) - Exit(); + if (this.IsCurrentScreen()) + this.Exit(); }, }, } @@ -176,35 +187,47 @@ namespace osu.Game.Screens.Select if (ShowFooter) { - Add(FooterPanels = new Container + AddRangeInternal(new[] { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding + FooterPanels = new Container { - Bottom = Footer.HEIGHT, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Bottom = Footer.HEIGHT }, + Children = new Drawable[] + { + BeatmapOptions = new BeatmapOptionsOverlay(), + ModSelect = new ModSelectOverlay + { + RelativeSizeAxes = Axes.X, + Origin = Anchor.BottomCentre, + Anchor = Anchor.BottomCentre, + } + } }, + Footer = new Footer() }); - Add(Footer = new Footer - { - OnBack = Exit, - }); - - FooterPanels.Add(BeatmapOptions = new BeatmapOptionsOverlay()); } + + BeatmapDetails.Leaderboard.ScoreSelected += s => this.Push(new SoloResults(s)); } [BackgroundDependencyLoader(true)] - private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours) + private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuColour colours, SkinManager skins) { + mods.BindTo(Mods); + if (Footer != null) { - Footer.AddButton(@"random", colours.Green, triggerRandom, Key.F2); - Footer.AddButton(@"options", colours.Blue, BeatmapOptions, Key.F3); + Footer.AddButton(new FooterButtonMods(mods), ModSelect); + Footer.AddButton(new FooterButtonRandom { Action = triggerRandom }); + Footer.AddButton(new FooterButtonOptions(), BeatmapOptions); - BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.fa_trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => delete(Beatmap.Value.BeatmapSetInfo), Key.Number4, float.MaxValue); + BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null, Key.Number1); + BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => clearScores(Beatmap.Value.BeatmapInfo), Key.Number2); } if (this.beatmaps == null) @@ -217,16 +240,44 @@ namespace osu.Game.Screens.Select dialogOverlay = dialog; - sampleChangeDifficulty = audio.Sample.Get(@"SongSelect/select-difficulty"); - sampleChangeBeatmap = audio.Sample.Get(@"SongSelect/select-expand"); + sampleChangeDifficulty = audio.Samples.Get(@"SongSelect/select-difficulty"); + sampleChangeBeatmap = audio.Samples.Get(@"SongSelect/select-expand"); + SampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); Carousel.LoadBeatmapSetsFromManager(this.beatmaps); + + if (dialogOverlay != null) + { + Schedule(() => + { + // if we have no beatmaps but osu-stable is found, let's prompt the user to import. + if (!beatmaps.GetAllUsableBeatmapSets().Any() && beatmaps.StableInstallationAvailable) + dialogOverlay.Push(new ImportFromStablePopup(() => + { + Task.Run(beatmaps.ImportFromStableAsync); + Task.Run(skins.ImportFromStableAsync); + })); + }); + } + } + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + dependencies.CacheAs(this); + dependencies.CacheAs(decoupledRuleset); + dependencies.CacheAs>(decoupledRuleset); + + return dependencies; } public void Edit(BeatmapInfo beatmap = null) { Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); - Push(new Editor()); + this.Push(new Editor()); } /// @@ -236,14 +287,18 @@ namespace osu.Game.Screens.Select /// Whether to trigger . public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true) { - // avoid attempting to continue before a selection has been obtained. - // this could happen via a user interaction while the carousel is still in a loading state. - if (Carousel.SelectedBeatmap == null) return; + // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. + if (!Carousel.BeatmapSetsLoaded) + return; // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). Carousel.FlushPendingFilterOperations(); + // avoid attempting to continue before a selection has been obtained. + // this could happen via a user interaction while the carousel is still in a loading state. + if (Carousel.SelectedBeatmap == null) return; + if (beatmap != null) Carousel.SelectBeatmap(beatmap); @@ -266,16 +321,16 @@ namespace osu.Game.Screens.Select private ScheduledDelegate selectionChangedDebounce; - private void workingBeatmapChanged(WorkingBeatmap beatmap) + private void workingBeatmapChanged(ValueChangedEvent e) { - if (beatmap is DummyWorkingBeatmap) return; + if (e.NewValue is DummyWorkingBeatmap) return; - if (IsCurrentScreen && !Carousel.SelectBeatmap(beatmap?.BeatmapInfo, false)) + if (this.IsCurrentScreen() && !Carousel.SelectBeatmap(e.NewValue?.BeatmapInfo, false)) // If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch - if (beatmap?.BeatmapInfo?.Ruleset != null && beatmap.BeatmapInfo.Ruleset != Ruleset.Value) + if (e.NewValue?.BeatmapInfo?.Ruleset != null && e.NewValue.BeatmapInfo.Ruleset != decoupledRuleset.Value) { - base.Ruleset.Value = beatmap.BeatmapInfo.Ruleset; - Carousel.SelectBeatmap(beatmap.BeatmapInfo); + Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; + Carousel.SelectBeatmap(e.NewValue.BeatmapInfo); } } @@ -309,18 +364,23 @@ namespace osu.Game.Screens.Select var beatmap = beatmapNoDebounce; var ruleset = rulesetNoDebounce; + selectionChangedDebounce?.Cancel(); + + if (beatmap == null) + run(); + else + selectionChangedDebounce = Scheduler.AddDelayed(run, 200); + void run() { Logger.Log($"updating selection with beatmap:{beatmap?.ID.ToString() ?? "null"} ruleset:{ruleset?.ID.ToString() ?? "null"}"); - bool preview = false; - - if (ruleset?.Equals(Ruleset.Value) == false) + if (ruleset?.Equals(decoupledRuleset.Value) == false) { - Logger.Log($"ruleset changed from \"{Ruleset.Value}\" to \"{ruleset}\""); + Logger.Log($"ruleset changed from \"{decoupledRuleset.Value}\" to \"{ruleset}\""); - Beatmap.Value.Mods.Value = Enumerable.Empty(); - Ruleset.Value = ruleset; + mods.Value = Array.Empty(); + decoupledRuleset.Value = ruleset; // force a filter before attempting to change the beatmap. // we may still be in the wrong ruleset as there is a debounce delay on ruleset changes. @@ -338,7 +398,6 @@ namespace osu.Game.Screens.Select { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - preview = beatmap?.BeatmapSetInfoID != Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, Beatmap.Value); if (beatmap != null) @@ -350,16 +409,10 @@ namespace osu.Game.Screens.Select } } - if (IsCurrentScreen) ensurePlayingSelected(preview); + if (this.IsCurrentScreen()) + ensurePlayingSelected(); UpdateBeatmap(Beatmap.Value); } - - selectionChangedDebounce?.Cancel(); - - if (beatmap == null) - run(); - else - selectionChangedDebounce = Scheduler.AddDelayed(run, 200); } private void triggerRandom() @@ -370,11 +423,11 @@ namespace osu.Game.Screens.Select Carousel.SelectNextRandom(); } - protected override void OnEntering(Screen last) + public override void OnEntering(IScreen last) { base.OnEntering(last); - Content.FadeInFromZero(250); + this.FadeInFromZero(250); FilterControl.Activate(); } @@ -384,7 +437,6 @@ namespace osu.Game.Screens.Select { base.LogoArriving(logo, resuming); - logo.RelativePositionAxes = Axes.Both; Vector2 position = new Vector2(0.95f, 0.96f); if (logo.Alpha > 0.8f) @@ -415,8 +467,12 @@ namespace osu.Game.Screens.Select logo.FadeOut(logo_transition / 2, Easing.Out); } - protected override void OnResuming(Screen last) + public override void OnResuming(IScreen last) { + BeatmapDetails.Leaderboard.RefreshScores(); + + Beatmap.Value.Track.Looping = true; + if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { UpdateBeatmap(Beatmap.Value); @@ -425,41 +481,58 @@ namespace osu.Game.Screens.Select base.OnResuming(last); - Content.FadeIn(250); + this.FadeIn(250); - Content.ScaleTo(1, 250, Easing.OutSine); + this.ScaleTo(1, 250, Easing.OutSine); FilterControl.Activate(); } - protected override void OnSuspending(Screen next) + public override void OnSuspending(IScreen next) { - Content.ScaleTo(1.1f, 250, Easing.InSine); + ModSelect.Hide(); - Content.FadeOut(250); + BeatmapOptions.Hide(); + + this.ScaleTo(1.1f, 250, Easing.InSine); + + this.FadeOut(250); FilterControl.Deactivate(); base.OnSuspending(next); } - protected override bool OnExiting(Screen next) + public override bool OnExiting(IScreen next) { - FinaliseSelection(performStartAction: false); + if (ModSelect.State.Value == Visibility.Visible) + { + ModSelect.Hide(); + return true; + } - beatmapInfoWedge.State = Visibility.Hidden; + if (base.OnExiting(next)) + return true; - Content.FadeOut(100); + beatmapInfoWedge.Hide(); + + this.FadeOut(100); FilterControl.Deactivate(); - return base.OnExiting(next); + if (Beatmap.Value.Track != null) + Beatmap.Value.Track.Looping = false; + + mods.UnbindAll(); + Mods.Value = Array.Empty(); + + return false; } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - Ruleset.UnbindAll(); + decoupledRuleset.UnbindAll(); if (beatmaps != null) { @@ -482,24 +555,26 @@ namespace osu.Game.Screens.Select if (Background is BackgroundScreenBeatmap backgroundModeBeatmap) { backgroundModeBeatmap.Beatmap = beatmap; - backgroundModeBeatmap.BlurTo(background_blur, 750, Easing.OutQuint); - backgroundModeBeatmap.FadeTo(1, 250); + backgroundModeBeatmap.BlurAmount.Value = BACKGROUND_BLUR; + backgroundModeBeatmap.FadeColour(Color4.White, 250); } beatmapInfoWedge.Beatmap = beatmap; + + BeatmapDetails.Beatmap = beatmap; + + if (beatmap.Track != null) + beatmap.Track.Looping = true; } - private void ensurePlayingSelected(bool preview = false) + private void ensurePlayingSelected(bool restart = false) { Track track = Beatmap.Value.Track; - if (!track.IsRunning) + if (!track.IsRunning || restart) { - // Ensure the track is added to the TrackManager, since it is removed after the player finishes the map. - // Using AddItemToList rather than AddItem so that it doesn't attempt to register adjustment dependencies more than once. - Game.Audio.Track.AddItemToList(track); - if (preview) track.Seek(Beatmap.Value.Metadata.PreviewTime); - track.Start(); + track.RestartPoint = Beatmap.Value.Metadata.PreviewTime; + track.Restart(); } } @@ -510,22 +585,19 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { - if (rulesetNoDebounce == null) - { - // manual binding to parent ruleset to allow for delayed load in the incoming direction. - rulesetNoDebounce = Ruleset.Value = base.Ruleset.Value; - base.Ruleset.ValueChanged += updateSelectedRuleset; - Ruleset.ValueChanged += r => base.Ruleset.Value = r; + bindBindables(); - Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); - Beatmap.BindValueChanged(workingBeatmapChanged); - } + // If a selection was already obtained, do not attempt to update the selected beatmap. + if (Carousel.SelectedBeatmapSet != null) + return; + // Attempt to select the current beatmap on the carousel, if it is valid to be selected. if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false && Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo, false)) return; - if (Carousel.SelectedBeatmapSet == null && !Carousel.SelectNextRandom()) + // If the current active beatmap could not be selected, select a new random beatmap. + if (!Carousel.SelectNextRandom()) { // in the case random selection failed, we want to trigger selectionChanged // to show the dummy beatmap (we have nothing else to display). @@ -533,15 +605,45 @@ namespace osu.Game.Screens.Select } } + private bool boundLocalBindables; + + private void bindBindables() + { + if (boundLocalBindables) + return; + + // manual binding to parent ruleset to allow for delayed load in the incoming direction. + rulesetNoDebounce = decoupledRuleset.Value = Ruleset.Value; + Ruleset.ValueChanged += r => updateSelectedRuleset(r.NewValue); + + decoupledRuleset.ValueChanged += r => Ruleset.Value = r.NewValue; + decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; + + Beatmap.BindDisabledChanged(disabled => Carousel.AllowSelection = !disabled, true); + Beatmap.BindValueChanged(workingBeatmapChanged); + + boundLocalBindables = true; + } + private void delete(BeatmapSetInfo beatmap) { if (beatmap == null || beatmap.ID <= 0) return; + dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } - public override bool OnPressed(GlobalAction action) + private void clearScores(BeatmapInfo beatmap) { - if (!IsCurrentScreen) return false; + if (beatmap == null || beatmap.ID <= 0) return; + + dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap, () => + // schedule done here rather than inside the dialog as the dialog may fade out and never callback. + Schedule(() => BeatmapDetails.Leaderboard.RefreshScores()))); + } + + public virtual bool OnPressed(GlobalAction action) + { + if (!this.IsCurrentScreen()) return false; switch (action) { @@ -550,9 +652,11 @@ namespace osu.Game.Screens.Select return true; } - return base.OnPressed(action); + return false; } + public bool OnReleased(GlobalAction action) => action == GlobalAction.Select; + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Repeat) return false; diff --git a/osu.Game/Screens/Select/WedgeBackground.cs b/osu.Game/Screens/Select/WedgeBackground.cs index 6e9ee0704b..f1337a8119 100644 --- a/osu.Game/Screens/Select/WedgeBackground.cs +++ b/osu.Game/Screens/Select/WedgeBackground.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs new file mode 100644 index 0000000000..797f185a37 --- /dev/null +++ b/osu.Game/Screens/StartupScreen.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Overlays; + +namespace osu.Game.Screens +{ + /// + /// A screen which is shown once as part of the startup procedure. + /// + public abstract class StartupScreen : OsuScreen + { + public override bool AllowBackButton => false; + + public override bool HideOverlaysOnEnter => true; + + public override bool CursorVisible => false; + + public override OverlayActivation InitialOverlayActivationMode => OverlayActivation.Disabled; + } +} diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs deleted file mode 100644 index 0a428ea289..0000000000 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ /dev/null @@ -1,351 +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.IO; -using System.Linq; -using System.Threading.Tasks; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Logging; -using osu.Framework.Platform; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Backgrounds; -using osu.Game.Screens.Tournament.Components; -using osu.Game.Screens.Tournament.Teams; -using osuTK; -using osuTK.Graphics; -using osu.Framework.IO.Stores; -using osu.Framework.Graphics.Shapes; - -namespace osu.Game.Screens.Tournament -{ - public class Drawings : OsuScreen - { - private const string results_filename = "drawings_results.txt"; - - protected override bool HideOverlaysOnEnter => true; - - protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); - - private ScrollingTeamContainer teamsContainer; - private GroupContainer groupsContainer; - private OsuSpriteText fullTeamNameText; - - private readonly List allTeams = new List(); - - private DrawingsConfigManager drawingsConfig; - - private Task writeOp; - - private Storage storage; - - public ITeamList TeamList; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - [BackgroundDependencyLoader] - private void load(TextureStore textures, Storage storage) - { - this.storage = storage; - - TextureStore flagStore = new TextureStore(); - // Local flag store - flagStore.AddStore(new TextureLoaderStore(new NamespacedResourceStore(new StorageBackedResourceStore(storage), "Drawings"))); - // Default texture store - flagStore.AddStore(textures); - - dependencies.Cache(flagStore); - - if (TeamList == null) - TeamList = new StorageBackedTeamList(storage); - - if (!TeamList.Teams.Any()) - { - Exit(); - return; - } - - drawingsConfig = new DrawingsConfigManager(storage); - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(77, 77, 77, 255) - }, - new Sprite - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - Texture = textures.Get(@"Backgrounds/Drawings/background.png") - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - - Children = new Drawable[] - { - // Main container - new Container - { - RelativeSizeAxes = Axes.Both, - Width = 0.85f, - - Children = new Drawable[] - { - // Visualiser - new VisualiserContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - - RelativeSizeAxes = Axes.X, - Size = new Vector2(1, 10), - - Colour = new Color4(255, 204, 34, 255), - - Lines = 6 - }, - // Groups - groupsContainer = new GroupContainer(drawingsConfig.Get(DrawingsConfig.Groups), drawingsConfig.Get(DrawingsConfig.TeamsPerGroup)) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, - - Padding = new MarginPadding - { - Top = 35f, - Bottom = 35f - } - }, - // Scrolling teams - teamsContainer = new ScrollingTeamContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - - RelativeSizeAxes = Axes.X, - }, - // Scrolling team name - fullTeamNameText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - - Position = new Vector2(0, 45f), - - Alpha = 0, - - Font = "Exo2.0-Light", - TextSize = 42f - } - } - }, - // Control panel container - new Container - { - RelativeSizeAxes = Axes.Both, - Width = 0.15f, - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = new Color4(54, 54, 54, 255) - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - - Text = "Control Panel", - TextSize = 22f, - Font = "Exo2.0-Bold" - }, - new FillFlowContainer - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.75f, - - Position = new Vector2(0, 35f), - - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5f), - - Children = new Drawable[] - { - new TriangleButton - { - RelativeSizeAxes = Axes.X, - - Text = "Begin random", - Action = teamsContainer.StartScrolling, - }, - new TriangleButton - { - RelativeSizeAxes = Axes.X, - - Text = "Stop random", - Action = teamsContainer.StopScrolling, - }, - new TriangleButton - { - RelativeSizeAxes = Axes.X, - - Text = "Reload", - Action = reloadTeams - } - } - }, - new FillFlowContainer - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Width = 0.75f, - - Position = new Vector2(0, -5f), - - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5f), - - Children = new Drawable[] - { - new TriangleButton - { - RelativeSizeAxes = Axes.X, - - Text = "Reset", - Action = () => reset() - } - } - } - } - } - } - } - }; - - teamsContainer.OnSelected += onTeamSelected; - teamsContainer.OnScrollStarted += () => fullTeamNameText.FadeOut(200); - - reset(true); - } - - private void onTeamSelected(DrawingsTeam team) - { - groupsContainer.AddTeam(team); - - fullTeamNameText.Text = team.FullName; - fullTeamNameText.FadeIn(200); - - writeResults(groupsContainer.GetStringRepresentation()); - } - - private void writeResults(string text) - { - void writeAction() - { - try - { - // Write to drawings_results - using (Stream stream = storage.GetStream(results_filename, FileAccess.Write, FileMode.Create)) - using (StreamWriter sw = new StreamWriter(stream)) - { - sw.Write(text); - } - } - catch (Exception ex) - { - Logger.Error(ex, "Failed to write results."); - } - } - - writeOp = writeOp?.ContinueWith(t => { writeAction(); }) ?? Task.Run((Action)writeAction); - } - - private void reloadTeams() - { - teamsContainer.ClearTeams(); - allTeams.Clear(); - - foreach (DrawingsTeam t in TeamList.Teams) - { - if (groupsContainer.ContainsTeam(t.FullName)) - continue; - - allTeams.Add(t); - teamsContainer.AddTeam(t); - } - } - - private void reset(bool loadLastResults = false) - { - groupsContainer.ClearTeams(); - - reloadTeams(); - - if (!storage.Exists(results_filename)) - return; - - if (loadLastResults) - { - try - { - // Read from drawings_results - using (Stream stream = storage.GetStream(results_filename, FileAccess.Read, FileMode.Open)) - using (StreamReader sr = new StreamReader(stream)) - { - string line; - while ((line = sr.ReadLine()?.Trim()) != null) - { - if (string.IsNullOrEmpty(line)) - continue; - - if (line.ToUpperInvariant().StartsWith("GROUP")) - continue; - - // ReSharper disable once AccessToModifiedClosure - DrawingsTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName == line); - - if (teamToAdd == null) - continue; - - groupsContainer.AddTeam(teamToAdd); - teamsContainer.RemoveTeam(teamToAdd); - } - } - } - catch (Exception ex) - { - Logger.Error(ex, "Failed to read last drawings results."); - } - } - else - { - writeResults(string.Empty); - } - } - } -} diff --git a/osu.Game/Screens/Tournament/Teams/DrawingsTeam.cs b/osu.Game/Screens/Tournament/Teams/DrawingsTeam.cs deleted file mode 100644 index 0926ed2748..0000000000 --- a/osu.Game/Screens/Tournament/Teams/DrawingsTeam.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Tournament.Teams -{ - public class DrawingsTeam - { - /// - /// The name of this team. - /// - public string FullName; - - /// - /// Short acronym which appears in the group boxes post-selection. - /// - public string Acronym; - - /// - /// Name of the file containing the flag. - /// - public string FlagName; - } -} diff --git a/osu.Game/Screens/Tournament/Teams/ITeamList.cs b/osu.Game/Screens/Tournament/Teams/ITeamList.cs deleted file mode 100644 index 728c702173..0000000000 --- a/osu.Game/Screens/Tournament/Teams/ITeamList.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using System.Collections.Generic; - -namespace osu.Game.Screens.Tournament.Teams -{ - public interface ITeamList - { - IEnumerable Teams { get; } - } -} diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 292889d4a3..c7556dddd5 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -1,10 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; -using osuTK.Graphics; namespace osu.Game.Skinning { @@ -13,16 +12,7 @@ namespace osu.Game.Skinning public DefaultSkin() : base(SkinInfo.Default) { - Configuration = new SkinConfiguration - { - ComboColours = - { - new Color4(17, 136, 170, 255), - new Color4(102, 136, 0, 255), - new Color4(204, 102, 0, 255), - new Color4(121, 9, 13, 255) - } - }; + Configuration = new SkinConfiguration(); } public override Drawable GetDrawableComponent(string componentName) => null; diff --git a/osu.Game/Skinning/ISkin.cs b/osu.Game/Skinning/ISkin.cs new file mode 100644 index 0000000000..0e67a1897c --- /dev/null +++ b/osu.Game/Skinning/ISkin.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + /// + /// Provides access to skinnable elements. + /// + public interface ISkin + { + Drawable GetDrawableComponent(string componentName); + + Texture GetTexture(string componentName); + + SampleChannel GetSample(string sampleName); + + TValue GetValue(Func query) where TConfiguration : SkinConfiguration; + } +} diff --git a/osu.Game/Skinning/ISkinSource.cs b/osu.Game/Skinning/ISkinSource.cs index 609f8e4ac4..337d2a87a4 100644 --- a/osu.Game/Skinning/ISkinSource.cs +++ b/osu.Game/Skinning/ISkinSource.cs @@ -1,28 +1,15 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio.Sample; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { /// /// Provides access to skinnable elements. /// - public interface ISkinSource + public interface ISkinSource : ISkin { event Action SourceChanged; - - Drawable GetDrawableComponent(string componentName); - - Texture GetTexture(string componentName); - - SampleChannel GetSample(string sampleName); - - TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class; - - TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct; } } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 0b17bc6f3a..6770da3c66 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; using osu.Framework.IO.Stores; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 25a9813ad6..7b658f86d0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -21,14 +22,15 @@ namespace osu.Game.Skinning { protected TextureStore Textures; - protected SampleManager Samples; + protected IResourceStore Samples; public LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager) : this(skin, new LegacySkinResourceStore(skin, storage), audioManager, "skin.ini") { } - protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) : base(skin) + protected LegacySkin(SkinInfo skin, IResourceStore storage, AudioManager audioManager, string filename) + : base(skin) { Stream stream = storage.GetStream(filename); if (stream != null) @@ -37,10 +39,17 @@ namespace osu.Game.Skinning else Configuration = new SkinConfiguration(); - Samples = audioManager.GetSampleManager(storage); + Samples = audioManager.GetSampleStore(storage); Textures = new TextureStore(new TextureLoaderStore(storage)); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + Textures?.Dispose(); + Samples?.Dispose(); + } + public override Drawable GetDrawableComponent(string componentName) { switch (componentName) @@ -48,17 +57,28 @@ namespace osu.Game.Skinning case "Play/Miss": componentName = "hit0"; break; + case "Play/Meh": componentName = "hit50"; break; + case "Play/Good": componentName = "hit100"; break; + case "Play/Great": componentName = "hit300"; break; + case "Play/osu/number-text": - return !hasFont(Configuration.HitCircleFont) ? null : new LegacySpriteText(Textures, Configuration.HitCircleFont) { Scale = new Vector2(0.96f) }; + return !hasFont(Configuration.HitCircleFont) + ? null + : new LegacySpriteText(Textures, Configuration.HitCircleFont) + { + Scale = new Vector2(0.96f), + // Spacing value was reverse-engineered from the ratio of the rendered sprite size in the visual inspector vs the actual texture size + Spacing = new Vector2(-Configuration.HitCircleOverlap * 0.89f, 0) + }; } var texture = GetTexture(componentName); @@ -74,6 +94,7 @@ namespace osu.Game.Skinning float ratio = 2; var texture = Textures.Get($"{componentName}@2x"); + if (texture == null) { ratio = 1; @@ -101,9 +122,10 @@ namespace osu.Game.Skinning bool hasExtension = filename.Contains('.'); string lastPiece = filename.Split('/').Last(); + var legacyName = filename.StartsWith("Gameplay/taiko/") ? "taiko-" + lastPiece : lastPiece; - var file = source.Files.FirstOrDefault(f => - string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), lastPiece, StringComparison.InvariantCultureIgnoreCase)); + var file = source.Files.Find(f => + string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), legacyName, StringComparison.InvariantCultureIgnoreCase)); return file?.FileInfo.StoragePath; } @@ -119,6 +141,8 @@ namespace osu.Game.Skinning return path == null ? null : underlyingStore.GetStream(path); } + public IEnumerable GetAvailableResources() => source.Files.Select(f => f.Filename); + byte[] IResourceStore.Get(string name) => GetAsync(name).Result; public Task GetAsync(string name) @@ -175,6 +199,7 @@ namespace osu.Game.Skinning float ratio = 36; var texture = textures.Get($"{textureName}@2x"); + if (texture == null) { ratio = 18; diff --git a/osu.Game/Skinning/LegacySkinDecoder.cs b/osu.Game/Skinning/LegacySkinDecoder.cs index 67a031fb50..ecb112955c 100644 --- a/osu.Game/Skinning/LegacySkinDecoder.cs +++ b/osu.Game/Skinning/LegacySkinDecoder.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps.Formats; @@ -16,37 +16,45 @@ namespace osu.Game.Skinning { line = StripComments(line); + var pair = SplitKeyVal(line); + switch (section) { case Section.General: - { - var pair = SplitKeyVal(line); - switch (pair.Key) { case @"Name": skin.SkinInfo.Name = pair.Value; break; + case @"Author": skin.SkinInfo.Creator = pair.Value; break; + + case @"CursorExpand": + skin.CursorExpand = pair.Value != "0"; + break; + + case @"SliderBorderSize": + skin.SliderBorderSize = Parsing.ParseFloat(pair.Value); + break; } break; - } - case Section.Fonts: - { - var pair = SplitKeyVal(line); + case Section.Fonts: switch (pair.Key) { case "HitCirclePrefix": skin.HitCircleFont = pair.Value; break; + + case "HitCircleOverlap": + skin.HitCircleOverlap = int.Parse(pair.Value); + break; } break; - } } base.ParseLine(skin, section, line); diff --git a/osu.Game/Skinning/LocalSkinOverrideContainer.cs b/osu.Game/Skinning/LocalSkinOverrideContainer.cs index 25d9442e6f..37f4cc28a2 100644 --- a/osu.Game/Skinning/LocalSkinOverrideContainer.cs +++ b/osu.Game/Skinning/LocalSkinOverrideContainer.cs @@ -1,10 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; @@ -12,61 +12,60 @@ using osu.Game.Configuration; namespace osu.Game.Skinning { + /// + /// A container which overrides existing skin options with beatmap-local values. + /// public class LocalSkinOverrideContainer : Container, ISkinSource { public event Action SourceChanged; - private Bindable beatmapSkins = new Bindable(); - private Bindable beatmapHitsounds = new Bindable(); + private readonly Bindable beatmapSkins = new Bindable(); + private readonly Bindable beatmapHitsounds = new Bindable(); + + private readonly ISkin skin; + + private ISkinSource fallbackSource; + + public LocalSkinOverrideContainer(ISkin skin) + { + this.skin = skin; + } public Drawable GetDrawableComponent(string componentName) { Drawable sourceDrawable; - if (beatmapSkins && (sourceDrawable = source.GetDrawableComponent(componentName)) != null) + if (beatmapSkins.Value && (sourceDrawable = skin.GetDrawableComponent(componentName)) != null) return sourceDrawable; + return fallbackSource?.GetDrawableComponent(componentName); } public Texture GetTexture(string componentName) { Texture sourceTexture; - if (beatmapSkins && (sourceTexture = source.GetTexture(componentName)) != null) + if (beatmapSkins.Value && (sourceTexture = skin.GetTexture(componentName)) != null) return sourceTexture; + return fallbackSource.GetTexture(componentName); } public SampleChannel GetSample(string sampleName) { SampleChannel sourceChannel; - if (beatmapHitsounds && (sourceChannel = source.GetSample(sampleName)) != null) + if (beatmapHitsounds.Value && (sourceChannel = skin.GetSample(sampleName)) != null) return sourceChannel; + return fallbackSource?.GetSample(sampleName); } - public TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration { - TValue? val = null; - if ((source as Skin)?.Configuration is TConfiguration conf) - val = query?.Invoke(conf); + TValue val; + if ((skin as Skin)?.Configuration is TConfiguration conf) + if (beatmapSkins.Value && (val = query.Invoke(conf)) != null) + return val; - return val ?? fallbackSource?.GetValue(query); - } - - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class - { - TValue val = null; - if ((source as Skin)?.Configuration is TConfiguration conf) - val = query?.Invoke(conf); - - return val ?? fallbackSource?.GetValue(query); - } - - private readonly ISkinSource source; - private ISkinSource fallbackSource; - - public LocalSkinOverrideContainer(ISkinSource source) - { - this.source = source; + return fallbackSource == null ? default : fallbackSource.GetValue(query); } private void onSourceChanged() => SourceChanged?.Invoke(); @@ -76,31 +75,27 @@ namespace osu.Game.Skinning var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); fallbackSource = dependencies.Get(); + if (fallbackSource != null) + fallbackSource.SourceChanged += onSourceChanged; + dependencies.CacheAs(this); + var config = dependencies.Get(); + + config.BindWith(OsuSetting.BeatmapSkins, beatmapSkins); + config.BindWith(OsuSetting.BeatmapHitsounds, beatmapHitsounds); + + beatmapSkins.BindValueChanged(_ => onSourceChanged()); + beatmapHitsounds.BindValueChanged(_ => onSourceChanged()); + return dependencies; } - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) - { - beatmapSkins = config.GetBindable(OsuSetting.BeatmapSkins); - beatmapSkins.BindValueChanged(_ => onSourceChanged()); - - beatmapHitsounds = config.GetBindable(OsuSetting.BeatmapHitsounds); - beatmapHitsounds.BindValueChanged(_ => onSourceChanged(), true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (fallbackSource != null) - fallbackSource.SourceChanged += onSourceChanged; - } - protected override void Dispose(bool isDisposing) { + // Must be done before base.Dispose() + SourceChanged = null; + base.Dispose(isDisposing); if (fallbackSource != null) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 29130f45df..09c0d3d0bc 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Audio.Sample; @@ -8,25 +8,20 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Skinning { - public abstract class Skin : IDisposable, ISkinSource + public abstract class Skin : IDisposable, ISkin { public readonly SkinInfo SkinInfo; public virtual SkinConfiguration Configuration { get; protected set; } - public event Action SourceChanged; - public abstract Drawable GetDrawableComponent(string componentName); public abstract SampleChannel GetSample(string sampleName); public abstract Texture GetTexture(string componentName); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class - => Configuration is TConfiguration conf ? query?.Invoke(conf) : null; - - public TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct - => Configuration is TConfiguration conf ? query?.Invoke(conf) : null; + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration + => Configuration is TConfiguration conf ? query.Invoke(conf) : default; protected Skin(SkinInfo skin) { @@ -52,6 +47,7 @@ namespace osu.Game.Skinning { if (isDisposed) return; + isDisposed = true; } diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs index 7d354d108c..043622f8ce 100644 --- a/osu.Game/Skinning/SkinConfiguration.cs +++ b/osu.Game/Skinning/SkinConfiguration.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; using osu.Game.Beatmaps.Formats; @@ -11,10 +11,22 @@ namespace osu.Game.Skinning { public readonly SkinInfo SkinInfo = new SkinInfo(); - public List ComboColours { get; set; } = new List(); + public List ComboColours { get; set; } = new List + { + new Color4(17, 136, 170, 255), + new Color4(102, 136, 0, 255), + new Color4(204, 102, 0, 255), + new Color4(121, 9, 13, 255) + }; public Dictionary CustomColours { get; set; } = new Dictionary(); public string HitCircleFont { get; set; } = "default"; + + public int HitCircleOverlap { get; set; } + + public float? SliderBorderSize { get; set; } + + public bool? CursorExpand { get; set; } = true; } } diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs index c9a3b0e94c..8a7019e1a3 100644 --- a/osu.Game/Skinning/SkinFileInfo.cs +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -1,16 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.ComponentModel.DataAnnotations; -using System.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; using osu.Game.IO; namespace osu.Game.Skinning { - public class SkinFileInfo : INamedFileInfo + public class SkinFileInfo : INamedFileInfo, IHasPrimaryKey { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public int SkinInfoID { get; set; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 47dad60d88..187ea910a7 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -1,30 +1,35 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Configuration; using osu.Game.Database; namespace osu.Game.Skinning { public class SkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete { - [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } public string Name { get; set; } + public string Hash { get; set; } + public string Creator { get; set; } public List Files { get; set; } + public List Settings { get; set; } + public bool DeletePending { get; set; } + public string FullName => $"\"{Name}\" by {Creator}"; + public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; public bool Equals(SkinInfo other) => other != null && ID == other.ID; - public override string ToString() => $"\"{Name}\" by {Creator}"; + public override string ToString() => FullName; } } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index bd694e443a..73cc47ea47 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -1,14 +1,16 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; @@ -26,8 +28,32 @@ namespace osu.Game.Skinning public override string[] HandledExtensions => new[] { ".osk" }; + protected override string[] HashableFileTypes => new[] { ".ini" }; + protected override string ImportFromStablePath => "Skins"; + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) + : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) + { + this.audio = audio; + + ItemRemoved += removedInfo => + { + // check the removed skin is not the current user choice. if it is, switch back to default. + if (removedInfo.ID == CurrentSkinInfo.Value.ID) + CurrentSkinInfo.Value = SkinInfo.Default; + }; + + CurrentSkinInfo.ValueChanged += skin => CurrentSkin.Value = getSkin(skin.NewValue); + CurrentSkin.ValueChanged += skin => + { + if (skin.NewValue.SkinInfo != CurrentSkinInfo.Value) + throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); + + SourceChanged?.Invoke(); + }; + } + /// /// Returns a list of all usable s. Includes the special default skin plus all skins from . /// @@ -45,24 +71,14 @@ namespace osu.Game.Skinning /// A list of available . public List GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); - protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo - { - Name = archive.Name - }; + protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; - protected override void Populate(SkinInfo model, ArchiveReader archive) + protected override async Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { - base.Populate(model, archive); - populate(model); - } + await base.Populate(model, archive, cancellationToken); + + Skin reference = getSkin(model); - /// - /// Populate a from its (if possible). - /// - /// - private void populate(SkinInfo model) - { - Skin reference = GetSkin(model); if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name)) { model.Name = reference.Configuration.SkinInfo.Name; @@ -80,7 +96,7 @@ namespace osu.Game.Skinning /// /// The skin to lookup. /// A instance correlating to the provided . - public Skin GetSkin(SkinInfo skinInfo) + private Skin getSkin(SkinInfo skinInfo) { if (skinInfo == SkinInfo.Default) return new DefaultSkin(); @@ -88,28 +104,6 @@ namespace osu.Game.Skinning return new LegacySkin(skinInfo, Files.Store, audio); } - public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost, AudioManager audio) - : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) - { - this.audio = audio; - - ItemRemoved += removedInfo => - { - // check the removed skin is not the current user choice. if it is, switch back to default. - if (removedInfo.ID == CurrentSkinInfo.Value.ID) - CurrentSkinInfo.Value = SkinInfo.Default; - }; - - CurrentSkinInfo.ValueChanged += info => CurrentSkin.Value = GetSkin(info); - CurrentSkin.ValueChanged += skin => - { - if (skin.SkinInfo != CurrentSkinInfo.Value) - throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead."); - - SourceChanged?.Invoke(); - }; - } - /// /// Perform a lookup query on available s. /// @@ -125,8 +119,6 @@ namespace osu.Game.Skinning public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName); - public TValue GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : class => CurrentSkin.Value.GetValue(query); - - public TValue? GetValue(Func query) where TConfiguration : SkinConfiguration where TValue : struct => CurrentSkin.Value.GetValue(query); + public TValue GetValue(Func query) where TConfiguration : SkinConfiguration => CurrentSkin.Value.GetValue(query); } } diff --git a/osu.Game/Skinning/SkinReloadableDrawable.cs b/osu.Game/Skinning/SkinReloadableDrawable.cs index 0b94697405..c09d5b1f92 100644 --- a/osu.Game/Skinning/SkinReloadableDrawable.cs +++ b/osu.Game/Skinning/SkinReloadableDrawable.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Allocation; diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs index e1dab17283..153eeda130 100644 --- a/osu.Game/Skinning/SkinStore.cs +++ b/osu.Game/Skinning/SkinStore.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Linq; using Microsoft.EntityFrameworkCore; @@ -8,15 +8,15 @@ using osu.Game.Database; namespace osu.Game.Skinning { - public class SkinStore : MutableDatabaseBackedStore + public class SkinStore : MutableDatabaseBackedStoreWithFileIncludes { public SkinStore(DatabaseContextFactory contextFactory, Storage storage = null) : base(contextFactory, storage) { } - protected override IQueryable AddIncludesForConsumption(IQueryable query) => - base.AddIncludesForConsumption(query) - .Include(s => s.Files).ThenInclude(f => f.FileInfo); + protected override IQueryable AddIncludesForDeletion(IQueryable query) => + base.AddIncludesForDeletion(query) + .Include(s => s.Settings); // don't include FileInfo. these are handled by the FileStore itself. } } diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 12bbde67fb..995cb15136 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics; @@ -15,6 +15,10 @@ namespace osu.Game.Skinning } } + /// + /// A drawable which can be skinned via an . + /// + /// The type of drawable. public class SkinnableDrawable : SkinReloadableDrawable where T : Drawable { @@ -23,48 +27,63 @@ namespace osu.Game.Skinning /// protected Drawable Drawable { get; private set; } - private readonly Func createDefault; - private readonly string componentName; private readonly bool restrictSize; /// - /// + /// Create a new skinnable drawable. /// /// The namespace-complete resource name for this skinnable element. /// A function to create the default skin implementation of this element. /// A conditional to decide whether to allow fallback to the default implementation if a skinned element is not present. /// Whether a user-skin drawable should be limited to the size of our parent. public SkinnableDrawable(string name, Func defaultImplementation, Func allowFallback = null, bool restrictSize = true) + : this(name, allowFallback, restrictSize) + { + createDefault = defaultImplementation; + } + + protected SkinnableDrawable(string name, Func allowFallback = null, bool restrictSize = true) : base(allowFallback) { componentName = name; - createDefault = defaultImplementation; this.restrictSize = restrictSize; RelativeSizeAxes = Axes.Both; } + private readonly Func createDefault; + + protected virtual T CreateDefault(string name) => createDefault(name); + + /// + /// Whether to apply size restrictions (specified via ) to the default implementation. + /// + protected virtual bool ApplySizeRestrictionsToDefault => false; + protected override void SkinChanged(ISkinSource skin, bool allowFallback) { Drawable = skin.GetDrawableComponent(componentName); + bool isDefault = false; + + if (Drawable == null && allowFallback) + { + Drawable = CreateDefault(componentName); + isDefault = true; + } + if (Drawable != null) { - if (restrictSize) + if (restrictSize && (!isDefault || ApplySizeRestrictionsToDefault)) { Drawable.RelativeSizeAxes = Axes.Both; Drawable.Size = Vector2.One; Drawable.Scale = Vector2.One; Drawable.FillMode = FillMode.Fit; } - } - else if (allowFallback) - Drawable = createDefault(componentName); - if (Drawable != null) - { Drawable.Origin = Anchor.Centre; Drawable.Anchor = Anchor.Centre; diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 4f881a917e..8e2b5cec98 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -13,14 +14,19 @@ namespace osu.Game.Skinning { public class SkinnableSound : SkinReloadableDrawable { - private readonly SampleInfo[] samples; + private readonly ISampleInfo[] hitSamples; private SampleChannel[] channels; private AudioManager audio; - public SkinnableSound(params SampleInfo[] samples) + public SkinnableSound(IEnumerable hitSamples) { - this.samples = samples; + this.hitSamples = hitSamples.ToArray(); + } + + public SkinnableSound(ISampleInfo hitSamples) + { + this.hitSamples = new[] { hitSamples }; } [BackgroundDependencyLoader] @@ -35,16 +41,16 @@ namespace osu.Game.Skinning protected override void SkinChanged(ISkinSource skin, bool allowFallback) { - channels = samples.Select(s => + channels = hitSamples.Select(s => { var ch = loadChannel(s, skin.GetSample); if (ch == null && allowFallback) - ch = loadChannel(s, audio.Sample.Get); + ch = loadChannel(s, audio.Samples.Get); return ch; }).Where(c => c != null).ToArray(); } - private SampleChannel loadChannel(SampleInfo info, Func getSampleFunction) + private SampleChannel loadChannel(ISampleInfo info, Func getSampleFunction) { foreach (var lookup in info.LookupNames) { @@ -58,5 +64,13 @@ namespace osu.Game.Skinning return null; } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + foreach (var c in channels) + c.Dispose(); + } } } diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs new file mode 100644 index 0000000000..ceb1ed0f70 --- /dev/null +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Skinning +{ + /// + /// A skinnable element which uses a stable sprite and can therefore share implementation logic. + /// + public class SkinnableSprite : SkinnableDrawable + { + protected override bool ApplySizeRestrictionsToDefault => true; + + [Resolved] + private TextureStore textures { get; set; } + + public SkinnableSprite(string name, Func allowFallback = null, bool restrictSize = true) + : base(name, allowFallback, restrictSize) + { + } + + protected override Sprite CreateDefault(string name) => new Sprite { Texture = textures.Get(name) }; + } +} diff --git a/osu.Game/Skinning/SkinnableSpriteText.cs b/osu.Game/Skinning/SkinnableSpriteText.cs index 8b09417bed..36e646d743 100644 --- a/osu.Game/Skinning/SkinnableSpriteText.cs +++ b/osu.Game/Skinning/SkinnableSpriteText.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework.Graphics.Sprites; @@ -30,6 +30,7 @@ namespace osu.Game.Skinning { if (text == value) return; + text = value; if (Drawable is IHasText textDrawable) diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index 9836d490af..c22ca0d8c0 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; diff --git a/osu.Game/Storyboards/CommandTimeline.cs b/osu.Game/Storyboards/CommandTimeline.cs index 64b0a45fcd..aa1f137cf3 100644 --- a/osu.Game/Storyboards/CommandTimeline.cs +++ b/osu.Game/Storyboards/CommandTimeline.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Caching; using osu.Framework.Graphics; @@ -52,6 +52,7 @@ namespace osu.Game.Storyboards { var result = StartTime.CompareTo(other.StartTime); if (result != 0) return result; + return EndTime.CompareTo(other.EndTime); } diff --git a/osu.Game/Storyboards/CommandTimelineGroup.cs b/osu.Game/Storyboards/CommandTimelineGroup.cs index b81b954f37..b1cc0436de 100644 --- a/osu.Game/Storyboards/CommandTimelineGroup.cs +++ b/osu.Game/Storyboards/CommandTimelineGroup.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osuTK.Graphics; diff --git a/osu.Game/Storyboards/CommandTrigger.cs b/osu.Game/Storyboards/CommandTrigger.cs index 2e355faf7c..011f345df2 100644 --- a/osu.Game/Storyboards/CommandTrigger.cs +++ b/osu.Game/Storyboards/CommandTrigger.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Storyboards { diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index bbb9491b0a..2b27a56844 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -1,12 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Threading; using osuTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Game.IO; +using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { @@ -20,12 +22,14 @@ namespace osu.Game.Storyboards.Drawables protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480); private bool passing = true; + public bool Passing { - get { return passing; } + get => passing; set { if (passing == value) return; + passing = value; updateLayerVisibility(); } @@ -34,6 +38,7 @@ namespace osu.Game.Storyboards.Drawables public override bool RemoveCompletedTransforms => false; private DependencyContainer dependencies; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -52,13 +57,20 @@ namespace osu.Game.Storyboards.Drawables }); } - [BackgroundDependencyLoader] - private void load(FileStore fileStore) + [BackgroundDependencyLoader(true)] + private void load(FileStore fileStore, GameplayClock clock, CancellationToken? cancellationToken) { + if (clock != null) + Clock = clock; + dependencies.Cache(new TextureStore(new TextureLoaderStore(fileStore.Store), false, scaleAdjust: 1)); foreach (var layer in Storyboard.Layers) + { + cancellationToken?.ThrowIfCancellationRequested(); + Add(layer.CreateDrawable()); + } } private void updateLayerVisibility() diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 8f860c5f53..de3077c025 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osuTK; using osu.Framework.Allocation; +using osu.Framework.Bindables; 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 @@ -64,20 +65,20 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore) { - var basePath = Animation.Path.ToLowerInvariant(); for (var frame = 0; frame < Animation.FrameCount; frame++) { - var framePath = basePath.Replace(".", frame + "."); + var framePath = Animation.Path.Replace(".", frame + "."); - var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; if (path == null) continue; var texture = textureStore.Get(path); AddFrame(texture, Animation.FrameDelay); } + Animation.ApplyTransforms(this); } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 74eaab33ca..fd2d441f34 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -1,13 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; namespace osu.Game.Storyboards.Drawables { - public class DrawableStoryboardLayer : Container + public class DrawableStoryboardLayer : LifetimeManagementContainer { public StoryboardLayer Layer { get; private set; } public bool Enabled; @@ -24,12 +25,14 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load() + private void load(CancellationToken? cancellationToken) { foreach (var element in Layer.Elements) { + cancellationToken?.ThrowIfCancellationRequested(); + if (element.IsDrawable) - Add(element.CreateDrawable()); + AddInternal(element.CreateDrawable()); } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 4f469ae593..ffd238d4e1 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -1,9 +1,10 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; @@ -24,11 +25,11 @@ namespace osu.Game.Storyboards.Drawables public DrawableStoryboardSample(StoryboardSample sample) { this.sample = sample; - LifetimeStart = sample.Time; + LifetimeStart = sample.StartTime; } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap) + private void load(IBindable beatmap) { // Try first with the full name, then attempt with no path channel = beatmap.Value.Skin.GetSample(sample.Path) ?? beatmap.Value.Skin.GetSample(Path.ChangeExtension(sample.Path, null)); @@ -42,27 +43,27 @@ namespace osu.Game.Storyboards.Drawables base.Update(); // TODO: this logic will need to be consolidated with other game samples like hit sounds. - if (Time.Current < sample.Time) + if (Time.Current < sample.StartTime) { // We've rewound before the start time of the sample channel?.Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) - LifetimeStart = sample.Time; + LifetimeStart = sample.StartTime; LifetimeEnd = double.MaxValue; } - else if (Time.Current - Time.Elapsed < sample.Time) + else if (Time.Current - Time.Elapsed < sample.StartTime) { // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future - if (Time.Current - sample.Time < allowable_late_start) + if (Time.Current - sample.StartTime < allowable_late_start) channel?.Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) LifetimeStart = double.MinValue; - LifetimeEnd = sample.Time; + LifetimeEnd = sample.StartTime; } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 485f9bf61c..5f1f5ddacb 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using osuTK; using osu.Framework.Allocation; +using osu.Framework.Bindables; 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 @@ -63,10 +64,9 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindableBeatmap beatmap, TextureStore textureStore) + private void load(IBindable beatmap, TextureStore textureStore) { - var spritePath = Sprite.Path.ToLowerInvariant(); - var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs index de8a1c2e71..7e31e1135e 100644 --- a/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs +++ b/osu.Game/Storyboards/Drawables/DrawablesExtensions.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; diff --git a/osu.Game/Storyboards/Drawables/IFlippable.cs b/osu.Game/Storyboards/Drawables/IFlippable.cs index 94cf0a891d..9e12de5833 100644 --- a/osu.Game/Storyboards/Drawables/IFlippable.cs +++ b/osu.Game/Storyboards/Drawables/IFlippable.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index 18424339e6..c4c150a8a4 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; @@ -10,6 +10,8 @@ namespace osu.Game.Storyboards string Path { get; } bool IsDrawable { get; } + double StartTime { get; } + Drawable CreateDrawable(); } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index c3bce27b56..3d988c5fe3 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -1,15 +1,14 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; -using System; namespace osu.Game.Storyboards { - public class Storyboard : IDisposable + public class Storyboard { private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; @@ -56,29 +55,5 @@ namespace osu.Game.Storyboards drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } - - #region Disposal - - ~Storyboard() - { - Dispose(false); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - private bool isDisposed; - - protected virtual void Dispose(bool isDisposing) - { - if (isDisposed) - return; - isDisposed = true; - } - - #endregion } } diff --git a/osu.Game/Storyboards/StoryboardAnimation.cs b/osu.Game/Storyboards/StoryboardAnimation.cs index b1c5720d99..1a4b6bb923 100644 --- a/osu.Game/Storyboards/StoryboardAnimation.cs +++ b/osu.Game/Storyboards/StoryboardAnimation.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; diff --git a/osu.Game/Storyboards/StoryboardLayer.cs b/osu.Game/Storyboards/StoryboardLayer.cs index 402c2c0add..d15f771534 100644 --- a/osu.Game/Storyboards/StoryboardLayer.cs +++ b/osu.Game/Storyboards/StoryboardLayer.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Game.Storyboards.Drawables; using System.Collections.Generic; @@ -13,8 +13,7 @@ namespace osu.Game.Storyboards public bool EnabledWhenPassing = true; public bool EnabledWhenFailing = true; - private readonly List elements = new List(); - public IEnumerable Elements => elements; + public List Elements = new List(); public StoryboardLayer(string name, int depth) { @@ -24,7 +23,7 @@ namespace osu.Game.Storyboards public void Add(IStoryboardElement element) { - elements.Add(element); + Elements.Add(element); } public DrawableStoryboardLayer CreateDrawable() diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index c34a39a7bf..24231cdca6 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; using osu.Game.Storyboards.Drawables; @@ -11,13 +11,14 @@ namespace osu.Game.Storyboards public string Path { get; set; } public bool IsDrawable => true; - public double Time; + public double StartTime { get; } + public float Volume; public StoryboardSample(string path, double time, float volume) { Path = path; - Time = time; + StartTime = time; Volume = volume; } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 173f403cc2..8f8ec22aae 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK; using osu.Framework.Graphics; @@ -34,6 +34,7 @@ namespace osu.Game.Storyboards public bool HasCommands => TimelineGroup.HasCommands || loops.Any(l => l.HasCommands); private delegate void DrawablePropertyInitializer(Drawable drawable, T value); + private delegate void DrawableTransformer(Drawable drawable, T value, double duration, Easing easing); public StoryboardSprite(string path, Anchor origin, Vector2 initialPosition) @@ -70,8 +71,7 @@ namespace osu.Game.Storyboards applyCommands(drawable, getCommands(g => g.Alpha, triggeredGroups), (d, value) => d.Alpha = value, (d, value, duration, easing) => d.FadeTo(value, duration, easing)); applyCommands(drawable, getCommands(g => g.BlendingMode, triggeredGroups), (d, value) => d.Blending = value, (d, value, duration, easing) => d.TransformBlendingMode(value, duration), false); - var flippable = drawable as IFlippable; - if (flippable != null) + if (drawable is IFlippable flippable) { applyCommands(drawable, getCommands(g => g.FlipH, triggeredGroups), (d, value) => flippable.FlipH = value, (d, value, duration, easing) => flippable.TransformFlipH(value, duration), false); applyCommands(drawable, getCommands(g => g.FlipV, triggeredGroups), (d, value) => flippable.FlipV = value, (d, value, duration, easing) => flippable.TransformFlipV(value, duration), false); @@ -82,6 +82,7 @@ namespace osu.Game.Storyboards where T : struct { var initialized = false; + foreach (var command in commands.OrderBy(l => l)) { if (!initialized) @@ -90,6 +91,7 @@ namespace osu.Game.Storyboards initializeProperty.Invoke(drawable, command.StartValue); initialized = true; } + using (drawable.BeginAbsoluteSequence(command.StartTime)) { transform(drawable, command.StartValue, 0, Easing.None); diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index ff57ef41c5..6a5e17eb38 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -35,10 +35,12 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int mappingCounter = 0; + while (true) { if (mappingCounter >= ourResult.Mappings.Count && mappingCounter >= expectedResult.Mappings.Count) break; + if (mappingCounter >= ourResult.Mappings.Count) Assert.Fail($"A conversion did not generate any hitobjects, but should have, for hitobject at time: {expectedResult.Mappings[mappingCounter].StartTime}\n"); else if (mappingCounter >= expectedResult.Mappings.Count) @@ -60,10 +62,12 @@ namespace osu.Game.Tests.Beatmaps Assert.Multiple(() => { int objectCounter = 0; + while (true) { if (objectCounter >= ourMapping.Objects.Count && objectCounter >= expectedMapping.Objects.Count) break; + if (objectCounter >= ourMapping.Objects.Count) Assert.Fail($"The conversion did not generate a hitobject, but should have, for hitobject at time: {expectedMapping.StartTime}:\n" + $"Expected: {JsonConvert.SerializeObject(expectedMapping.Objects[objectCounter])}\n"); @@ -189,7 +193,10 @@ namespace osu.Game.Tests.Beatmaps public List Objects = new List(); [JsonProperty("Objects")] - private List setObjects { set => Objects = value; } + private List setObjects + { + set => Objects = value; + } public virtual bool Equals(ConvertMapping other) => StartTime.Equals(other?.StartTime); } diff --git a/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs new file mode 100644 index 0000000000..108fa8ff71 --- /dev/null +++ b/osu.Game/Tests/Beatmaps/DifficultyCalculatorTest.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Reflection; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Tests.Beatmaps +{ + [TestFixture] + public abstract class DifficultyCalculatorTest + { + private const string resource_namespace = "Testing.Beatmaps"; + + protected abstract string ResourceAssembly { get; } + + protected void Test(double expected, string name, params Mod[] mods) + => Assert.AreEqual(expected, CreateDifficultyCalculator(getBeatmap(name)).Calculate(mods).StarRating); + + private WorkingBeatmap getBeatmap(string name) + { + using (var resStream = openResource($"{resource_namespace}.{name}.osu")) + using (var stream = new StreamReader(resStream)) + { + var decoder = Decoder.GetDecoder(stream); + ((LegacyBeatmapDecoder)decoder).ApplyOffsets = false; + + var working = new TestWorkingBeatmap(decoder.Decode(stream)); + working.BeatmapInfo.Ruleset = CreateRuleset().RulesetInfo; + + return working; + } + } + + private Stream openResource(string name) + { + var localPath = Path.GetDirectoryName(Uri.UnescapeDataString(new UriBuilder(Assembly.GetExecutingAssembly().CodeBase).Path)); + return Assembly.LoadFrom(Path.Combine(localPath, $"{ResourceAssembly}.dll")).GetManifestResourceStream($@"{ResourceAssembly}.Resources.{name}"); + } + + protected abstract DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap); + + protected abstract Ruleset CreateRuleset(); + } +} diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index 6bad08baaa..b77a8508ad 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.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 +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.IO; using System.Text; using osu.Game.Beatmaps; @@ -21,6 +22,18 @@ namespace osu.Game.Tests.Beatmaps HitObjects = baseBeatmap.HitObjects; BeatmapInfo.Ruleset = ruleset; + BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; + BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; + BeatmapInfo.BeatmapSet.OnlineInfo = new BeatmapSetOnlineInfo + { + Status = BeatmapSetOnlineStatus.Ranked, + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg", + Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg", + List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg" + } + }; } private static Beatmap createTestBeatmap() @@ -30,8 +43,7 @@ namespace osu.Game.Tests.Beatmaps return Decoder.GetDecoder(reader).Decode(reader); } - private const string test_beatmap_data = -@"osu file format v14 + private const string test_beatmap_data = @"osu file format v14 [General] AudioLeadIn: 500 diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 2f5c887726..0ef35879e3 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -1,29 +1,30 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; -using osu.Game.Rulesets; namespace osu.Game.Tests.Beatmaps { public class TestWorkingBeatmap : WorkingBeatmap { - public TestWorkingBeatmap(RulesetInfo ruleset) - : this(new TestBeatmap(ruleset)) - { - } + private readonly IBeatmap beatmap; + /// + /// Create an instance which provides the when requested. + /// + /// The beatmap public TestWorkingBeatmap(IBeatmap beatmap) - : base(beatmap.BeatmapInfo) + : base(beatmap.BeatmapInfo, null) { this.beatmap = beatmap; } - private readonly IBeatmap beatmap; protected override IBeatmap GetBeatmap() => beatmap; + protected override Texture GetBackground() => null; + protected override Track GetTrack() => null; } } diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 2308e541c1..bfbf7bb9da 100644 --- a/osu.Game/Tests/CleanRunHeadlessGameHost.cs +++ b/osu.Game/Tests/CleanRunHeadlessGameHost.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Platform; @@ -13,6 +13,11 @@ namespace osu.Game.Tests public CleanRunHeadlessGameHost(string gameName = @"", bool bindIPC = false, bool realtime = true) : base(gameName, bindIPC, realtime) { + } + + protected override void SetupForRun() + { + base.SetupForRun(); Storage.DeleteDirectory(string.Empty); } } diff --git a/osu.Game/Tests/OsuTestBrowser.cs b/osu.Game/Tests/OsuTestBrowser.cs index 217af8eb77..71b0b02fa6 100644 --- a/osu.Game/Tests/OsuTestBrowser.cs +++ b/osu.Game/Tests/OsuTestBrowser.cs @@ -1,7 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Screens.Backgrounds; @@ -14,10 +16,10 @@ namespace osu.Game.Tests { base.LoadComplete(); - LoadComponentAsync(new BackgroundScreenDefault + LoadComponentAsync(new ScreenStack(new BackgroundScreenDefault { Colour = OsuColour.Gray(0.5f) }) { - Colour = OsuColour.Gray(0.5f), - Depth = 10 + Depth = 10, + RelativeSizeAxes = Axes.Both, }, AddInternal); // Have to construct this here, rather than in the constructor, because diff --git a/osu.Game/Tests/Visual/AllPlayersTestScene.cs b/osu.Game/Tests/Visual/AllPlayersTestScene.cs new file mode 100644 index 0000000000..b7d1979b0d --- /dev/null +++ b/osu.Game/Tests/Visual/AllPlayersTestScene.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + /// + /// A base class which runs test for all available rulesets. + /// Steps to be run for each ruleset should be added via . + /// + public abstract class AllPlayersTestScene : RateAdjustedBeatmapTestScene + { + protected Player Player; + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + foreach (var r in rulesets.AvailableRulesets) + { + Player p = null; + AddStep(r.Name, () => p = loadPlayerFor(r)); + AddUntilStep("player loaded", () => + { + if (p?.IsLoaded == true) + { + p = null; + return true; + } + + return false; + }); + + AddCheckSteps(); + } + + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; + } + + protected abstract void AddCheckSteps(); + + private Player loadPlayerFor(RulesetInfo rulesetInfo) + { + Ruleset.Value = rulesetInfo; + var ruleset = rulesetInfo.CreateInstance(); + + var working = CreateWorkingBeatmap(rulesetInfo); + + Beatmap.Value = working; + Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + + Player?.Exit(); + Player = null; + + Player = CreatePlayer(ruleset); + + LoadScreen(Player); + + return Player; + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); + } +} diff --git a/osu.Game/Tests/Visual/EditorClockTestCase.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs similarity index 74% rename from osu.Game/Tests/Visual/EditorClockTestCase.cs rename to osu.Game/Tests/Visual/EditorClockTestScene.cs index 479ed385e2..58a443ed3d 100644 --- a/osu.Game/Tests/Visual/EditorClockTestCase.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -14,12 +15,12 @@ namespace osu.Game.Tests.Visual /// Provides a clock, beat-divisor, and scrolling capability for test cases of editor components that /// are preferrably tested within the presence of a clock and seek controls. /// - public abstract class EditorClockTestCase : OsuTestCase + public abstract class EditorClockTestScene : OsuTestScene { protected readonly BindableBeatDivisor BeatDivisor = new BindableBeatDivisor(); - protected readonly EditorClock Clock; + protected new readonly EditorClock Clock; - protected EditorClockTestCase() + protected EditorClockTestScene() { Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false }; } @@ -41,10 +42,10 @@ namespace osu.Game.Tests.Visual Beatmap.BindValueChanged(beatmapChanged, true); } - private void beatmapChanged(WorkingBeatmap working) + private void beatmapChanged(ValueChangedEvent e) { - Clock.ControlPointInfo = working.Beatmap.ControlPointInfo; - Clock.ChangeSource((IAdjustableClock)working.Track ?? new StopwatchClock()); + Clock.ControlPointInfo = e.NewValue.Beatmap.ControlPointInfo; + Clock.ChangeSource((IAdjustableClock)e.NewValue.Track ?? new StopwatchClock()); Clock.ProcessFrame(); } diff --git a/osu.Game/Tests/Visual/EditorTestCase.cs b/osu.Game/Tests/Visual/EditorTestScene.cs similarity index 53% rename from osu.Game/Tests/Visual/EditorTestCase.cs rename to osu.Game/Tests/Visual/EditorTestScene.cs index 2d02509b58..75bbb3e110 100644 --- a/osu.Game/Tests/Visual/EditorTestCase.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -1,22 +1,21 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Game.Rulesets; using osu.Game.Screens.Edit; -using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { - public abstract class EditorTestCase : ScreenTestCase + public abstract class EditorTestScene : ScreenTestScene { public override IReadOnlyList RequiredTypes => new[] { typeof(Editor), typeof(EditorScreen) }; private readonly Ruleset ruleset; - protected EditorTestCase(Ruleset ruleset) + protected EditorTestScene(Ruleset ruleset) { this.ruleset = ruleset; } @@ -24,9 +23,9 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - Beatmap.Value = new TestWorkingBeatmap(ruleset.RulesetInfo); + Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo); - LoadComponentAsync(new Editor(), LoadScreen); + LoadScreen(new Editor()); } } } diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs deleted file mode 100644 index 01676ad56e..0000000000 --- a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.Testing.Input; - -namespace osu.Game.Tests.Visual -{ - public abstract class ManualInputManagerTestCase : OsuTestCase - { - protected override Container Content => InputManager; - protected readonly ManualInputManager InputManager; - - protected ManualInputManagerTestCase() - { - base.Content.Add(InputManager = new ManualInputManager()); - ReturnUserInput(); - } - - /// - /// Returns input back to the user. - /// - protected void ReturnUserInput() - { - AddStep("Return user input", () => InputManager.UseParentInput = true); - } - } -} diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/ManualInputManagerTestScene.cs new file mode 100644 index 0000000000..86191609a4 --- /dev/null +++ b/osu.Game/Tests/Visual/ManualInputManagerTestScene.cs @@ -0,0 +1,111 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing.Input; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + public abstract class ManualInputManagerTestScene : OsuTestScene + { + protected override Container Content => content; + private readonly Container content; + + protected readonly ManualInputManager InputManager; + + private readonly TriangleButton buttonTest; + private readonly TriangleButton buttonLocal; + + protected ManualInputManagerTestScene() + { + base.Content.AddRange(new Drawable[] + { + InputManager = new ManualInputManager + { + UseParentInput = true, + Child = content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }, + }, + new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(5), + CornerRadius = 5, + Masking = true, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Margin = new MarginPadding(5), + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = "Input Priority" + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Margin = new MarginPadding(5), + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal, + + Children = new Drawable[] + { + buttonLocal = new TriangleButton + { + Text = "local", + Size = new Vector2(50, 30), + Action = returnUserInput + }, + buttonTest = new TriangleButton + { + Text = "test", + Size = new Vector2(50, 30), + Action = returnTestInput + }, + } + }, + } + }, + } + }, + }); + } + + protected override void Update() + { + base.Update(); + + buttonTest.Enabled.Value = InputManager.UseParentInput; + buttonLocal.Enabled.Value = !InputManager.UseParentInput; + } + + private void returnUserInput() => + InputManager.UseParentInput = true; + + private void returnTestInput() => + InputManager.UseParentInput = false; + } +} diff --git a/osu.Game/Tests/Visual/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/MultiplayerTestScene.cs new file mode 100644 index 0000000000..ffb431b4d3 --- /dev/null +++ b/osu.Game/Tests/Visual/MultiplayerTestScene.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Tests.Visual +{ + public abstract class MultiplayerTestScene : ScreenTestScene + { + [Cached] + private readonly Bindable currentRoom = new Bindable(new Room()); + + protected Room Room + { + get => currentRoom.Value; + set => currentRoom.Value = value; + } + + private CachedModelDependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + dependencies = new CachedModelDependencyContainer(base.CreateChildDependencies(parent)); + dependencies.Model.BindTo(currentRoom); + return dependencies; + } + } +} diff --git a/osu.Game/Tests/Visual/OsuTestCase.cs b/osu.Game/Tests/Visual/OsuTestCase.cs deleted file mode 100644 index 67a13bd850..0000000000 --- a/osu.Game/Tests/Visual/OsuTestCase.cs +++ /dev/null @@ -1,111 +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.Linq; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Configuration; -using osu.Framework.Platform; -using osu.Framework.Testing; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; - -namespace osu.Game.Tests.Visual -{ - public abstract class OsuTestCase : TestCase - { - private readonly OsuTestBeatmap beatmap = new OsuTestBeatmap(new DummyWorkingBeatmap()); - protected BindableBeatmap Beatmap => beatmap; - - protected readonly Bindable Ruleset = new Bindable(); - - protected DependencyContainer Dependencies { get; private set; } - - private readonly Lazy localStorage; - protected Storage LocalStorage => localStorage.Value; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - - Dependencies.CacheAs(beatmap); - Dependencies.CacheAs(beatmap); - - Dependencies.CacheAs(Ruleset); - Dependencies.CacheAs>(Ruleset); - - return Dependencies; - } - - protected OsuTestCase() - { - localStorage = new Lazy(() => new DesktopStorage($"{GetType().Name}-{Guid.NewGuid()}", null)); - } - - [BackgroundDependencyLoader] - private void load(AudioManager audioManager, RulesetStore rulesets) - { - beatmap.SetAudioManager(audioManager); - - Ruleset.Value = rulesets.AvailableRulesets.First(); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmap != null) - { - beatmap.Disabled = true; - beatmap.Value.Track.Stop(); - } - - if (localStorage.IsValueCreated) - { - try - { - localStorage.Value.DeleteDirectory("."); - } - catch - { - // we don't really care if this fails; it will just leave folders lying around from test runs. - } - } - } - - protected override ITestCaseTestRunner CreateRunner() => new OsuTestCaseTestRunner(); - - public class OsuTestCaseTestRunner : OsuGameBase, ITestCaseTestRunner - { - private TestCaseTestRunner.TestRunner runner; - - protected override void LoadAsyncComplete() - { - // this has to be run here rather than LoadComplete because - // TestCase.cs is checking the IsLoaded state (on another thread) and expects - // the runner to be loaded at that point. - Add(runner = new TestCaseTestRunner.TestRunner()); - } - - public void RunTestBlocking(TestCase test) => runner.RunTestBlocking(test); - } - - 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/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs new file mode 100644 index 0000000000..c8798448ae --- /dev/null +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -0,0 +1,284 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Textures; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Tests.Beatmaps; +using osuTK; + +namespace osu.Game.Tests.Visual +{ + public abstract class OsuTestScene : TestScene + { + [Cached(typeof(Bindable))] + [Cached(typeof(IBindable))] + private OsuTestBeatmap beatmap; + + protected BindableBeatmap Beatmap => beatmap; + + [Cached] + [Cached(typeof(IBindable))] + protected readonly Bindable Ruleset = new Bindable(); + + [Cached] + [Cached(Type = typeof(IBindable>))] + protected readonly Bindable> Mods = new Bindable>(Array.Empty()); + + protected new DependencyContainer Dependencies { get; private set; } + + private readonly Lazy localStorage; + protected Storage LocalStorage => localStorage.Value; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + // This is the earliest we can get OsuGameBase, which is used by the dummy working beatmap to find textures + var working = new DummyWorkingBeatmap(parent.Get(), parent.Get()); + + beatmap = new OsuTestBeatmap(working) + { + Default = working + }; + + return Dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + } + + protected OsuTestScene() + { + localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); + } + + [Resolved] + private AudioManager audio { get; set; } + + protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); + + protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => + CreateWorkingBeatmap(CreateBeatmap(ruleset)); + + protected virtual WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap) => + new ClockBackedTestWorkingBeatmap(beatmap, Clock, audio); + + [BackgroundDependencyLoader] + private void load(RulesetStore rulesets) + { + Ruleset.Value = rulesets.AvailableRulesets.First(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmap?.Value.TrackLoaded == true) + beatmap.Value.Track.Stop(); + + if (localStorage.IsValueCreated) + { + try + { + localStorage.Value.DeleteDirectory("."); + } + catch + { + // we don't really care if this fails; it will just leave folders lying around from test runs. + } + } + } + + protected override ITestSceneTestRunner CreateRunner() => new OsuTestSceneTestRunner(); + + public class ClockBackedTestWorkingBeatmap : TestWorkingBeatmap + { + private readonly Track track; + + private readonly TrackVirtualStore store; + + /// + /// Create an instance which creates a for the provided ruleset when requested. + /// + /// The target ruleset. + /// A clock which should be used instead of a stopwatch for virtual time progression. + /// Audio manager. Required if a reference clock isn't provided. + public ClockBackedTestWorkingBeatmap(RulesetInfo ruleset, IFrameBasedClock referenceClock, AudioManager audio) + : this(new TestBeatmap(ruleset), referenceClock, audio) + { + } + + /// + /// Create an instance which provides the when requested. + /// + /// The beatmap + /// An optional clock which should be used instead of a stopwatch for virtual time progression. + /// Audio manager. Required if a reference clock isn't provided. + /// The length of the returned virtual track. + public ClockBackedTestWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio, double length = 60000) + : base(beatmap) + { + if (referenceClock != null) + { + store = new TrackVirtualStore(referenceClock); + audio.AddItem(store); + track = store.GetVirtual(length); + } + else + track = audio?.Tracks.GetVirtual(length); + } + + public override void Dispose() + { + base.Dispose(); + store?.Dispose(); + } + + protected override Track GetTrack() => track; + + public class TrackVirtualStore : AudioCollectionManager, ITrackStore + { + private readonly IFrameBasedClock referenceClock; + + public TrackVirtualStore(IFrameBasedClock referenceClock) + { + this.referenceClock = referenceClock; + } + + public Track Get(string name) => throw new NotImplementedException(); + + public Task GetAsync(string name) => throw new NotImplementedException(); + + public Stream GetStream(string name) => throw new NotImplementedException(); + + public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + + public Track GetVirtual(double length = Double.PositiveInfinity) + { + var track = new TrackVirtualManual(referenceClock) { Length = length }; + AddItem(track); + return track; + } + } + + /// + /// A virtual track which tracks a reference clock. + /// + public class TrackVirtualManual : Track + { + private readonly IFrameBasedClock referenceClock; + + private readonly ManualClock clock = new ManualClock(); + + private bool running; + + /// + /// Local offset added to the reference clock to resolve correct time. + /// + private double offset; + + public TrackVirtualManual(IFrameBasedClock referenceClock) + { + this.referenceClock = referenceClock; + Length = double.PositiveInfinity; + } + + public override bool Seek(double seek) + { + offset = MathHelper.Clamp(seek, 0, Length); + lastReferenceTime = null; + + return offset == seek; + } + + public override void Start() + { + running = true; + } + + public override void Reset() + { + Seek(0); + base.Reset(); + } + + public override void Stop() + { + if (running) + { + running = false; + // on stopping, the current value should be transferred out of the clock, as we can no longer rely on + // the referenceClock (which will still be counting time). + offset = clock.CurrentTime; + lastReferenceTime = null; + } + } + + public override bool IsRunning => running; + + private double? lastReferenceTime; + + public override double CurrentTime => clock.CurrentTime; + + protected override void UpdateState() + { + base.UpdateState(); + + if (running) + { + double refTime = referenceClock.CurrentTime; + + if (!lastReferenceTime.HasValue) + { + // if the clock just started running, the current value should be transferred to the offset + // (to zero the progression of time). + offset -= refTime; + } + + lastReferenceTime = refTime; + } + + clock.CurrentTime = Math.Min((lastReferenceTime ?? 0) + offset, Length); + + if (CurrentTime >= Length) + { + Stop(); + RaiseCompleted(); + } + } + } + } + + public class OsuTestSceneTestRunner : OsuGameBase, ITestSceneTestRunner + { + private TestSceneTestRunner.TestRunner runner; + + protected override void LoadAsyncComplete() + { + // this has to be run here rather than LoadComplete because + // TestScene.cs is checking the IsLoaded state (on another thread) and expects + // the runner to be loaded at that point. + Add(runner = new TestSceneTestRunner.TestRunner()); + } + + public void RunTestBlocking(TestScene test) => runner.RunTestBlocking(test); + } + + private class OsuTestBeatmap : BindableBeatmap + { + public OsuTestBeatmap(WorkingBeatmap defaultValue) + : base(defaultValue) + { + } + } + } +} diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs similarity index 68% rename from osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs rename to osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index f893d8456b..2b177e264f 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestCase.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -13,25 +13,20 @@ using osu.Game.Screens.Edit.Compose; namespace osu.Game.Tests.Visual { [Cached(Type = typeof(IPlacementHandler))] - public abstract class PlacementBlueprintTestCase : OsuTestCase, IPlacementHandler + public abstract class PlacementBlueprintTestScene : OsuTestScene, IPlacementHandler { - private readonly Container hitObjectContainer; + protected Container HitObjectContainer; private PlacementBlueprint currentBlueprint; - protected PlacementBlueprintTestCase() + protected PlacementBlueprintTestScene() { - Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; - - Add(hitObjectContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(new StopwatchClock()) - }); + Add(HitObjectContainer = CreateHitObjectContainer()); } [BackgroundDependencyLoader] private void load() { + Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; Add(currentBlueprint = CreateBlueprint()); } @@ -49,7 +44,7 @@ namespace osu.Game.Tests.Visual public void EndPlacement(HitObject hitObject) { - hitObjectContainer.Add(CreateHitObject(hitObject)); + AddHitObject(CreateHitObject(hitObject)); Remove(currentBlueprint); Add(currentBlueprint = CreateBlueprint()); @@ -59,6 +54,10 @@ namespace osu.Game.Tests.Visual { } + protected virtual Container CreateHitObjectContainer() => new Container { RelativeSizeAxes = Axes.Both }; + + protected virtual void AddHitObject(DrawableHitObject hitObject) => HitObjectContainer.Add(hitObject); + protected abstract DrawableHitObject CreateHitObject(HitObject hitObject); protected abstract PlacementBlueprint CreateBlueprint(); } diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs new file mode 100644 index 0000000000..03e17a819c --- /dev/null +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public abstract class PlayerTestScene : RateAdjustedBeatmapTestScene + { + private readonly Ruleset ruleset; + + protected Player Player; + + protected PlayerTestScene(Ruleset ruleset) + { + this.ruleset = ruleset; + } + + [BackgroundDependencyLoader] + private void load() + { + OsuConfigManager manager; + Dependencies.Cache(manager = new OsuConfigManager(LocalStorage)); + manager.GetBindable(OsuSetting.DimLevel).Value = 1.0; + } + + [SetUpSteps] + public virtual void SetUpSteps() + { + AddStep(ruleset.RulesetInfo.Name, loadPlayer); + AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); + } + + protected virtual bool AllowFail => false; + + private void loadPlayer() + { + var beatmap = CreateBeatmap(ruleset.RulesetInfo); + + Beatmap.Value = CreateWorkingBeatmap(beatmap); + + if (!AllowFail) + Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + + Player = CreatePlayer(ruleset); + LoadScreen(Player); + } + + protected virtual Player CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); + } +} diff --git a/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs new file mode 100644 index 0000000000..921a1d9789 --- /dev/null +++ b/osu.Game/Tests/Visual/RateAdjustedBeatmapTestScene.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Tests.Visual +{ + /// + /// Test case which adjusts the beatmap's rate to match any speed adjustments in visual tests. + /// + public abstract class RateAdjustedBeatmapTestScene : ScreenTestScene + { + protected override void Update() + { + base.Update(); + + // note that this will override any mod rate application + Beatmap.Value.Track.TempoAdjust = Clock.Rate; + } + } +} diff --git a/osu.Game/Tests/Visual/ScreenTestCase.cs b/osu.Game/Tests/Visual/ScreenTestCase.cs deleted file mode 100644 index e610fafa7b..0000000000 --- a/osu.Game/Tests/Visual/ScreenTestCase.cs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Screens; -using osu.Game.Screens; - -namespace osu.Game.Tests.Visual -{ - /// - /// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions). - /// - public abstract class ScreenTestCase : OsuTestCase - { - private readonly TestOsuScreen baseScreen; - - protected ScreenTestCase() - { - Add(baseScreen = new TestOsuScreen()); - } - - protected void LoadScreen(OsuScreen screen) => baseScreen.LoadScreen(screen); - - public class TestOsuScreen : OsuScreen - { - private OsuScreen nextScreen; - - public void LoadScreen(OsuScreen screen) => Schedule(() => - { - nextScreen = screen; - - if (IsCurrentScreen) - { - Push(screen); - nextScreen = null; - } - else - MakeCurrent(); - }); - - protected override void OnResuming(Screen last) - { - base.OnResuming(last); - if (nextScreen != null) - LoadScreen(nextScreen); - } - } - } -} diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs new file mode 100644 index 0000000000..23f45e0d0f --- /dev/null +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Screens; + +namespace osu.Game.Tests.Visual +{ + /// + /// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions). + /// + public abstract class ScreenTestScene : ManualInputManagerTestScene + { + private readonly OsuScreenStack stack; + + private readonly Container content; + + protected override Container Content => content; + + protected ScreenTestScene() + { + base.Content.AddRange(new Drawable[] + { + stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, + content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + protected void LoadScreen(OsuScreen screen) + { + if (stack.CurrentScreen != null) + stack.Exit(); + stack.Push(screen); + } + } +} diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index 1819fdb2a0..bdad3d278c 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -1,8 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Lists; using osu.Game.Configuration; @@ -20,9 +20,17 @@ namespace osu.Game.Tests.Visual { public SortedList ControlPoints => scrollingInfo.Algorithm.ControlPoints; - public ScrollVisualisationMethod ScrollAlgorithm { set => scrollingInfo.Algorithm.Algorithm = value; } + public ScrollVisualisationMethod ScrollAlgorithm + { + set => scrollingInfo.Algorithm.Algorithm = value; + } - public double TimeRange { set => scrollingInfo.TimeRange.Value = value; } + public double TimeRange + { + set => scrollingInfo.TimeRange.Value = value; + } + + public IScrollingInfo ScrollingInfo => scrollingInfo; [Cached(Type = typeof(IScrollingInfo))] private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo(); @@ -66,9 +74,11 @@ namespace osu.Game.Tests.Visual case ScrollVisualisationMethod.Constant: implementation = new ConstantScrollAlgorithm(); break; + case ScrollVisualisationMethod.Overlapping: implementation = new OverlappingScrollAlgorithm(ControlPoints); break; + case ScrollVisualisationMethod.Sequential: implementation = new SequentialScrollAlgorithm(ControlPoints); break; diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs similarity index 82% rename from osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs rename to osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index 183bef7602..df3af2cc43 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestCase.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -10,14 +10,14 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Tests.Visual { - public abstract class SelectionBlueprintTestCase : OsuTestCase + public abstract class SelectionBlueprintTestScene : OsuTestScene { private SelectionBlueprint blueprint; protected override Container Content => content ?? base.Content; private readonly Container content; - protected SelectionBlueprintTestCase() + protected SelectionBlueprintTestScene() { base.Content.Add(content = new Container { diff --git a/osu.Game/Tests/Visual/TestCasePlayer.cs b/osu.Game/Tests/Visual/TestCasePlayer.cs deleted file mode 100644 index 47bf787bb5..0000000000 --- a/osu.Game/Tests/Visual/TestCasePlayer.cs +++ /dev/null @@ -1,135 +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.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; -using osu.Game.Tests.Beatmaps; -using osuTK.Graphics; - -namespace osu.Game.Tests.Visual -{ - public abstract class TestCasePlayer : ScreenTestCase - { - private readonly Ruleset ruleset; - - protected Player Player; - - protected TestCasePlayer(Ruleset ruleset) - { - this.ruleset = ruleset; - } - - protected TestCasePlayer() - { - } - - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - Add(new Box - { - RelativeSizeAxes = Framework.Graphics.Axes.Both, - Colour = Color4.Black, - Depth = int.MaxValue - }); - - if (ruleset != null) - { - Player p = null; - AddStep(ruleset.RulesetInfo.Name, () => p = loadPlayerFor(ruleset)); - AddUntilStep(() => ContinueCondition(p)); - } - else - { - foreach (var r in rulesets.AvailableRulesets) - { - Player p = null; - AddStep(r.Name, () => p = loadPlayerFor(r)); - AddUntilStep(() => ContinueCondition(p)); - - AddUntilStep(() => - { - p = null; - - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - workingWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked beatmaps"); - - AddUntilStep(() => - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - int count = 0; - - playerWeakReferences.ForEachAlive(_ => count++); - return count == 1; - }, "no leaked players"); - } - } - } - - protected virtual bool ContinueCondition(Player player) => player.IsLoaded; - - protected virtual IBeatmap CreateBeatmap(Ruleset ruleset) => new TestBeatmap(ruleset.RulesetInfo); - - private readonly WeakList workingWeakReferences = new WeakList(); - private readonly WeakList playerWeakReferences = new WeakList(); - - private Player loadPlayerFor(RulesetInfo ri) - { - Ruleset.Value = ri; - return loadPlayerFor(ri.CreateInstance()); - } - - private Player loadPlayerFor(Ruleset r) - { - var beatmap = CreateBeatmap(r); - var working = new TestWorkingBeatmap(beatmap); - - workingWeakReferences.Add(working); - - Beatmap.Value = working; - Beatmap.Value.Mods.Value = new[] { r.GetAllMods().First(m => m is ModNoFail) }; - - Player?.Exit(); - - var player = CreatePlayer(r); - - playerWeakReferences.Add(player); - - LoadComponentAsync(player, p => - { - Player = p; - LoadScreen(p); - }); - - return player; - } - - protected override void Update() - { - base.Update(); - - // note that this will override any mod rate application - Beatmap.Value.Track.Rate = Clock.Rate; - } - - protected virtual Player CreatePlayer(Ruleset ruleset) => new Player - { - AllowPause = false, - AllowLeadIn = false, - AllowResults = false, - }; - } -} diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs new file mode 100644 index 0000000000..b93a1466e0 --- /dev/null +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -0,0 +1,17 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Screens.Play; + +namespace osu.Game.Tests.Visual +{ + public class TestPlayer : Player + { + protected override bool PauseOnFocusLost => false; + + public TestPlayer(bool allowPause = true, bool showResults = true) + : base(allowPause, showResults) + { + } + } +} diff --git a/osu.Game/Tests/VisualTestRunner.cs b/osu.Game/Tests/VisualTestRunner.cs index 2340371aab..d63b3d48b2 100644 --- a/osu.Game/Tests/VisualTestRunner.cs +++ b/osu.Game/Tests/VisualTestRunner.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osu.Framework; diff --git a/osu.Game/Users/Avatar.cs b/osu.Game/Users/Avatar.cs deleted file mode 100644 index e6e1ba3c53..0000000000 --- a/osu.Game/Users/Avatar.cs +++ /dev/null @@ -1,46 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; - -namespace osu.Game.Users -{ - public class Avatar : Container - { - private readonly User user; - - /// - /// An avatar for specified user. - /// - /// The user. A null value will get a placeholder avatar. - public Avatar(User user = null) - { - this.user = user; - } - - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - if (textures == null) - throw new ArgumentNullException(nameof(textures)); - - Texture texture = null; - if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); - if (texture == null) texture = textures.Get(@"Online/avatar-guest"); - - Add(new Sprite - { - RelativeSizeAxes = Axes.Both, - Texture = texture, - FillMode = FillMode.Fit, - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }); - } - } -} diff --git a/osu.Game/Users/Badge.cs b/osu.Game/Users/Badge.cs index 25ef8ffdf4..9d46a1992b 100644 --- a/osu.Game/Users/Badge.cs +++ b/osu.Game/Users/Badge.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using Newtonsoft.Json; diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index 80039eadad..1dcce6e870 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -1,14 +1,7 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using Newtonsoft.Json; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; namespace osu.Game.Users { @@ -26,53 +19,4 @@ namespace osu.Game.Users [JsonProperty(@"code")] public string FlagName; } - - public class DrawableFlag : Container, IHasTooltip - { - private readonly Sprite sprite; - private TextureStore textures; - - private Country country; - public Country Country - { - get { return country; } - set - { - if (value == country) - return; - - country = value; - - if (LoadState >= LoadState.Ready) - sprite.Texture = getFlagTexture(); - } - } - - public string TooltipText => country?.FullName; - - public DrawableFlag(Country country = null) - { - this.country = country; - - Children = new Drawable[] - { - sprite = new Sprite - { - RelativeSizeAxes = Axes.Both, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore ts) - { - if (ts == null) - throw new ArgumentNullException(nameof(ts)); - - textures = ts; - sprite.Texture = getFlagTexture(); - } - - private Texture getFlagTexture() => textures.Get($@"Flags/{country?.FlagName ?? @"__"}"); - } } diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs new file mode 100644 index 0000000000..ee3cf6331b --- /dev/null +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -0,0 +1,87 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Users.Drawables +{ + public class DrawableAvatar : Container + { + /// + /// Whether to open the user's profile when clicked. + /// + public readonly BindableBool OpenOnClick = new BindableBool(true); + + private readonly User user; + + [Resolved(CanBeNull = true)] + private OsuGame game { get; set; } + + /// + /// An avatar for specified user. + /// + /// The user. A null value will get a placeholder avatar. + public DrawableAvatar(User user = null) + { + this.user = user; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + if (textures == null) + throw new ArgumentNullException(nameof(textures)); + + Texture texture = null; + if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); + if (texture == null) texture = textures.Get(@"Online/avatar-guest"); + + ClickableArea clickableArea; + Add(clickableArea = new ClickableArea + { + RelativeSizeAxes = Axes.Both, + Child = new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = texture, + FillMode = FillMode.Fit, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + Action = openProfile + }); + + clickableArea.Enabled.BindTo(OpenOnClick); + } + + private void openProfile() + { + if (!OpenOnClick.Value) + return; + + if (user != null) + game?.ShowUser(user.Id); + } + + private class ClickableArea : OsuClickableContainer + { + public override string TooltipText => Enabled.Value ? @"View Profile" : null; + + protected override bool OnClick(ClickEvent e) + { + if (!Enabled.Value) + return false; + + return base.OnClick(e); + } + } + } +} diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs new file mode 100644 index 0000000000..368354e48e --- /dev/null +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; + +namespace osu.Game.Users.Drawables +{ + public class DrawableFlag : Sprite, IHasTooltip + { + private readonly Country country; + + public string TooltipText => country?.FullName; + + public DrawableFlag(Country country) + { + this.country = country; + } + + [BackgroundDependencyLoader] + private void load(TextureStore ts) + { + if (ts == null) + throw new ArgumentNullException(nameof(ts)); + + Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}"); + } + } +} diff --git a/osu.Game/Users/Drawables/UpdateableAvatar.cs b/osu.Game/Users/Drawables/UpdateableAvatar.cs new file mode 100644 index 0000000000..a49f2d079b --- /dev/null +++ b/osu.Game/Users/Drawables/UpdateableAvatar.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Users.Drawables +{ + /// + /// An avatar which can update to a new user when needed. + /// + public class UpdateableAvatar : ModelBackedDrawable + { + public User User + { + get => Model; + set => Model = value; + } + + public new bool Masking + { + get => base.Masking; + set => base.Masking = value; + } + + public new float CornerRadius + { + get => base.CornerRadius; + set => base.CornerRadius = value; + } + + public new EdgeEffectParameters EdgeEffect + { + get => base.EdgeEffect; + set => base.EdgeEffect = value; + } + + protected override bool TransformImmediately { get; } + + /// + /// Whether to show a default guest representation on null user (as opposed to nothing). + /// + public bool ShowGuestOnNull = true; + + /// + /// Whether to open the user's profile when clicked. + /// + public readonly BindableBool OpenOnClick = new BindableBool(true); + + public UpdateableAvatar(User user = null, bool hideImmediately = false) + { + TransformImmediately = hideImmediately; + User = user; + } + + protected override TransformSequence ApplyHideTransforms(Drawable drawable) => TransformImmediately ? drawable?.FadeOut() : base.ApplyHideTransforms(drawable); + + protected override Drawable CreateDrawable(User user) + { + if (user == null && !ShowGuestOnNull) + return null; + + var avatar = new DrawableAvatar(user) + { + RelativeSizeAxes = Axes.Both, + }; + + avatar.OnLoadComplete += d => d.FadeInFromZero(300, Easing.OutQuint); + avatar.OpenOnClick.BindTo(OpenOnClick); + + return avatar; + } + } +} diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs new file mode 100644 index 0000000000..78d1a8de20 --- /dev/null +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Users.Drawables +{ + public class UpdateableFlag : ModelBackedDrawable + { + public Country Country + { + get => Model; + set => Model = value; + } + + protected override bool TransformImmediately { get; } + + /// + /// Whether to show a place holder on null country. + /// + public bool ShowPlaceholderOnNull = true; + + public UpdateableFlag(Country country = null, bool hideImmediately = false) + { + TransformImmediately = hideImmediately; + Country = country; + } + + protected override TransformSequence ApplyHideTransforms(Drawable drawable) => TransformImmediately ? drawable?.FadeOut() : base.ApplyHideTransforms(drawable); + + protected override Drawable CreateDrawable(Country country) + { + if (country == null && !ShowPlaceholderOnNull) + return null; + + return new DrawableFlag(country) + { + RelativeSizeAxes = Axes.Both, + }; + } + } +} diff --git a/osu.Game/Users/Medal.cs b/osu.Game/Users/Medal.cs index 727eaefbc5..2e4f6de1d7 100644 --- a/osu.Game/Users/Medal.cs +++ b/osu.Game/Users/Medal.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Users { diff --git a/osu.Game/Users/Team.cs b/osu.Game/Users/Team.cs index 955bf8ec92..b56a083fdf 100644 --- a/osu.Game/Users/Team.cs +++ b/osu.Game/Users/Team.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. namespace osu.Game.Users { diff --git a/osu.Game/Users/UpdateableAvatar.cs b/osu.Game/Users/UpdateableAvatar.cs deleted file mode 100644 index 6c0b841abf..0000000000 --- a/osu.Game/Users/UpdateableAvatar.cs +++ /dev/null @@ -1,60 +0,0 @@ -// 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; - -namespace osu.Game.Users -{ - /// - /// An avatar which can update to a new user when needed. - /// - public class UpdateableAvatar : Container - { - private Drawable displayedAvatar; - - private User user; - - /// - /// Whether to show a default guest representation on null user (as opposed to nothing). - /// - public bool ShowGuestOnNull = true; - - public User User - { - get { return user; } - set - { - if (user?.Id == value?.Id) - return; - - user = value; - - if (IsLoaded) - updateAvatar(); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - updateAvatar(); - } - - private void updateAvatar() - { - displayedAvatar?.FadeOut(300); - displayedAvatar?.Expire(); - if (user != null || ShowGuestOnNull) - { - Add(displayedAvatar = new DelayedLoadWrapper( - new Avatar(user) - { - RelativeSizeAxes = Axes.Both, - OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint), - }) - ); - } - } - } -} diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs index a5d8c03a67..df41e194b0 100644 --- a/osu.Game/Users/User.cs +++ b/osu.Game/Users/User.cs @@ -1,9 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; +using System.Linq; using Newtonsoft.Json; -using osu.Framework.Configuration; +using osu.Framework.Bindables; namespace osu.Game.Users { @@ -23,6 +25,8 @@ namespace osu.Game.Users public Bindable Status = new Bindable(); + public IBindable Activity = new Bindable(); + //public Team Team; [JsonProperty(@"profile_colour")] @@ -34,8 +38,8 @@ namespace osu.Game.Users [JsonProperty(@"cover_url")] public string CoverUrl { - get { return Cover?.Url; } - set { Cover = new UserCover { Url = value }; } + get => Cover?.Url; + set => Cover = new UserCover { Url = value }; } [JsonProperty(@"cover")] @@ -59,6 +63,9 @@ namespace osu.Game.Users [JsonProperty(@"is_supporter")] public bool IsSupporter; + [JsonProperty(@"support_level")] + public int SupportLevel; + [JsonProperty(@"is_gmt")] public bool IsGMT; @@ -71,6 +78,12 @@ namespace osu.Game.Users [JsonProperty(@"is_active")] public bool Active; + [JsonProperty(@"is_online")] + public bool IsOnline; + + [JsonProperty(@"pm_friends_only")] + public bool PMFriendsOnly; + [JsonProperty(@"interests")] public string Interests; @@ -104,8 +117,16 @@ namespace osu.Game.Users [JsonProperty(@"post_count")] public int PostCount; - [JsonProperty(@"playstyle")] - public string[] PlayStyle; + [JsonProperty(@"follower_count")] + public int[] FollowerCount; + + [JsonProperty] + private string[] playstyle + { + set { PlayStyles = value?.Select(str => Enum.Parse(typeof(PlayStyle), str, true)).Cast().ToArray(); } + } + + public PlayStyle[] PlayStyles; [JsonProperty(@"playmode")] public string PlayMode; @@ -143,6 +164,18 @@ namespace osu.Game.Users [JsonProperty("badges")] public Badge[] Badges; + [JsonProperty("user_achievements")] + public UserAchievement[] Achievements; + + public class UserAchievement + { + [JsonProperty("achieved_at")] + public DateTimeOffset AchievedAt; + + [JsonProperty("achievement_id")] + public int ID; + } + public override string ToString() => Username; /// @@ -153,5 +186,20 @@ namespace osu.Game.Users Username = "system", Id = 0 }; + + public enum PlayStyle + { + [Description("Keyboard")] + Keyboard, + + [Description("Mouse")] + Mouse, + + [Description("Tablet")] + Tablet, + + [Description("Touch Screen")] + Touch, + } } } diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs new file mode 100644 index 0000000000..918c547978 --- /dev/null +++ b/osu.Game/Users/UserActivity.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osuTK.Graphics; + +namespace osu.Game.Users +{ + public abstract class UserActivity + { + public abstract string Status { get; } + public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker; + + public class Modding : UserActivity + { + public override string Status => "Modding a map"; + public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark; + } + + public class ChoosingBeatmap : UserActivity + { + public override string Status => "Choosing a beatmap"; + } + + public class MultiplayerGame : UserActivity + { + public override string Status => "Playing with others"; + } + + public class Editing : UserActivity + { + public BeatmapInfo Beatmap { get; } + + public Editing(BeatmapInfo info) + { + Beatmap = info; + } + + public override string Status => @"Editing a beatmap"; + } + + public class SoloGame : UserActivity + { + public BeatmapInfo Beatmap { get; } + + public Rulesets.RulesetInfo Ruleset { get; } + + public SoloGame(BeatmapInfo info, Rulesets.RulesetInfo ruleset) + { + Beatmap = info; + Ruleset = ruleset; + } + + public override string Status => @"Playing alone"; + } + + public class Spectating : UserActivity + { + public override string Status => @"Spectating a game"; + } + + public class InLobby : UserActivity + { + public override string Status => @"In a Multiplayer Lobby"; + } + } +} diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index fddbd57f9c..e583acac9f 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -1,30 +1,66 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osuTK.Graphics; namespace osu.Game.Users { - public class UserCoverBackground : Sprite + public class UserCoverBackground : ModelBackedDrawable { - private readonly User user; - - public UserCoverBackground(User user) + public User User { - this.user = user; + get => Model; + set => Model = value; } - [BackgroundDependencyLoader] - private void load(LargeTextureStore textures) - { - if (textures == null) - throw new ArgumentNullException(nameof(textures)); + protected override Drawable CreateDrawable(User user) => new Cover(user); - if (!string.IsNullOrEmpty(user.CoverUrl)) - Texture = textures.Get(user.CoverUrl); + private class Cover : CompositeDrawable + { + private readonly User user; + + public Cover(User user) + { + this.user = user; + + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + if (user == null) + { + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.1f), Color4.Black.Opacity(0.75f)) + }; + } + else + InternalChild = new Sprite + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(user.CoverUrl), + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(400); + } } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index d86f608bd1..c63c12773e 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -1,11 +1,11 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Configuration; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,8 +16,11 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Containers; -using osu.Game.Overlays.Profile.Header; +using osu.Game.Overlays.Profile.Header.Components; +using osu.Game.Users.Drawables; namespace osu.Game.Users { @@ -28,6 +31,9 @@ namespace osu.Game.Users private const float content_padding = 10; private const float status_height = 30; + [Resolved(canBeNull: true)] + private OsuColour colours { get; set; } + private Container statusBar; private Box statusBg; private OsuSpriteText statusMessage; @@ -37,6 +43,8 @@ namespace osu.Game.Users public readonly Bindable Status = new Bindable(); + public readonly IBindable Activity = new Bindable(); + public new Action Action; protected Action ViewProfile; @@ -52,7 +60,7 @@ namespace osu.Game.Users } [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, UserProfileOverlay profile) + private void load(UserProfileOverlay profile) { if (colours == null) throw new ArgumentNullException(nameof(colours)); @@ -73,14 +81,16 @@ namespace osu.Game.Users Children = new Drawable[] { - new DelayedLoadWrapper(new UserCoverBackground(user) + new DelayedLoadUnloadWrapper(() => new UserCoverBackground { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - FillMode = FillMode.Fill, - OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out) - }, 300) { RelativeSizeAxes = Axes.Both }, + User = user, + }, 300, 5000) + { + RelativeSizeAxes = Axes.Both, + }, new Box { RelativeSizeAxes = Axes.Both, @@ -99,6 +109,7 @@ namespace osu.Game.Users User = user, Masking = true, CornerRadius = 5, + OpenOnClick = { Value = false }, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, @@ -115,8 +126,7 @@ namespace osu.Game.Users new OsuSpriteText { Text = user.Username, - TextSize = 18, - Font = @"Exo2.0-SemiBoldItalic", + Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 18, italics: true), }, infoContainer = new FillFlowContainer { @@ -128,7 +138,7 @@ namespace osu.Game.Users Spacing = new Vector2(5f, 0f), Children = new Drawable[] { - new DrawableFlag(user.Country) + new UpdateableFlag(user.Country) { Width = 30f, RelativeSizeAxes = Axes.Y, @@ -164,7 +174,7 @@ namespace osu.Game.Users { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = FontAwesome.fa_circle_o, + Icon = FontAwesome.Regular.Circle, Shadow = true, Size = new Vector2(14), }, @@ -172,7 +182,7 @@ namespace osu.Game.Users { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = @"Exo2.0-Semibold", + Font = OsuFont.GetFont(weight: FontWeight.SemiBold), }, }, }, @@ -185,13 +195,13 @@ namespace osu.Game.Users { infoContainer.Add(new SupporterIcon { - RelativeSizeAxes = Axes.Y, - Width = 20f, + Height = 20f, + SupportLevel = user.SupportLevel }); } - Status.ValueChanged += displayStatus; - Status.ValueChanged += status => statusBg.FadeColour(status?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint); + Status.ValueChanged += status => displayStatus(status.NewValue, Activity.Value); + Activity.ValueChanged += activity => displayStatus(Status.Value, activity.NewValue); base.Action = ViewProfile = () => { @@ -206,7 +216,7 @@ namespace osu.Game.Users Status.TriggerChange(); } - private void displayStatus(UserStatus status) + private void displayStatus(UserStatus status, UserActivity activity = null) { const float transition_duration = 500; @@ -221,8 +231,17 @@ namespace osu.Game.Users statusBar.ResizeHeightTo(status_height, transition_duration, Easing.OutQuint); statusBar.FadeIn(transition_duration, Easing.OutQuint); this.ResizeHeightTo(height, transition_duration, Easing.OutQuint); + } - statusMessage.Text = status.Message; + if (status is UserStatusOnline && activity != null) + { + statusMessage.Text = activity.Status; + statusBg.FadeColour(activity.GetAppropriateColour(colours), 500, Easing.OutQuint); + } + else + { + statusMessage.Text = status?.Message; + statusBg.FadeColour(status?.GetAppropriateColour(colours) ?? colours.Gray5, 500, Easing.OutQuint); } } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index f04bfb62bb..7afbef01c5 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -1,7 +1,9 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. +using System; using Newtonsoft.Json; +using osu.Game.Scoring; namespace osu.Game.Users { @@ -23,7 +25,10 @@ namespace osu.Game.Users public decimal? PP; [JsonProperty(@"pp_rank")] // the API sometimes only returns this value in condensed user responses - private int rank { set => Ranks.Global = value; } + private int rank + { + set => Ranks.Global = value; + } [JsonProperty(@"rank")] public UserRanks Ranks; @@ -37,6 +42,9 @@ namespace osu.Game.Users [JsonProperty(@"play_count")] public int PlayCount; + [JsonProperty(@"play_time")] + public int? PlayTime; + [JsonProperty(@"total_score")] public long TotalScore; @@ -68,6 +76,33 @@ namespace osu.Game.Users [JsonProperty(@"a")] public int A; + + public int this[ScoreRank rank] + { + get + { + switch (rank) + { + case ScoreRank.XH: + return SSPlus; + + case ScoreRank.X: + return SS; + + case ScoreRank.SH: + return SPlus; + + case ScoreRank.S: + return S; + + case ScoreRank.A: + return A; + + default: + throw new ArgumentException($"API does not return {rank.ToString()}"); + } + } + } } public struct UserRanks @@ -78,6 +113,5 @@ namespace osu.Game.Users [JsonProperty(@"country")] public int? Country; } - } } diff --git a/osu.Game/Users/UserStatus.cs b/osu.Game/Users/UserStatus.cs index 6b27ff18a7..cf372560af 100644 --- a/osu.Game/Users/UserStatus.cs +++ b/osu.Game/Users/UserStatus.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using osuTK.Graphics; using osu.Game.Graphics; @@ -29,33 +29,7 @@ namespace osu.Game.Users public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray7; } - public class UserStatusSpectating : UserStatusOnline - { - public override string Message => @"Spectating a game"; - } - - public class UserStatusInLobby : UserStatusOnline - { - public override string Message => @"in Multiplayer Lobby"; - } - - public class UserStatusSoloGame : UserStatusBusy - { - public override string Message => @"Solo Game"; - } - - public class UserStatusMultiplayerGame : UserStatusBusy - { - public override string Message => @"Multiplaying"; - } - - public class UserStatusModding : UserStatusOnline - { - public override string Message => @"Modding a map"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.PurpleDark; - } - - public class UserStatusDoNotDisturb : UserStatusBusy + public class UserStatusDoNotDisturb : UserStatus { public override string Message => @"Do not disturb"; public override Color4 GetAppropriateColour(OsuColour colours) => colours.RedDark; diff --git a/osu.Game/Utils/DebugUtils.cs b/osu.Game/Utils/DebugUtils.cs deleted file mode 100644 index 191662c690..0000000000 --- a/osu.Game/Utils/DebugUtils.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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/Utils/RavenLogger.cs b/osu.Game/Utils/RavenLogger.cs index c6e6d1e9d7..7f4faa60ae 100644 --- a/osu.Game/Utils/RavenLogger.cs +++ b/osu.Game/Utils/RavenLogger.cs @@ -1,5 +1,5 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; @@ -20,14 +20,14 @@ namespace osu.Game.Utils private readonly List tasks = new List(); - private Exception lastException; - public RavenLogger(OsuGame game) { raven.Release = game.Version; if (!game.IsDeployedBuild) return; + Exception lastException = null; + Logger.NewEntry += entry => { if (entry.Level < LogLevel.Verbose) return; diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index c96ea9c80a..cd4d876451 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -1,7 +1,8 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. using System; +using System.IO; using SharpCompress.Archives.Zip; namespace osu.Game.Utils @@ -10,6 +11,9 @@ namespace osu.Game.Utils { public static bool IsZipArchive(string path) { + if (!File.Exists(path)) + return false; + try { using (var arc = ZipArchive.Open(path)) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e1e5415215..791c739527 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -5,23 +5,20 @@ Library AnyCPU true - 0 - - - - - - - - - - + + + + + + + + - \ No newline at end of file + diff --git a/osu.TestProject.props b/osu.TestProject.props index 456ecfd468..a5c70f4edc 100644 --- a/osu.TestProject.props +++ b/osu.TestProject.props @@ -8,7 +8,6 @@ - diff --git a/osu.iOS.props b/osu.iOS.props new file mode 100644 index 0000000000..a8013914af --- /dev/null +++ b/osu.iOS.props @@ -0,0 +1,116 @@ + + + {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Resources + PackageReference + --nolinkaway -gcc_flags "-lstdc++ -framework AudioToolbox -framework SystemConfiguration -framework CFNetwork -framework Accelerate + + + true + full + false + bin\iPhoneSimulator\Debug + DEBUG;ENABLE_TEST_CLOUD; + prompt + 4 + iPhone Developer + true + true + true + 25823 + None + x86_64 + NSUrlSessionHandler + false + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + pdbonly + true + bin\iPhone\Release + + prompt + 4 + iPhone Distribution + true + true + Entitlements.plist + SdkOnly + ARM64 + NSUrlSessionHandler + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + pdbonly + true + bin\iPhoneSimulator\Release + + prompt + 4 + iPhone Developer + true + None + x86_64 + NSUrlSessionHandler + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + true + full + false + bin\iPhone\Debug + DEBUG;ENABLE_TEST_CLOUD; + prompt + 4 + iPhone Developer + true + true + true + true + Entitlements.plist + 28126 + SdkOnly + ARM64 + NSUrlSessionHandler + + Default + $(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a" + false + cjk,mideast,other,rare,west + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.iOS.sln b/osu.iOS.sln new file mode 100644 index 0000000000..21d02d33ab --- /dev/null +++ b/osu.iOS.sln @@ -0,0 +1,187 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2006 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game", "osu.Game\osu.Game.csproj", "{2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu", "osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj", "{C92A607B-1FDD-4954-9F92-03FF547D9080}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko", "osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj", "{F167E17A-7DE6-4AF5-B920-A5112296C695}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania", "osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj", "{48F4582B-7687-4621-9CBE-5C24197CB536}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.iOS", "osu.iOS\osu.iOS.csproj", "{3F082D0B-A964-43D7-BDF7-C256D76A50D0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tests.iOS", "osu.Game.Tests.iOS\osu.Game.Tests.iOS.csproj", "{65FF8E19-6934-469B-B690-23C6D6E56A17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Taiko.Tests.iOS", "osu.Game.Rulesets.Taiko.Tests.iOS\osu.Game.Rulesets.Taiko.Tests.iOS.csproj", "{7E408809-66AC-49D1-AF4D-98834F9B979A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Osu.Tests.iOS", "osu.Game.Rulesets.Osu.Tests.iOS\osu.Game.Rulesets.Osu.Tests.iOS.csproj", "{6653CA6F-DB06-4604-A3FD-762E25C2AF96}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Mania.Tests.iOS", "osu.Game.Rulesets.Mania.Tests.iOS\osu.Game.Rulesets.Mania.Tests.iOS.csproj", "{39FD990E-B6CE-4B2A-999F-BC008CF2C64C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Catch.Tests.iOS", "osu.Game.Rulesets.Catch.Tests.iOS\osu.Game.Rulesets.Catch.Tests.iOS.csproj", "{4004C7B7-1A62-43F1-9DF2-52450FA67E70}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + Debug|iPhoneSimulator = Debug|iPhoneSimulator + Release|iPhone = Release|iPhone + Release|iPhoneSimulator = Release|iPhoneSimulator + Debug|iPhone = Debug|iPhone + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.ActiveCfg = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhone.Build.0 = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D}.Debug|iPhone.Build.0 = Debug|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 + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|Any CPU.Build.0 = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhone.ActiveCfg = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhone.Build.0 = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Debug|iPhone.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhone.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|iPhone.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|Any CPU.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhone.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3}.Debug|iPhone.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|Any CPU.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhone.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {F167E17A-7DE6-4AF5-B920-A5112296C695}.Debug|iPhone.Build.0 = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {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 + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.ActiveCfg = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhone.Build.0 = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {48F4582B-7687-4621-9CBE-5C24197CB536}.Debug|iPhone.Build.0 = Debug|Any CPU + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|Any CPU.ActiveCfg = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.ActiveCfg = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhone.Build.0 = Release|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.ActiveCfg = Debug|iPhone + {3F082D0B-A964-43D7-BDF7-C256D76A50D0}.Debug|iPhone.Build.0 = Debug|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|Any CPU.ActiveCfg = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.ActiveCfg = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhone.Build.0 = Release|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.ActiveCfg = Debug|iPhone + {65FF8E19-6934-469B-B690-23C6D6E56A17}.Debug|iPhone.Build.0 = Debug|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|Any CPU.ActiveCfg = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.ActiveCfg = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhone.Build.0 = Release|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.ActiveCfg = Debug|iPhone + {7E408809-66AC-49D1-AF4D-98834F9B979A}.Debug|iPhone.Build.0 = Debug|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|Any CPU.ActiveCfg = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.ActiveCfg = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhone.Build.0 = Release|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.ActiveCfg = Debug|iPhone + {6653CA6F-DB06-4604-A3FD-762E25C2AF96}.Debug|iPhone.Build.0 = Debug|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|Any CPU.ActiveCfg = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.ActiveCfg = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhone.Build.0 = Release|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.ActiveCfg = Debug|iPhone + {39FD990E-B6CE-4B2A-999F-BC008CF2C64C}.Debug|iPhone.Build.0 = Debug|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|Any CPU.ActiveCfg = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.ActiveCfg = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhone.Build.0 = Release|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.ActiveCfg = Debug|iPhone + {4004C7B7-1A62-43F1-9DF2-52450FA67E70}.Debug|iPhone.Build.0 = Debug|iPhone + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668} + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + Policies = $0 + $0.TextStylePolicy = $1 + $1.EolMarker = Windows + $1.scope = text/x-csharp + $1.FileWidth = 80 + $1.TabsToSpaces = True + $0.CSharpFormattingPolicy = $2 + $2.scope = text/x-csharp + EndGlobalSection +EndGlobal diff --git a/osu.iOS.sln.DotSettings b/osu.iOS.sln.DotSettings new file mode 100644 index 0000000000..3b2b851d45 --- /dev/null +++ b/osu.iOS.sln.DotSettings @@ -0,0 +1,816 @@ + + True + True + True + True + ExplicitlyExcluded + ExplicitlyExcluded + SOLUTION + HINT + WARNING + + True + WARNING + WARNING + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + HINT + WARNING + HINT + SUGGESTION + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + WARNING + WARNING + DO_NOT_SHOW + HINT + WARNING + DO_NOT_SHOW + WARNING + HINT + HINT + HINT + ERROR + HINT + HINT + HINT + WARNING + WARNING + HINT + DO_NOT_SHOW + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + HINT + HINT + HINT + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + + WARNING + WARNING + WARNING + ERROR + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + WARNING + WARNING + HINT + WARNING + HINT + HINT + HINT + HINT + HINT + HINT + HINT + + HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + HINT + WARNING + WARNING + HINT + HINT + WARNING + <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> + Code Cleanup (peppy) + True + True + True + True + True + True + True + True + NEXT_LINE + NEXT_LINE + True + NEVER + NEVER + False + NEVER + False + True + False + False + True + True + False + CHOP_IF_LONG + True + 200 + CHOP_IF_LONG + False + False + AABB + API + BPM + GC + GL + GLSL + HID + HUD + ID + IP + IPC + LTRB + MD5 + NS + OS + RGB + RNG + SHA + SRGB + TK + SS + PP + GMT + QAT + BNG + UI + False + HINT + <?xml version="1.0" encoding="utf-16"?> +<Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> + <TypePattern DisplayName="COM interfaces or structs"> + <TypePattern.Match> + <Or> + <And> + <Kind Is="Interface" /> + <Or> + <HasAttribute Name="System.Runtime.InteropServices.InterfaceTypeAttribute" /> + <HasAttribute Name="System.Runtime.InteropServices.ComImport" /> + </Or> + </And> + <Kind Is="Struct" /> + </Or> + </TypePattern.Match> + </TypePattern> + <TypePattern DisplayName="NUnit Test Fixtures" RemoveRegions="All"> + <TypePattern.Match> + <And> + <Kind Is="Class" /> + <HasAttribute Name="NUnit.Framework.TestFixtureAttribute" Inherited="True" /> + </And> + </TypePattern.Match> + <Entry DisplayName="Setup/Teardown Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <Or> + <HasAttribute Name="NUnit.Framework.SetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.TearDownAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureSetUpAttribute" Inherited="True" /> + <HasAttribute Name="NUnit.Framework.FixtureTearDownAttribute" Inherited="True" /> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="All other members" /> + <Entry Priority="100" DisplayName="Test Methods"> + <Entry.Match> + <And> + <Kind Is="Method" /> + <HasAttribute Name="NUnit.Framework.TestAttribute" /> + </And> + </Entry.Match> + <Entry.SortBy> + <Name /> + </Entry.SortBy> + </Entry> + </TypePattern> + <TypePattern DisplayName="Default Pattern"> + <Group DisplayName="Fields/Properties"> + <Group DisplayName="Public Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Public Properties"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Internal Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Internal Properties"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Protected Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Protected Properties"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + <Group DisplayName="Private Fields"> + <Entry DisplayName="Constant Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Or> + <Kind Is="Constant" /> + <Readonly /> + <And> + <Static /> + <Readonly /> + </And> + </Or> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Static Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Not> + <Readonly /> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Normal Fields"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Or> + <Static /> + <Readonly /> + </Or> + </Not> + <Kind Is="Field" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Entry DisplayName="Private Properties"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Kind Is="Property" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Constructor/Destructor"> + <Entry DisplayName="Ctor"> + <Entry.Match> + <Kind Is="Constructor" /> + </Entry.Match> + </Entry> + <Region Name="Disposal"> + <Entry DisplayName="Dtor"> + <Entry.Match> + <Kind Is="Destructor" /> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose()"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Dispose(true)"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Or> + <Virtual /> + <Override /> + </Or> + <Kind Is="Method" /> + <Name Is="Dispose" /> + </And> + </Entry.Match> + </Entry> + </Region> + </Group> + <Group DisplayName="Methods"> + <Group DisplayName="Public"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Public" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Internal"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Internal" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Protected"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Protected" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + <Group DisplayName="Private"> + <Entry DisplayName="Static Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Static /> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + <Entry DisplayName="Methods"> + <Entry.Match> + <And> + <Access Is="Private" /> + <Not> + <Static /> + </Not> + <Kind Is="Method" /> + </And> + </Entry.Match> + </Entry> + </Group> + </Group> + </TypePattern> +</Patterns> + Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +See the LICENCE file in the repository root for full licence text. + + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public methods"><ElementKinds><Kind Name="ASYNC_METHOD" /><Kind Name="METHOD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="private properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static, Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public" Description="internal/protected/public properties"><ElementKinds><Kind Name="PROPERTY" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True + True + True + True + o!f – Object Initializer: Anchor&Origin + True + constant("Centre") + 0 + True + True + 2.0 + InCSharpFile + ofao + True + Anchor = Anchor.$anchor$, +Origin = Anchor.$anchor$, + True + True + o!f – InternalChildren = [] + True + True + 2.0 + InCSharpFile + ofic + True + InternalChildren = new Drawable[] +{ + $END$ +}; + True + True + o!f – new GridContainer { .. } + True + True + 2.0 + InCSharpFile + ofgc + True + new GridContainer +{ + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } +}; + True + True + o!f – new FillFlowContainer { .. } + True + True + 2.0 + InCSharpFile + offf + True + new FillFlowContainer +{ + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – new Container { .. } + True + True + 2.0 + InCSharpFile + ofcont + True + new Container +{ + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – BackgroundDependencyLoader load() + True + True + 2.0 + InCSharpFile + ofbdl + True + [BackgroundDependencyLoader] +private void load() +{ + $END$ +} + True + True + o!f – new Box { .. } + True + True + 2.0 + InCSharpFile + ofbox + True + new Box +{ + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, +}, + True + True + o!f – Children = [] + True + True + 2.0 + InCSharpFile + ofc + True + Children = new Drawable[] +{ + $END$ +}; + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True + True diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs new file mode 100644 index 0000000000..058e246ed8 --- /dev/null +++ b/osu.iOS/AppDelegate.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Foundation; +using osu.Framework.iOS; +using osu.Game; + +namespace osu.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameAppDelegate + { + protected override Framework.Game CreateGame() => new OsuGameIOS(); + } +} diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs new file mode 100644 index 0000000000..30e0e15ad1 --- /dev/null +++ b/osu.iOS/Application.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using UIKit; + +namespace osu.iOS +{ + public class Application + { + public static void Main(string[] args) + { + UIApplication.Main(args, "GameUIApplication", "AppDelegate"); + } + } +} diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..bbd5f4f3ba --- /dev/null +++ b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,249 @@ +{ + "images": [ + { + "filename": "Icon-App-20x20@2x.png", + "size": "20x20", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-20x20@3x.png", + "size": "20x20", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-29x29@2x.png", + "size": "29x29", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-29x29@3x.png", + "size": "29x29", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-40x40@2x.png", + "size": "40x40", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-40x40@3x.png", + "size": "40x40", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-60x60@2x.png", + "size": "60x60", + "scale": "2x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-60x60@3x.png", + "size": "60x60", + "scale": "3x", + "idiom": "iphone" + }, + { + "filename": "Icon-App-20x20@1x.png", + "size": "20x20", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-20x20@2x.png", + "size": "20x20", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-29x29@1x.png", + "size": "29x29", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-29x29@2x.png", + "size": "29x29", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-40x40@1x.png", + "size": "40x40", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-40x40@2x.png", + "size": "40x40", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-83.5x83.5@2x.png", + "size": "83.5x83.5", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-76x76@1x.png", + "size": "76x76", + "scale": "1x", + "idiom": "ipad" + }, + { + "filename": "Icon-App-76x76@2x.png", + "size": "76x76", + "scale": "2x", + "idiom": "ipad" + }, + { + "filename": "ItunesArtwork@2x.png", + "size": "1024x1024", + "scale": "1x", + "idiom": "ios-marketing" + }, + { + "size": "60x60", + "scale": "2x", + "idiom": "car" + }, + { + "size": "60x60", + "scale": "3x", + "idiom": "car" + }, + { + "role": "notificationCenter", + "size": "24x24", + "subtype": "38mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "notificationCenter", + "size": "27.5x27.5", + "subtype": "42mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "companionSettings", + "size": "29x29", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "companionSettings", + "size": "29x29", + "scale": "3x", + "idiom": "watch" + }, + { + "role": "appLauncher", + "size": "40x40", + "subtype": "38mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "appLauncher", + "size": "44x44", + "subtype": "40mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "appLauncher", + "size": "50x50", + "subtype": "44mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "quickLook", + "size": "86x86", + "subtype": "38mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "quickLook", + "size": "98x98", + "subtype": "42mm", + "scale": "2x", + "idiom": "watch" + }, + { + "role": "quickLook", + "size": "108x108", + "subtype": "44mm", + "scale": "2x", + "idiom": "watch" + }, + { + "size": "1024x1024", + "scale": "1x", + "idiom": "watch-marketing" + }, + { + "size": "16x16", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "16x16", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "32x32", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "32x32", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "128x128", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "128x128", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "256x256", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "256x256", + "scale": "2x", + "idiom": "mac" + }, + { + "size": "512x512", + "scale": "1x", + "idiom": "mac" + }, + { + "size": "512x512", + "scale": "2x", + "idiom": "mac" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} \ No newline at end of file diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000000..dfcce1297b Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000000..d563e8cf5e Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000..8a21d9f81d Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000000..4477ece73a Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000..1fe354a7fa Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000000..0f8c36a038 Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000000..d563e8cf5e Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000..0208f80e3b Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000..f7ad8bb6bb Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000..f7ad8bb6bb Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000..3d85a133aa Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000..edd53bc954 Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000000..b13a6e7a1e Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000000..edff3788f0 Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png new file mode 100644 index 0000000000..c04f6f3ec4 Binary files /dev/null and b/osu.iOS/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png differ diff --git a/osu.iOS/Assets.xcassets/Contents.json b/osu.iOS/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..4caf392f92 --- /dev/null +++ b/osu.iOS/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/osu.iOS/Entitlements.plist b/osu.iOS/Entitlements.plist new file mode 100644 index 0000000000..9ae599370b --- /dev/null +++ b/osu.iOS/Entitlements.plist @@ -0,0 +1,6 @@ + + + + + + diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist new file mode 100644 index 0000000000..d7992353cf --- /dev/null +++ b/osu.iOS/Info.plist @@ -0,0 +1,44 @@ + + + + + CFBundleIdentifier + sh.ppy.osulazer + CFBundleName + osu! + CFBundleDisplayName + osu! + CFBundleShortVersionString + 0.1 + CFBundleVersion + 0.1.0 + LSRequiresIPhoneOS + + MinimumOSVersion + 10.0 + UIDeviceFamily + + 1 + 2 + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UIRequiresFullScreen + + UIStatusBarHidden + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/AppIcon.appiconset + + diff --git a/osu.iOS/LaunchScreen.storyboard b/osu.iOS/LaunchScreen.storyboard new file mode 100644 index 0000000000..3655e9f2f0 --- /dev/null +++ b/osu.iOS/LaunchScreen.storyboard @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/osu.iOS/Linker.xml b/osu.iOS/Linker.xml new file mode 100644 index 0000000000..04591c55d0 --- /dev/null +++ b/osu.iOS/Linker.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs new file mode 100644 index 0000000000..6cf18df9a6 --- /dev/null +++ b/osu.iOS/OsuGameIOS.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Foundation; +using osu.Game; + +namespace osu.iOS +{ + public class OsuGameIOS : OsuGame + { + public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); + } +} diff --git a/osu.iOS/iTunesArtwork b/osu.iOS/iTunesArtwork new file mode 100644 index 0000000000..ef7441433a Binary files /dev/null and b/osu.iOS/iTunesArtwork differ diff --git a/osu.iOS/iTunesArtwork@2x b/osu.iOS/iTunesArtwork@2x new file mode 100644 index 0000000000..c04f6f3ec4 Binary files /dev/null and b/osu.iOS/iTunesArtwork@2x differ diff --git a/osu.iOS/libbass.a b/osu.iOS/libbass.a new file mode 100644 index 0000000000..c34e6a0a0c Binary files /dev/null and b/osu.iOS/libbass.a differ diff --git a/osu.iOS/libbass_fx.a b/osu.iOS/libbass_fx.a new file mode 100644 index 0000000000..007cd28647 Binary files /dev/null and b/osu.iOS/libbass_fx.a differ diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj new file mode 100644 index 0000000000..9df3ad5516 --- /dev/null +++ b/osu.iOS/osu.iOS.csproj @@ -0,0 +1,84 @@ + + + + + Debug + iPhoneSimulator + Exe + {3F082D0B-A964-43D7-BDF7-C256D76A50D0} + osu.iOS + osu.iOS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + + + + + + + + + + {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} + osu.Game + + + {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} + osu.Game.Rulesets.Catch + + + {48F4582B-7687-4621-9CBE-5C24197CB536} + osu.Game.Rulesets.Mania + + + {C92A607B-1FDD-4954-9F92-03FF547D9080} + osu.Game.Rulesets.Osu + + + {F167E17A-7DE6-4AF5-B920-A5112296C695} + osu.Game.Rulesets.Taiko + + + + + + + + + \ No newline at end of file diff --git a/osu.licenseheader b/osu.licenseheader index 798b25e550..ab8af1984b 100644 --- a/osu.licenseheader +++ b/osu.licenseheader @@ -1,9 +1,9 @@ -extensions: .cs -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -extensions: .xml .config .xsd - \ No newline at end of file diff --git a/osu.sln b/osu.sln index bf1b6d60e1..688339fab5 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.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}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Catch", "osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj", "{58F6C80C-1253-4A0E-A465-B8C85EBEADF3}" @@ -27,6 +25,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Taiko.Tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Osu.Tests", "osu.Game.Rulesets.Osu.Tests\osu.Game.Rulesets.Osu.Tests.csproj", "{6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tournament", "osu.Game.Tournament\osu.Game.Tournament.csproj", "{5672CA4D-1B37-425B-A118-A8DA26E78938}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Tournament.Tests", "osu.Game.Tournament.Tests\osu.Game.Tournament.Tests.csproj", "{5789E78D-38F9-4072-AB7B-978F34B2C17F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -37,10 +39,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 - {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 - {D9A367C9-4C1A-489F-9B05-A0CEA2B53B58}.Release|Any CPU.Build.0 = Release|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Debug|Any CPU.Build.0 = Debug|Any CPU {C92A607B-1FDD-4954-9F92-03FF547D9080}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -81,6 +79,14 @@ Global {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {6A2D5D58-0261-4A75-BE84-2BE8B076B7C2}.Release|Any CPU.Build.0 = Release|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5672CA4D-1B37-425B-A118-A8DA26E78938}.Release|Any CPU.Build.0 = Release|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5789E78D-38F9-4072-AB7B-978F34B2C17F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index d6882282e6..c3e274569d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -6,15 +6,46 @@ ExplicitlyExcluded ExplicitlyExcluded SOLUTION - HINT + WARNING + WARNING + WARNING + HINT + HINT WARNING - WARNING + + True WARNING WARNING HINT HINT HINT HINT + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING + WARNING WARNING WARNING WARNING @@ -47,6 +78,7 @@ HINT HINT ERROR + WARNING HINT HINT HINT @@ -61,18 +93,29 @@ WARNING WARNING WARNING + WARNING + WARNING + WARNING + WARNING WARNING + WARNING + WARNING + WARNING WARNING HINT + WARNING HINT HINT HINT + HINT HINT WARNING WARNING + HINT WARNING WARNING WARNING + WARNING HINT DO_NOT_SHOW DO_NOT_SHOW @@ -82,6 +125,8 @@ WARNING WARNING WARNING + WARNING + WARNING ERROR WARNING WARNING @@ -135,13 +180,18 @@ WARNING WARNING WARNING + WARNING HINT DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW + WARNING + WARNING + WARNING WARNING WARNING HINT + WARNING HINT HINT HINT @@ -155,16 +205,22 @@ WARNING WARNING WARNING + + True WARNING WARNING - HINT + WARNING WARNING WARNING HINT HINT WARNING + WARNING <?xml version="1.0" encoding="utf-16"?><Profile name="Code Cleanup (peppy)"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSUseVar><BehavourStyle>CAN_CHANGE_TO_EXPLICIT</BehavourStyle><LocalVariableStyle>ALWAYS_EXPLICIT</LocalVariableStyle><ForeachVariableStyle>ALWAYS_EXPLICIT</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSUpdateFileHeader>True</CSUpdateFileHeader><CSCodeStyleAttributes ArrangeTypeAccessModifier="False" ArrangeTypeMemberAccessModifier="False" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="False" ArrangeBraces="False" ArrangeAttributes="False" ArrangeArgumentsStyle="False" /><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CSArrangeQualifiers>True</CSArrangeQualifiers></Profile> Code Cleanup (peppy) + ExpressionBody + ExpressionBody + True True True True @@ -174,7 +230,11 @@ True True NEXT_LINE + 1 + 1 NEXT_LINE + 1 + 1 True NEVER NEVER @@ -202,12 +262,15 @@ HID HUD ID + IL IP IPC + JIT LTRB MD5 NS OS + PM RGB RNG SHA @@ -219,6 +282,7 @@ QAT BNG UI + False HINT <?xml version="1.0" encoding="utf-16"?> <Patterns xmlns="urn:schemas-jetbrains-com:member-reordering-patterns"> @@ -604,8 +668,8 @@ </Group> </TypePattern> </Patterns> - Copyright (c) 2007-$CURRENT_YEAR$ ppy Pty Ltd <contact@ppy.sh>. -Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. +See the LICENCE file in the repository root for full licence text. <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> @@ -672,6 +736,127 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste True True True + True + True + o!f – Object Initializer: Anchor&Origin + True + constant("Centre") + 0 + True + True + 2.0 + InCSharpFile + ofao + True + Anchor = Anchor.$anchor$, +Origin = Anchor.$anchor$, + True + True + o!f – InternalChildren = [] + True + True + 2.0 + InCSharpFile + ofic + True + InternalChildren = new Drawable[] +{ + $END$ +}; + True + True + o!f – new GridContainer { .. } + True + True + 2.0 + InCSharpFile + ofgc + True + new GridContainer +{ + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { $END$ }, + new Drawable[] { } + } +}; + True + True + o!f – new FillFlowContainer { .. } + True + True + 2.0 + InCSharpFile + offf + True + new FillFlowContainer +{ + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – new Container { .. } + True + True + 2.0 + InCSharpFile + ofcont + True + new Container +{ + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + $END$ + } +}, + True + True + o!f – BackgroundDependencyLoader load() + True + True + 2.0 + InCSharpFile + ofbdl + True + [BackgroundDependencyLoader] +private void load() +{ + $END$ +} + True + True + o!f – new Box { .. } + True + True + 2.0 + InCSharpFile + ofbox + True + new Box +{ + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, +}, + True + True + o!f – Children = [] + True + True + 2.0 + InCSharpFile + ofc + True + Children = new Drawable[] +{ + $END$ +}; True True True