diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
index 6ba6ae82c8..58c24181d3 100644
--- a/.config/dotnet-tools.json
+++ b/.config/dotnet-tools.json
@@ -13,6 +13,24 @@
"commands": [
"dotnet-format"
]
+ },
+ "jetbrains.resharper.globaltools": {
+ "version": "2020.3.2",
+ "commands": [
+ "jb"
+ ]
+ },
+ "nvika": {
+ "version": "2.0.0",
+ "commands": [
+ "nvika"
+ ]
+ },
+ "codefilesanity": {
+ "version": "15.0.0",
+ "commands": [
+ "CodeFileSanity"
+ ]
}
}
}
\ No newline at end of file
diff --git a/.editorconfig b/.editorconfig
index 67f98f94eb..0cdf3b92d3 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -191,4 +191,10 @@ dotnet_diagnostic.IDE0052.severity = silent
#Rules for disposable
dotnet_diagnostic.IDE0067.severity = none
dotnet_diagnostic.IDE0068.severity = none
-dotnet_diagnostic.IDE0069.severity = none
\ No newline at end of file
+dotnet_diagnostic.IDE0069.severity = none
+
+#Disable operator overloads requiring alternate named methods
+dotnet_diagnostic.CA2225.severity = none
+
+# Banned APIs
+dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/01-bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md
index 0b80ce44dd..e45893b97a 100644
--- a/.github/ISSUE_TEMPLATE/01-bug-issues.md
+++ b/.github/ISSUE_TEMPLATE/01-bug-issues.md
@@ -1,7 +1,18 @@
---
name: Bug Report
-about: Issues regarding encountered bugs.
+about: Report a bug or crash to desktop
---
+
+
+
+
**Describe the bug:**
**Screenshots or videos showing encountered issue:**
@@ -9,8 +20,11 @@ about: Issues regarding encountered bugs.
**osu!lazer version:**
**Logs:**
+
diff --git a/.github/ISSUE_TEMPLATE/02-crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md
deleted file mode 100644
index ada8de73c0..0000000000
--- a/.github/ISSUE_TEMPLATE/02-crash-issues.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-name: Crash Report
-about: Issues regarding crashes or permanent freezes.
----
-**Describe the crash:**
-
-**Screenshots or videos showing encountered issue:**
-
-**osu!lazer version:**
-
-**Logs:**
-
-
-**Computer Specifications:**
diff --git a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
similarity index 62%
rename from .github/ISSUE_TEMPLATE/03-feature-request-issues.md
rename to .github/ISSUE_TEMPLATE/02-feature-request-issues.md
index 54c4ff94e5..c3357dd780 100644
--- a/.github/ISSUE_TEMPLATE/03-feature-request-issues.md
+++ b/.github/ISSUE_TEMPLATE/02-feature-request-issues.md
@@ -1,6 +1,6 @@
---
name: Feature Request
-about: Features you would like to see in the game!
+about: Propose a feature you would like to see in the game!
---
**Describe the new feature:**
diff --git a/.gitignore b/.gitignore
index 732b171f69..d122d25054 100644
--- a/.gitignore
+++ b/.gitignore
@@ -334,3 +334,5 @@ inspectcode
# BenchmarkDotNet
/BenchmarkDotNet.Artifacts
+
+*.GeneratedMSBuildEditorConfig.editorconfig
diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml
index 366f172c30..680312ad27 100644
--- a/.idea/.idea.osu.Desktop/.idea/modules.xml
+++ b/.idea/.idea.osu.Desktop/.idea/modules.xml
@@ -2,8 +2,7 @@
-
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
index 1815c271b4..8fa7608b8e 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Benchmarks.xml
@@ -1,8 +1,8 @@
-
+
-
+
@@ -12,7 +12,7 @@
-
+
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
index a4154623b6..657b885df1 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,10 +12,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
index 080dc04001..847dcf822c 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,10 +12,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
index 3de6a7e609..5dc1168e35 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,10 +12,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
index da14c2a29e..ab4ce5a9cc 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,10 +12,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
index 45d1ce25e9..61331944a8 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,9 +12,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
index ba80f7c100..9a00f58c3b 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,10 +12,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
index 911c3ed9b7..4ae656b6d8 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,9 +12,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml
deleted file mode 100644
index d85a0ae44c..0000000000
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
index ec3c81f4cd..e58c602962 100644
--- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
+++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml
@@ -1,8 +1,8 @@
-
-
+
+
-
+
@@ -12,9 +12,9 @@
-
+
-
+
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
index 4e8af405a2..afd997f91d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
@@ -19,7 +19,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll"
+ "${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
@@ -31,7 +31,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll"
+ "${workspaceRoot}/osu.Game.Tests/bin/Debug/net5.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
@@ -43,7 +43,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.1/osu.Game.Tests.dll"
+ "${workspaceRoot}/osu.Game.Tests/bin/Release/net5.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
@@ -55,7 +55,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Debug/net5.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
@@ -68,7 +68,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.1/osu!.dll",
+ "${workspaceRoot}/osu.Desktop/bin/Release/net5.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
@@ -81,7 +81,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
@@ -94,7 +94,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll",
+ "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net5.0/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
@@ -105,7 +105,7 @@
"name": "Benchmark",
"type": "coreclr",
"request": "launch",
- "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/netcoreapp3.1/osu.Game.Benchmarks.dll",
+ "program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll",
"args": [
"--filter",
"*"
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index e638dec767..a70e5ac3a9 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -9,11 +9,10 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Desktop",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -24,12 +23,11 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Desktop",
- "/p:Configuration=Release",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:Configuration=Release",
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -40,11 +38,10 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Game.Tests",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -55,12 +52,11 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Game.Tests",
- "/p:Configuration=Release",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:Configuration=Release",
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -71,11 +67,10 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Game.Tournament.Tests",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -86,12 +81,11 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Game.Tournament.Tests",
- "/p:Configuration=Release",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:Configuration=Release",
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -102,25 +96,14 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Game.Benchmarks",
- "/p:Configuration=Release",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:Configuration=Release",
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
- },
- {
- "label": "Restore (netcoreapp3.1)",
- "type": "shell",
- "command": "dotnet",
- "args": [
- "restore",
- "build/Desktop.proj"
- ],
- "problemMatcher": []
}
]
}
\ No newline at end of file
diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index e34626a59e..46c50dbfa2 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -4,5 +4,7 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
+M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900)
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
+M:System.Enum.HasFlag(System.Enum);Use osu.Framework.Extensions.EnumExtensions.HasFlagFast() instead.
\ No newline at end of file
diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset
index d497365f87..6a99e230d1 100644
--- a/CodeAnalysis/osu.ruleset
+++ b/CodeAnalysis/osu.ruleset
@@ -30,7 +30,7 @@
-
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 2cd40c8675..53ad973e47 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,9 +16,9 @@
-
+
-
+
$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset
@@ -28,9 +28,17 @@
$(NoWarn);CS1591
-
- $(NoWarn);NU1701
+
+ $(NoWarn);NU1701;CA9998
false
@@ -40,7 +48,7 @@
https://github.com/ppy/osu
Automated release.
ppy Pty Ltd
- Copyright (c) 2020 ppy Pty Ltd
+ Copyright (c) 2021 ppy Pty Ltd
osu game
-
\ No newline at end of file
+
diff --git a/Gemfile.lock b/Gemfile.lock
index bf971d2c22..8ac863c9a8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,75 +1,75 @@
GEM
remote: https://rubygems.org/
specs:
- CFPropertyList (3.0.2)
+ CFPropertyList (3.0.3)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
aws-eventstream (1.1.0)
- aws-partitions (1.329.0)
- aws-sdk-core (3.99.2)
+ aws-partitions (1.413.0)
+ aws-sdk-core (3.110.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
- aws-sdk-kms (1.34.1)
- aws-sdk-core (~> 3, >= 3.99.0)
+ aws-sdk-kms (1.40.0)
+ aws-sdk-core (~> 3, >= 3.109.0)
aws-sigv4 (~> 1.1)
- aws-sdk-s3 (1.68.1)
- aws-sdk-core (~> 3, >= 3.99.0)
+ aws-sdk-s3 (1.87.0)
+ aws-sdk-core (~> 3, >= 3.109.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1)
- aws-sigv4 (1.1.4)
- aws-eventstream (~> 1.0, >= 1.0.2)
- babosa (1.0.3)
+ aws-sigv4 (1.2.2)
+ aws-eventstream (~> 1, >= 1.0.2)
+ babosa (1.0.4)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
highline (~> 1.7.2)
- declarative (0.0.10)
+ declarative (0.0.20)
declarative-option (0.1.0)
- digest-crc (0.5.1)
+ digest-crc (0.6.3)
+ rake (>= 12.0.0, < 14.0.0)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
- dotenv (2.7.5)
- emoji_regex (1.0.1)
- excon (0.74.0)
- faraday (1.0.1)
+ dotenv (2.7.6)
+ emoji_regex (3.2.1)
+ excon (0.78.1)
+ faraday (1.2.0)
multipart-post (>= 1.2, < 3)
- faraday-cookie_jar (0.0.6)
- faraday (>= 0.7.4)
+ ruby2_keywords
+ faraday-cookie_jar (0.0.7)
+ faraday (>= 0.8.0)
http-cookie (~> 1.0.0)
faraday_middleware (1.0.0)
faraday (~> 1.0)
- fastimage (2.1.7)
- fastlane (2.149.1)
+ fastimage (2.2.1)
+ fastlane (2.170.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
aws-sdk-s3 (~> 1.0)
- babosa (>= 1.0.2, < 2.0.0)
+ babosa (>= 1.0.3, < 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)
+ emoji_regex (>= 0.1, < 4.0)
excon (>= 0.71.0, < 1.0.0)
- faraday (>= 0.17, < 2.0)
+ faraday (~> 1.0)
faraday-cookie_jar (~> 0.0.6)
- faraday_middleware (>= 0.13.1, < 2.0)
+ faraday_middleware (~> 1.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.37.0, < 0.39.0)
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
- jwt (~> 2.1.0)
+ jwt (>= 2.1.0, < 3)
mini_magick (>= 4.9.4, < 5.0.0)
- multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
- public_suffix (~> 2.0.0)
- rubyzip (>= 1.3.0, < 2.0.0)
+ rubyzip (>= 2.0.0, < 3.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
@@ -97,17 +97,17 @@ GEM
google-cloud-core (1.5.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
- google-cloud-env (1.3.2)
+ google-cloud-env (1.4.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-errors (1.0.1)
- google-cloud-storage (1.26.2)
+ google-cloud-storage (1.29.2)
addressable (~> 2.5)
digest-crc (~> 0.4)
google-api-client (~> 0.33)
google-cloud-core (~> 1.2)
googleauth (~> 0.9)
mini_mime (~> 1.0)
- googleauth (0.12.0)
+ googleauth (0.14.0)
faraday (>= 0.17.3, < 2.0)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)
@@ -119,29 +119,30 @@ GEM
domain_name (~> 0.5)
httpclient (2.8.3)
jmespath (1.4.0)
- json (2.3.0)
- jwt (2.1.0)
+ json (2.5.1)
+ jwt (2.2.2)
memoist (0.16.2)
- mini_magick (4.10.1)
+ mini_magick (4.11.0)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
- multi_json (1.14.1)
- multi_xml (0.6.0)
+ multi_json (1.15.0)
multipart-post (2.0.0)
- nanaimo (0.2.6)
+ nanaimo (0.3.0)
naturally (2.2.0)
- nokogiri (1.10.7)
+ nokogiri (1.10.10)
mini_portile2 (~> 2.4.0)
- os (1.1.0)
+ os (1.1.1)
plist (3.5.0)
- public_suffix (2.0.5)
+ public_suffix (4.0.6)
+ rake (13.0.3)
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.3.0)
+ ruby2_keywords (0.0.2)
+ rubyzip (2.3.0)
security (0.1.3)
signet (0.14.0)
addressable (~> 2.3)
@@ -160,7 +161,7 @@ GEM
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.7.1)
- tty-screen (0.8.0)
+ tty-screen (0.8.1)
tty-spinner (0.9.3)
tty-cursor (~> 0.7)
uber (0.1.0)
@@ -169,12 +170,12 @@ GEM
unf_ext (0.0.7.7)
unicode-display_width (1.7.0)
word_wrap (1.0.0)
- xcodeproj (1.16.0)
+ xcodeproj (1.19.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
- nanaimo (~> 0.2.6)
+ nanaimo (~> 0.3.0)
xcpretty (0.3.0)
rouge (~> 2.0.7)
xcpretty-travis-formatter (1.0.0)
diff --git a/LICENCE b/LICENCE
index 2435c23545..b5962ad3b2 100644
--- a/LICENCE
+++ b/LICENCE
@@ -1,4 +1,4 @@
-Copyright (c) 2020 ppy Pty Ltd .
+Copyright (c) 2021 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 dc3ee63844..0d6af2aeba 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,13 @@
# osu!
[](https://ci.appveyor.com/project/peppy/osu)
-[]()
+[](https://github.com/ppy/osu/releases/latest)
[](https://www.codefactor.io/repository/github/ppy/osu)
[](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.
+A free-to-win rhythm game. 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
@@ -32,15 +34,22 @@ If you are looking to install or test osu! without setting up a development envi
| [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) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
-- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/dependencies?tabs=netcore31&pivots=os-windows)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
+- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
+- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
-## Developing or debugging
+## Developing a custom ruleset
+
+osu! is designed to have extensible modular gameplay modes, called "rulesets". Building one of these allows a developer to harness the power of osu! for their own game style. To get started working on a ruleset, we have some templates available [here](https://github.com/ppy/osu/tree/master/Templates).
+
+You can see some examples of custom rulesets by visiting the [custom ruleset directory](https://github.com/ppy/osu/issues/5852).
+
+## Developing osu!
Please make sure you have the following prerequisites:
-- A desktop platform with the [.NET Core 3.1 SDK](https://dotnet.microsoft.com/download) or higher installed.
+- A desktop platform with the [.NET 5.0 SDK](https://dotnet.microsoft.com/download) or higher installed.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as [Visual Studio 2019+](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
@@ -65,7 +74,6 @@ git pull
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 is provided [below](#contributing).
- Visual Studio / Rider users should load the project via one of the platform-specific `.slnf` files, rather than the main `.sln.` This will allow access to template run configurations.
-- 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:
diff --git a/Templates/Directory.Build.props b/Templates/Directory.Build.props
new file mode 100644
index 0000000000..0e470106e8
--- /dev/null
+++ b/Templates/Directory.Build.props
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Templates/README.md b/Templates/README.md
new file mode 100644
index 0000000000..cf25a89273
--- /dev/null
+++ b/Templates/README.md
@@ -0,0 +1,21 @@
+# Templates
+
+Templates for use when creating osu! dependent projects. Create a fully-testable (and ready for git) custom ruleset in just two lines.
+
+## Usage
+
+```bash
+# install (or update) templates package.
+# this only needs to be done once
+dotnet new -i ppy.osu.Game.Templates
+
+# create an empty freeform ruleset
+dotnet new ruleset -n MyCoolRuleset
+# create an empty scrolling ruleset (which provides the basics for a scrolling ←↑→↓ ruleset)
+dotnet new ruleset-scrolling -n MyCoolRuleset
+
+# ..or start with a working sample freeform game
+dotnet new ruleset-example -n MyCoolWorkingRuleset
+# ..or a working sample scrolling game
+dotnet new ruleset-scrolling-example -n MyCoolWorkingRuleset
+```
diff --git a/Templates/Rulesets/ruleset-empty/.editorconfig b/Templates/Rulesets/ruleset-empty/.editorconfig
new file mode 100644
index 0000000000..f3badda9b3
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/.editorconfig
@@ -0,0 +1,200 @@
+# EditorConfig is awesome: http://editorconfig.org
+root = true
+
+[*.cs]
+end_of_line = crlf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+#Roslyn naming styles
+
+#PascalCase for public and protected members
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.public_members_pascalcase.severity = error
+dotnet_naming_rule.public_members_pascalcase.symbols = public_members
+dotnet_naming_rule.public_members_pascalcase.style = pascalcase
+
+#camelCase for private members
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_symbols.private_members.applicable_accessibilities = private
+dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.private_members_camelcase.severity = warning
+dotnet_naming_rule.private_members_camelcase.symbols = private_members
+dotnet_naming_rule.private_members_camelcase.style = camelcase
+
+dotnet_naming_symbols.local_function.applicable_kinds = local_function
+dotnet_naming_rule.local_function_camelcase.severity = warning
+dotnet_naming_rule.local_function_camelcase.symbols = local_function
+dotnet_naming_rule.local_function_camelcase.style = camelcase
+
+#all_lower for private and local constants/static readonlys
+dotnet_naming_style.all_lower.capitalization = all_lower
+dotnet_naming_style.all_lower.word_separator = _
+
+dotnet_naming_symbols.private_constants.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants.required_modifiers = const
+dotnet_naming_symbols.private_constants.applicable_kinds = field
+dotnet_naming_rule.private_const_all_lower.severity = warning
+dotnet_naming_rule.private_const_all_lower.symbols = private_constants
+dotnet_naming_rule.private_const_all_lower.style = all_lower
+
+dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
+dotnet_naming_rule.private_static_readonly_all_lower.severity = warning
+dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly
+dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+dotnet_naming_rule.local_const_all_lower.severity = warning
+dotnet_naming_rule.local_const_all_lower.symbols = local_constants
+dotnet_naming_rule.local_const_all_lower.style = all_lower
+
+#ALL_UPPER for non private constants/static readonlys
+dotnet_naming_style.all_upper.capitalization = all_upper
+dotnet_naming_style.all_upper.word_separator = _
+
+dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_constants.required_modifiers = const
+dotnet_naming_symbols.public_constants.applicable_kinds = field
+dotnet_naming_rule.public_const_all_upper.severity = warning
+dotnet_naming_rule.public_const_all_upper.symbols = public_constants
+dotnet_naming_rule.public_const_all_upper.style = all_upper
+
+dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.public_static_readonly.applicable_kinds = field
+dotnet_naming_rule.public_static_readonly_all_upper.severity = warning
+dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly
+dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper
+
+#Roslyn formating options
+
+#Formatting - indentation options
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_open_brace = all
+#csharp_new_line_before_members_in_anonymous_types = true
+#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing
+csharp_new_line_between_query_expression_clauses = true
+
+#Formatting - organize using options
+dotnet_sort_system_directives_first = true
+
+#Formatting - spacing options
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#Roslyn language styles
+
+#Style - this. qualification
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_event = false:warning
+
+#Style - type names
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+csharp_style_var_when_type_is_apparent = true:none
+csharp_style_var_for_built_in_types = true:none
+csharp_style_var_elsewhere = true:silent
+
+#Style - modifiers
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning
+
+#Style - parentheses
+# Skipped because roslyn cannot separate +-*/ with << >>
+
+#Style - expression bodies
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+csharp_style_expression_bodied_local_functions = true:silent
+
+#Style - expression preferences
+dotnet_style_object_initializer = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_compound_assignment = true:warning
+
+#Style - null/type checks
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_throw_expression = true:silent
+csharp_style_conditional_delegate_call = true:warning
+
+#Style - unused
+dotnet_style_readonly_field = true:silent
+dotnet_code_quality_unused_parameters = non_public:silent
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+
+#Style - variable declaration
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = true:warning
+
+#Style - other C# 7.x features
+dotnet_style_prefer_inferred_tuple_names = true:warning
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+
+#Style - C# 8 features
+csharp_prefer_static_local_function = true:warning
+csharp_prefer_simple_using_statement = true:silent
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_switch_expression = false:none
+
+#Supressing roslyn built-in analyzers
+# Suppress: EC112
+
+#Private method is unused
+dotnet_diagnostic.IDE0051.severity = silent
+#Private member is unused
+dotnet_diagnostic.IDE0052.severity = silent
+
+#Rules for disposable
+dotnet_diagnostic.IDE0067.severity = none
+dotnet_diagnostic.IDE0068.severity = none
+dotnet_diagnostic.IDE0069.severity = none
+
+#Disable operator overloads requiring alternate named methods
+dotnet_diagnostic.CA2225.severity = none
+
+# Banned APIs
+dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-empty/.gitignore b/Templates/Rulesets/ruleset-empty/.gitignore
new file mode 100644
index 0000000000..940794e60f
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/.gitignore
@@ -0,0 +1,288 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
diff --git a/Templates/Rulesets/ruleset-empty/.template.config/template.json b/Templates/Rulesets/ruleset-empty/.template.config/template.json
new file mode 100644
index 0000000000..6bfe2e19dc
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/.template.config/template.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "ppy Pty Ltd",
+ "classifications": [
+ "Console"
+ ],
+ "name": "osu! ruleset",
+ "identity": "ppy.osu.Game.Templates.Rulesets",
+ "shortName": "ruleset",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "EmptyFreeform",
+ "preferNameDirectory": true
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/launch.json
new file mode 100644
index 0000000000..fd03878699
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.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/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Debug)",
+ "env": {},
+ "console": "internalConsole"
+ },
+ {
+ "name": "VisualTests (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Release)",
+ "env": {},
+ "console": "internalConsole"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/.vscode/tasks.json
new file mode 100644
index 0000000000..509df6a510
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.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.Rulesets.EmptyFreeformRuleset.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.Rulesets.EmptyFreeformRuleset.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/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.cs
new file mode 100644
index 0000000000..9c512a01ea
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuGame.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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Platform;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Tests
+{
+ public class TestSceneOsuGame : OsuTestScene
+ {
+ [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
+ };
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuPlayer.cs
new file mode 100644
index 0000000000..0f2ddf82a5
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/TestSceneOsuPlayer.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 NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Tests
+{
+ [TestFixture]
+ public class TestSceneOsuPlayer : PlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new EmptyFreeformRuleset();
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.cs
new file mode 100644
index 0000000000..4f810ce17f
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/VisualTestRunner.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;
+using osu.Framework;
+using osu.Framework.Platform;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Tests
+{
+ public static class VisualTestRunner
+ {
+ [STAThread]
+ public static int Main(string[] args)
+ {
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ {
+ host.Run(new OsuTestBrowser());
+ return 0;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
new file mode 100644
index 0000000000..98a32f9b3a
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj
@@ -0,0 +1,26 @@
+
+
+ osu.Game.Rulesets.EmptyFreeform.Tests.VisualTestRunner
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ WinExe
+ net5.0
+ osu.Game.Rulesets.EmptyFreeform.Tests
+
+
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln
new file mode 100644
index 0000000000..706df08472
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln
@@ -0,0 +1,96 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29123.88
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.EmptyFreeform", "osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyFreeform.Tests", "osu.Game.Rulesets.EmptyFreeform.Tests\osu.Game.Rulesets.EmptyFreeform.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ VisualTests|Any CPU = VisualTests|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668}
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+EndGlobal
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings
new file mode 100644
index 0000000000..aa8f8739c1
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.sln.DotSettings
@@ -0,0 +1,934 @@
+
+ True
+ True
+ True
+ True
+ ExplicitlyExcluded
+ ExplicitlyExcluded
+ SOLUTION
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
+ WARNING
+ WARNING
+ True
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ 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
+ HINT
+ WARNING
+ HINT
+ SUGGESTION
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ ERROR
+ WARNING
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ 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
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+
+ True
+ WARNING
+ WARNING
+ 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)
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ Explicit
+ ExpressionBody
+ BlockBody
+ True
+ NEXT_LINE
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ NEXT_LINE
+ MULTILINE
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ True
+ NEXT_LINE
+ NEVER
+ NEVER
+ True
+ False
+ True
+ NEVER
+ False
+ False
+ True
+ False
+ False
+ True
+ True
+ False
+ False
+ CHOP_IF_LONG
+ True
+ 200
+ CHOP_IF_LONG
+ False
+ False
+ AABB
+ API
+ BPM
+ GC
+ GL
+ GLSL
+ HID
+ HTML
+ HUD
+ ID
+ IL
+ IOS
+ IP
+ IPC
+ JIT
+ LTRB
+ MD5
+ NS
+ OS
+ PM
+ 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
+ TestFolder
+ 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
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Beatmaps/EmptyFreeformBeatmapConverter.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Beatmaps/EmptyFreeformBeatmapConverter.cs
new file mode 100644
index 0000000000..a441438d80
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Beatmaps/EmptyFreeformBeatmapConverter.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 System.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.EmptyFreeform.Objects;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osuTK;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Beatmaps
+{
+ public class EmptyFreeformBeatmapConverter : BeatmapConverter
+ {
+ public EmptyFreeformBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
+ {
+ }
+
+ // todo: Check for conversion types that should be supported (ie. Beatmap.HitObjects.Any(h => h is IHasXPosition))
+ // https://github.com/ppy/osu/tree/master/osu.Game/Rulesets/Objects/Types
+ public override bool CanConvert() => true;
+
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
+ {
+ yield return new EmptyFreeformHitObject
+ {
+ Samples = original.Samples,
+ StartTime = original.StartTime,
+ Position = (original as IHasPosition)?.Position ?? Vector2.Zero,
+ };
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.cs
new file mode 100644
index 0000000000..59a68245a6
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformDifficultyCalculator.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.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;
+
+namespace osu.Game.Rulesets.EmptyFreeform
+{
+ public class EmptyFreeformDifficultyCalculator : DifficultyCalculator
+ {
+ public EmptyFreeformDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
+ {
+ }
+
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
+ {
+ return new DifficultyAttributes(mods, skills, 0);
+ }
+
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty();
+
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformInputManager.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformInputManager.cs
new file mode 100644
index 0000000000..b292a28c0d
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformInputManager.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.ComponentModel;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.EmptyFreeform
+{
+ public class EmptyFreeformInputManager : RulesetInputManager
+ {
+ public EmptyFreeformInputManager(RulesetInfo ruleset)
+ : base(ruleset, 0, SimultaneousBindingMode.Unique)
+ {
+ }
+ }
+
+ public enum EmptyFreeformAction
+ {
+ [Description("Button 1")]
+ Button1,
+
+ [Description("Button 2")]
+ Button2,
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.cs
new file mode 100644
index 0000000000..96675e3e99
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/EmptyFreeformRuleset.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.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Bindings;
+using osu.Game.Beatmaps;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.EmptyFreeform.Beatmaps;
+using osu.Game.Rulesets.EmptyFreeform.Mods;
+using osu.Game.Rulesets.EmptyFreeform.UI;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.UI;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.EmptyFreeform
+{
+ public class EmptyFreeformRuleset : Ruleset
+ {
+ public override string Description => "a very emptyfreeformruleset ruleset";
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) =>
+ new DrawableEmptyFreeformRuleset(this, beatmap, mods);
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
+ new EmptyFreeformBeatmapConverter(beatmap, this);
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
+ new EmptyFreeformDifficultyCalculator(this, beatmap);
+
+ public override IEnumerable GetModsFor(ModType type)
+ {
+ switch (type)
+ {
+ case ModType.Automation:
+ return new[] { new EmptyFreeformModAutoplay() };
+
+ default:
+ return new Mod[] { null };
+ }
+ }
+
+ public override string ShortName => "emptyfreeformruleset";
+
+ public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
+ {
+ new KeyBinding(InputKey.Z, EmptyFreeformAction.Button1),
+ new KeyBinding(InputKey.X, EmptyFreeformAction.Button2),
+ };
+
+ public override Drawable CreateIcon() => new Icon(ShortName[0]);
+
+ public class Icon : CompositeDrawable
+ {
+ public Icon(char c)
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Circle
+ {
+ Size = new Vector2(20),
+ Colour = Color4.White,
+ },
+ new SpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = c.ToString(),
+ Font = OsuFont.Default.With(size: 18)
+ }
+ };
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.cs
new file mode 100644
index 0000000000..d5c1e9bd15
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Mods/EmptyFreeformModAutoplay.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.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.EmptyFreeform.Objects;
+using osu.Game.Rulesets.EmptyFreeform.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Mods
+{
+ public class EmptyFreeformModAutoplay : ModAutoplay
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score
+ {
+ ScoreInfo = new ScoreInfo
+ {
+ User = new User { Username = "sample" },
+ },
+ Replay = new EmptyFreeformAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.cs
new file mode 100644
index 0000000000..0f38e9fdf8
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/Drawables/DrawableEmptyFreeformHitObject.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 osu.Framework.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables
+{
+ public class DrawableEmptyFreeformHitObject : DrawableHitObject
+ {
+ public DrawableEmptyFreeformHitObject(EmptyFreeformHitObject hitObject)
+ : base(hitObject)
+ {
+ Size = new Vector2(40);
+ Origin = Anchor.Centre;
+
+ Position = hitObject.Position;
+
+ // todo: add visuals.
+ }
+
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
+ {
+ if (timeOffset >= 0)
+ // todo: implement judgement logic
+ ApplyResult(r => r.Type = HitResult.Perfect);
+ }
+
+ protected override void UpdateHitStateTransforms(ArmedState state)
+ {
+ const double duration = 1000;
+
+ switch (state)
+ {
+ case ArmedState.Hit:
+ this.FadeOut(duration, Easing.OutQuint).Expire();
+ break;
+
+ case ArmedState.Miss:
+ this.FadeColour(Color4.Red, duration);
+ this.FadeOut(duration, Easing.InQuint).Expire();
+ break;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.cs
new file mode 100644
index 0000000000..9cd18d2d9f
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Objects/EmptyFreeformHitObject.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.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osuTK;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Objects
+{
+ public class EmptyFreeformHitObject : HitObject, IHasPosition
+ {
+ public override Judgement CreateJudgement() => new Judgement();
+
+ public Vector2 Position { get; set; }
+
+ public float X => Position.X;
+ public float Y => Position.Y;
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.cs
new file mode 100644
index 0000000000..6d8d4215a2
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformAutoGenerator.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 System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Replays;
+using osu.Game.Rulesets.EmptyFreeform.Objects;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Replays
+{
+ public class EmptyFreeformAutoGenerator : AutoGenerator
+ {
+ protected Replay Replay;
+ protected List Frames => Replay.Frames;
+
+ public new Beatmap Beatmap => (Beatmap)base.Beatmap;
+
+ public EmptyFreeformAutoGenerator(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ Replay = new Replay();
+ }
+
+ public override Replay Generate()
+ {
+ Frames.Add(new EmptyFreeformReplayFrame());
+
+ foreach (EmptyFreeformHitObject hitObject in Beatmap.HitObjects)
+ {
+ Frames.Add(new EmptyFreeformReplayFrame
+ {
+ Time = hitObject.StartTime,
+ Position = hitObject.Position,
+ // todo: add required inputs and extra frames.
+ });
+ }
+
+ return Replay;
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.cs
new file mode 100644
index 0000000000..f25ea6ec62
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformFramedReplayInputHandler.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 System.Diagnostics;
+using System.Linq;
+using osu.Framework.Input.StateChanges;
+using osu.Framework.Utils;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Replays;
+using osuTK;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Replays
+{
+ public class EmptyFreeformFramedReplayInputHandler : FramedReplayInputHandler
+ {
+ public EmptyFreeformFramedReplayInputHandler(Replay replay)
+ : base(replay)
+ {
+ }
+
+ protected override bool IsImportant(EmptyFreeformReplayFrame frame) => frame.Actions.Any();
+
+ protected Vector2 Position
+ {
+ get
+ {
+ var frame = CurrentFrame;
+
+ if (frame == null)
+ return Vector2.Zero;
+
+ Debug.Assert(CurrentTime != null);
+
+ return Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time);
+ }
+ }
+
+ public override void CollectPendingInputs(List inputs)
+ {
+ inputs.Add(new MousePositionAbsoluteInput
+ {
+ Position = GamefieldToScreenSpace(Position),
+ });
+ inputs.Add(new ReplayState
+ {
+ PressedActions = CurrentFrame?.Actions ?? new List(),
+ });
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformReplayFrame.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformReplayFrame.cs
new file mode 100644
index 0000000000..c84101ca70
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/Replays/EmptyFreeformReplayFrame.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.Collections.Generic;
+using osu.Game.Rulesets.Replays;
+using osuTK;
+
+namespace osu.Game.Rulesets.EmptyFreeform.Replays
+{
+ public class EmptyFreeformReplayFrame : ReplayFrame
+ {
+ public List Actions = new List();
+ public Vector2 Position;
+
+ public EmptyFreeformReplayFrame(EmptyFreeformAction? button = null)
+ {
+ if (button.HasValue)
+ Actions.Add(button.Value);
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/DrawableEmptyFreeformRuleset.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/DrawableEmptyFreeformRuleset.cs
new file mode 100644
index 0000000000..290f35f516
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/DrawableEmptyFreeformRuleset.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.Allocation;
+using osu.Framework.Input;
+using osu.Game.Beatmaps;
+using osu.Game.Input.Handlers;
+using osu.Game.Replays;
+using osu.Game.Rulesets.EmptyFreeform.Objects;
+using osu.Game.Rulesets.EmptyFreeform.Objects.Drawables;
+using osu.Game.Rulesets.EmptyFreeform.Replays;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.EmptyFreeform.UI
+{
+ [Cached]
+ public class DrawableEmptyFreeformRuleset : DrawableRuleset
+ {
+ public DrawableEmptyFreeformRuleset(EmptyFreeformRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
+ : base(ruleset, beatmap, mods)
+ {
+ }
+
+ protected override Playfield CreatePlayfield() => new EmptyFreeformPlayfield();
+
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new EmptyFreeformFramedReplayInputHandler(replay);
+
+ public override DrawableHitObject CreateDrawableRepresentation(EmptyFreeformHitObject h) => new DrawableEmptyFreeformHitObject(h);
+
+ protected override PassThroughInputManager CreateInputManager() => new EmptyFreeformInputManager(Ruleset?.RulesetInfo);
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/EmptyFreeformPlayfield.cs b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/EmptyFreeformPlayfield.cs
new file mode 100644
index 0000000000..9df5935c45
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/UI/EmptyFreeformPlayfield.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;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.EmptyFreeform.UI
+{
+ [Cached]
+ public class EmptyFreeformPlayfield : Playfield
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddRangeInternal(new Drawable[]
+ {
+ HitObjectContainer,
+ });
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
new file mode 100644
index 0000000000..cfe2bd1cb2
--- /dev/null
+++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj
@@ -0,0 +1,15 @@
+
+
+ netstandard2.1
+ osu.Game.Rulesets.Sample
+ Library
+ AnyCPU
+ osu.Game.Rulesets.EmptyFreeform
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-example/.editorconfig b/Templates/Rulesets/ruleset-example/.editorconfig
new file mode 100644
index 0000000000..f3badda9b3
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/.editorconfig
@@ -0,0 +1,200 @@
+# EditorConfig is awesome: http://editorconfig.org
+root = true
+
+[*.cs]
+end_of_line = crlf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+#Roslyn naming styles
+
+#PascalCase for public and protected members
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.public_members_pascalcase.severity = error
+dotnet_naming_rule.public_members_pascalcase.symbols = public_members
+dotnet_naming_rule.public_members_pascalcase.style = pascalcase
+
+#camelCase for private members
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_symbols.private_members.applicable_accessibilities = private
+dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.private_members_camelcase.severity = warning
+dotnet_naming_rule.private_members_camelcase.symbols = private_members
+dotnet_naming_rule.private_members_camelcase.style = camelcase
+
+dotnet_naming_symbols.local_function.applicable_kinds = local_function
+dotnet_naming_rule.local_function_camelcase.severity = warning
+dotnet_naming_rule.local_function_camelcase.symbols = local_function
+dotnet_naming_rule.local_function_camelcase.style = camelcase
+
+#all_lower for private and local constants/static readonlys
+dotnet_naming_style.all_lower.capitalization = all_lower
+dotnet_naming_style.all_lower.word_separator = _
+
+dotnet_naming_symbols.private_constants.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants.required_modifiers = const
+dotnet_naming_symbols.private_constants.applicable_kinds = field
+dotnet_naming_rule.private_const_all_lower.severity = warning
+dotnet_naming_rule.private_const_all_lower.symbols = private_constants
+dotnet_naming_rule.private_const_all_lower.style = all_lower
+
+dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
+dotnet_naming_rule.private_static_readonly_all_lower.severity = warning
+dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly
+dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+dotnet_naming_rule.local_const_all_lower.severity = warning
+dotnet_naming_rule.local_const_all_lower.symbols = local_constants
+dotnet_naming_rule.local_const_all_lower.style = all_lower
+
+#ALL_UPPER for non private constants/static readonlys
+dotnet_naming_style.all_upper.capitalization = all_upper
+dotnet_naming_style.all_upper.word_separator = _
+
+dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_constants.required_modifiers = const
+dotnet_naming_symbols.public_constants.applicable_kinds = field
+dotnet_naming_rule.public_const_all_upper.severity = warning
+dotnet_naming_rule.public_const_all_upper.symbols = public_constants
+dotnet_naming_rule.public_const_all_upper.style = all_upper
+
+dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.public_static_readonly.applicable_kinds = field
+dotnet_naming_rule.public_static_readonly_all_upper.severity = warning
+dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly
+dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper
+
+#Roslyn formating options
+
+#Formatting - indentation options
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_open_brace = all
+#csharp_new_line_before_members_in_anonymous_types = true
+#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing
+csharp_new_line_between_query_expression_clauses = true
+
+#Formatting - organize using options
+dotnet_sort_system_directives_first = true
+
+#Formatting - spacing options
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#Roslyn language styles
+
+#Style - this. qualification
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_event = false:warning
+
+#Style - type names
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+csharp_style_var_when_type_is_apparent = true:none
+csharp_style_var_for_built_in_types = true:none
+csharp_style_var_elsewhere = true:silent
+
+#Style - modifiers
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning
+
+#Style - parentheses
+# Skipped because roslyn cannot separate +-*/ with << >>
+
+#Style - expression bodies
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+csharp_style_expression_bodied_local_functions = true:silent
+
+#Style - expression preferences
+dotnet_style_object_initializer = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_compound_assignment = true:warning
+
+#Style - null/type checks
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_throw_expression = true:silent
+csharp_style_conditional_delegate_call = true:warning
+
+#Style - unused
+dotnet_style_readonly_field = true:silent
+dotnet_code_quality_unused_parameters = non_public:silent
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+
+#Style - variable declaration
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = true:warning
+
+#Style - other C# 7.x features
+dotnet_style_prefer_inferred_tuple_names = true:warning
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+
+#Style - C# 8 features
+csharp_prefer_static_local_function = true:warning
+csharp_prefer_simple_using_statement = true:silent
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_switch_expression = false:none
+
+#Supressing roslyn built-in analyzers
+# Suppress: EC112
+
+#Private method is unused
+dotnet_diagnostic.IDE0051.severity = silent
+#Private member is unused
+dotnet_diagnostic.IDE0052.severity = silent
+
+#Rules for disposable
+dotnet_diagnostic.IDE0067.severity = none
+dotnet_diagnostic.IDE0068.severity = none
+dotnet_diagnostic.IDE0069.severity = none
+
+#Disable operator overloads requiring alternate named methods
+dotnet_diagnostic.CA2225.severity = none
+
+# Banned APIs
+dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-example/.gitignore b/Templates/Rulesets/ruleset-example/.gitignore
new file mode 100644
index 0000000000..940794e60f
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/.gitignore
@@ -0,0 +1,288 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
diff --git a/Templates/Rulesets/ruleset-example/.template.config/template.json b/Templates/Rulesets/ruleset-example/.template.config/template.json
new file mode 100644
index 0000000000..5d2f6f1ebd
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/.template.config/template.json
@@ -0,0 +1,17 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "ppy Pty Ltd",
+ "classifications": [
+ "Console"
+ ],
+ "name": "osu! ruleset (pippidon example)",
+ "identity": "ppy.osu.Game.Templates.Rulesets.Pippidon",
+ "shortName": "ruleset-example",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "Pippidon",
+ "preferNameDirectory": true
+}
+
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json
new file mode 100644
index 0000000000..bd9db14259
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.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/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Debug)",
+ "env": {},
+ "console": "internalConsole"
+ },
+ {
+ "name": "VisualTests (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Release)",
+ "env": {},
+ "console": "internalConsole"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json
new file mode 100644
index 0000000000..0ee07c1036
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.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.Rulesets.Pippidon.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.Rulesets.Pippidon.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/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
new file mode 100644
index 0000000000..270d906b01
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Platform;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Pippidon.Tests
+{
+ public class TestSceneOsuGame : OsuTestScene
+ {
+ [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
+ };
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs
new file mode 100644
index 0000000000..f00528900c
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.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 NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Pippidon.Tests
+{
+ [TestFixture]
+ public class TestSceneOsuPlayer : PlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new PippidonRuleset();
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
new file mode 100644
index 0000000000..fd6bd9b714
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.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;
+using osu.Framework;
+using osu.Framework.Platform;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Pippidon.Tests
+{
+ public static class VisualTestRunner
+ {
+ [STAThread]
+ public static int Main(string[] args)
+ {
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ {
+ host.Run(new OsuTestBrowser());
+ return 0;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
new file mode 100644
index 0000000000..afa7b03536
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -0,0 +1,26 @@
+
+
+ osu.Game.Rulesets.Pippidon.Tests.VisualTestRunner
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ WinExe
+ net5.0
+ osu.Game.Rulesets.Pippidon.Tests
+
+
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln
new file mode 100644
index 0000000000..bccffcd7ff
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln
@@ -0,0 +1,96 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29123.88
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Pippidon", "osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ VisualTests|Any CPU = VisualTests|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668}
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+EndGlobal
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
new file mode 100644
index 0000000000..aa8f8739c1
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
@@ -0,0 +1,934 @@
+
+ True
+ True
+ True
+ True
+ ExplicitlyExcluded
+ ExplicitlyExcluded
+ SOLUTION
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
+ WARNING
+ WARNING
+ True
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ 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
+ HINT
+ WARNING
+ HINT
+ SUGGESTION
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ ERROR
+ WARNING
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ 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
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+
+ True
+ WARNING
+ WARNING
+ 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)
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ Explicit
+ ExpressionBody
+ BlockBody
+ True
+ NEXT_LINE
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ NEXT_LINE
+ MULTILINE
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ True
+ NEXT_LINE
+ NEVER
+ NEVER
+ True
+ False
+ True
+ NEVER
+ False
+ False
+ True
+ False
+ False
+ True
+ True
+ False
+ False
+ CHOP_IF_LONG
+ True
+ 200
+ CHOP_IF_LONG
+ False
+ False
+ AABB
+ API
+ BPM
+ GC
+ GL
+ GLSL
+ HID
+ HTML
+ HUD
+ ID
+ IL
+ IOS
+ IP
+ IPC
+ JIT
+ LTRB
+ MD5
+ NS
+ OS
+ PM
+ 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
+ TestFolder
+ 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
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs
new file mode 100644
index 0000000000..a2a4784603
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.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.Linq;
+using System.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Pippidon.Objects;
+using osuTK;
+
+namespace osu.Game.Rulesets.Pippidon.Beatmaps
+{
+ public class PippidonBeatmapConverter : BeatmapConverter
+ {
+ public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
+ {
+ }
+
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasPosition);
+
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
+ {
+ yield return new PippidonHitObject
+ {
+ Samples = original.Samples,
+ StartTime = original.StartTime,
+ Position = (original as IHasPosition)?.Position ?? Vector2.Zero,
+ };
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs
new file mode 100644
index 0000000000..8ea334c99c
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.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.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Pippidon.Objects;
+using osu.Game.Rulesets.Pippidon.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Pippidon.Mods
+{
+ public class PippidonModAutoplay : ModAutoplay
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score
+ {
+ ScoreInfo = new ScoreInfo
+ {
+ User = new User { Username = "sample" },
+ },
+ Replay = new PippidonAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs
new file mode 100644
index 0000000000..399d6adda2
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.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.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Extensions.Color4Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
+{
+ public class DrawablePippidonHitObject : DrawableHitObject
+ {
+ private const double time_preempt = 600;
+ private const double time_fadein = 400;
+
+ public override bool HandlePositionalInput => true;
+
+ public DrawablePippidonHitObject(PippidonHitObject hitObject)
+ : base(hitObject)
+ {
+ Size = new Vector2(80);
+
+ Origin = Anchor.Centre;
+ Position = hitObject.Position;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ AddInternal(new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ Texture = textures.Get("coin"),
+ });
+ }
+
+ public override IEnumerable GetSamples() => new[]
+ {
+ new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK)
+ };
+
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
+ {
+ if (timeOffset >= 0)
+ ApplyResult(r => r.Type = IsHovered ? HitResult.Perfect : HitResult.Miss);
+ }
+
+ protected override double InitialLifetimeOffset => time_preempt;
+
+ protected override void UpdateInitialTransforms() => this.FadeInFromZero(time_fadein);
+
+ protected override void UpdateHitStateTransforms(ArmedState state)
+ {
+ switch (state)
+ {
+ case ArmedState.Hit:
+ this.ScaleTo(5, 1500, Easing.OutQuint).FadeOut(1500, Easing.OutQuint).Expire();
+ break;
+
+ case ArmedState.Miss:
+ const double duration = 1000;
+
+ this.ScaleTo(0.8f, duration, Easing.OutQuint);
+ this.MoveToOffset(new Vector2(0, 10), duration, Easing.In);
+ this.FadeColour(Color4.Red.Opacity(0.5f), duration / 2, Easing.OutQuint).Then().FadeOut(duration / 2, Easing.InQuint).Expire();
+ break;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs
new file mode 100644
index 0000000000..0c22554e82
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.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.Judgements;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osuTK;
+
+namespace osu.Game.Rulesets.Pippidon.Objects
+{
+ public class PippidonHitObject : HitObject, IHasPosition
+ {
+ public override Judgement CreateJudgement() => new Judgement();
+
+ public Vector2 Position { get; set; }
+
+ public float X => Position.X;
+ public float Y => Position.Y;
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs
new file mode 100644
index 0000000000..f6340f6c25
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.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.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;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonDifficultyCalculator : DifficultyCalculator
+ {
+ public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
+ {
+ }
+
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
+ {
+ return new DifficultyAttributes(mods, skills, 0);
+ }
+
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty();
+
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs
new file mode 100644
index 0000000000..aa7fa3188b
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.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.ComponentModel;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonInputManager : RulesetInputManager
+ {
+ public PippidonInputManager(RulesetInfo ruleset)
+ : base(ruleset, 0, SimultaneousBindingMode.Unique)
+ {
+ }
+ }
+
+ public enum PippidonAction
+ {
+ [Description("Button 1")]
+ Button1,
+
+ [Description("Button 2")]
+ Button2,
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
new file mode 100644
index 0000000000..89fed791cd
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.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.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input.Bindings;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Pippidon.Beatmaps;
+using osu.Game.Rulesets.Pippidon.Mods;
+using osu.Game.Rulesets.Pippidon.UI;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonRuleset : Ruleset
+ {
+ public override string Description => "gather the osu!coins";
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) =>
+ new DrawablePippidonRuleset(this, beatmap, mods);
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) =>
+ new PippidonBeatmapConverter(beatmap, this);
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) =>
+ new PippidonDifficultyCalculator(this, beatmap);
+
+ public override IEnumerable GetModsFor(ModType type)
+ {
+ switch (type)
+ {
+ case ModType.Automation:
+ return new[] { new PippidonModAutoplay() };
+
+ default:
+ return new Mod[] { null };
+ }
+ }
+
+ public override string ShortName => "pippidon";
+
+ public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
+ {
+ new KeyBinding(InputKey.Z, PippidonAction.Button1),
+ new KeyBinding(InputKey.X, PippidonAction.Button2),
+ };
+
+ public override Drawable CreateIcon() => new Sprite
+ {
+ Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
+ };
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs
new file mode 100644
index 0000000000..9c54b82e38
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.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.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Pippidon.Objects;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.Pippidon.Replays
+{
+ public class PippidonAutoGenerator : AutoGenerator
+ {
+ protected Replay Replay;
+ protected List Frames => Replay.Frames;
+
+ public new Beatmap Beatmap => (Beatmap)base.Beatmap;
+
+ public PippidonAutoGenerator(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ Replay = new Replay();
+ }
+
+ public override Replay Generate()
+ {
+ Frames.Add(new PippidonReplayFrame());
+
+ foreach (PippidonHitObject hitObject in Beatmap.HitObjects)
+ {
+ Frames.Add(new PippidonReplayFrame
+ {
+ Time = hitObject.StartTime,
+ Position = hitObject.Position,
+ });
+ }
+
+ return Replay;
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
new file mode 100644
index 0000000000..18efa6b885
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.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.Collections.Generic;
+using System.Diagnostics;
+using osu.Framework.Input.StateChanges;
+using osu.Framework.Utils;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Replays;
+using osuTK;
+
+namespace osu.Game.Rulesets.Pippidon.Replays
+{
+ public class PippidonFramedReplayInputHandler : FramedReplayInputHandler
+ {
+ public PippidonFramedReplayInputHandler(Replay replay)
+ : base(replay)
+ {
+ }
+
+ protected override bool IsImportant(PippidonReplayFrame frame) => true;
+
+ protected Vector2 Position
+ {
+ get
+ {
+ var frame = CurrentFrame;
+
+ if (frame == null)
+ return Vector2.Zero;
+
+ Debug.Assert(CurrentTime != null);
+
+ return NextFrame != null ? Interpolation.ValueAt(CurrentTime.Value, frame.Position, NextFrame.Position, frame.Time, NextFrame.Time) : frame.Position;
+ }
+ }
+
+ public override void CollectPendingInputs(List inputs)
+ {
+ inputs.Add(new MousePositionAbsoluteInput
+ {
+ Position = GamefieldToScreenSpace(Position)
+ });
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs
new file mode 100644
index 0000000000..949ca160be
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.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.Rulesets.Replays;
+using osuTK;
+
+namespace osu.Game.Rulesets.Pippidon.Replays
+{
+ public class PippidonReplayFrame : ReplayFrame
+ {
+ public Vector2 Position;
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3
new file mode 100644
index 0000000000..90b13d1f73
Binary files /dev/null and b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 differ
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png
new file mode 100644
index 0000000000..e79d2528ec
Binary files /dev/null and b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png differ
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png
new file mode 100644
index 0000000000..3cd89c6ce6
Binary files /dev/null and b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png differ
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.cs
new file mode 100644
index 0000000000..1c4fe698c2
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/Scoring/PippidonScoreProcessor.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 osu.Game.Rulesets.Scoring;
+
+namespace osu.Game.Rulesets.Pippidon.Scoring
+{
+ public class PippidonScoreProcessor : ScoreProcessor
+ {
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs
new file mode 100644
index 0000000000..d923963bef
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.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 System.Collections.Generic;
+using osu.Framework.Allocation;
+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.Pippidon.Objects;
+using osu.Game.Rulesets.Pippidon.Objects.Drawables;
+using osu.Game.Rulesets.Pippidon.Replays;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Pippidon.UI
+{
+ [Cached]
+ public class DrawablePippidonRuleset : DrawableRuleset
+ {
+ public DrawablePippidonRuleset(PippidonRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
+ : base(ruleset, beatmap, mods)
+ {
+ }
+
+ public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new PippidonPlayfieldAdjustmentContainer();
+
+ protected override Playfield CreatePlayfield() => new PippidonPlayfield();
+
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new PippidonFramedReplayInputHandler(replay);
+
+ public override DrawableHitObject CreateDrawableRepresentation(PippidonHitObject h) => new DrawablePippidonHitObject(h);
+
+ protected override PassThroughInputManager CreateInputManager() => new PippidonInputManager(Ruleset?.RulesetInfo);
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonCursorContainer.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonCursorContainer.cs
new file mode 100644
index 0000000000..9de3f4ba14
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonCursorContainer.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.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Rulesets.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Pippidon.UI
+{
+ public class PippidonCursorContainer : GameplayCursorContainer
+ {
+ private Sprite cursorSprite;
+ private Texture cursorTexture;
+
+ protected override Drawable CreateCursor() => cursorSprite = new Sprite
+ {
+ Scale = new Vector2(0.5f),
+ Origin = Anchor.Centre,
+ Texture = cursorTexture,
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ cursorTexture = textures.Get("character");
+
+ if (cursorSprite != null)
+ cursorSprite.Texture = cursorTexture;
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
new file mode 100644
index 0000000000..b5a97c5ea3
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.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.Allocation;
+using osu.Framework.Graphics;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Pippidon.UI
+{
+ [Cached]
+ public class PippidonPlayfield : Playfield
+ {
+ protected override GameplayCursorContainer CreateCursor() => new PippidonCursorContainer();
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddRangeInternal(new Drawable[]
+ {
+ HitObjectContainer,
+ });
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfieldAdjustmentContainer.cs b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfieldAdjustmentContainer.cs
new file mode 100644
index 0000000000..9236683827
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfieldAdjustmentContainer.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.Pippidon.UI
+{
+ public class PippidonPlayfieldAdjustmentContainer : PlayfieldAdjustmentContainer
+ {
+ public PippidonPlayfieldAdjustmentContainer()
+ {
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ Size = new Vector2(0.8f);
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
new file mode 100644
index 0000000000..61b859f45b
--- /dev/null
+++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
@@ -0,0 +1,15 @@
+
+
+ netstandard2.1
+ osu.Game.Rulesets.Sample
+ Library
+ AnyCPU
+ osu.Game.Rulesets.Pippidon
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig
new file mode 100644
index 0000000000..f3badda9b3
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/.editorconfig
@@ -0,0 +1,200 @@
+# EditorConfig is awesome: http://editorconfig.org
+root = true
+
+[*.cs]
+end_of_line = crlf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+#Roslyn naming styles
+
+#PascalCase for public and protected members
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.public_members_pascalcase.severity = error
+dotnet_naming_rule.public_members_pascalcase.symbols = public_members
+dotnet_naming_rule.public_members_pascalcase.style = pascalcase
+
+#camelCase for private members
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_symbols.private_members.applicable_accessibilities = private
+dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.private_members_camelcase.severity = warning
+dotnet_naming_rule.private_members_camelcase.symbols = private_members
+dotnet_naming_rule.private_members_camelcase.style = camelcase
+
+dotnet_naming_symbols.local_function.applicable_kinds = local_function
+dotnet_naming_rule.local_function_camelcase.severity = warning
+dotnet_naming_rule.local_function_camelcase.symbols = local_function
+dotnet_naming_rule.local_function_camelcase.style = camelcase
+
+#all_lower for private and local constants/static readonlys
+dotnet_naming_style.all_lower.capitalization = all_lower
+dotnet_naming_style.all_lower.word_separator = _
+
+dotnet_naming_symbols.private_constants.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants.required_modifiers = const
+dotnet_naming_symbols.private_constants.applicable_kinds = field
+dotnet_naming_rule.private_const_all_lower.severity = warning
+dotnet_naming_rule.private_const_all_lower.symbols = private_constants
+dotnet_naming_rule.private_const_all_lower.style = all_lower
+
+dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
+dotnet_naming_rule.private_static_readonly_all_lower.severity = warning
+dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly
+dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+dotnet_naming_rule.local_const_all_lower.severity = warning
+dotnet_naming_rule.local_const_all_lower.symbols = local_constants
+dotnet_naming_rule.local_const_all_lower.style = all_lower
+
+#ALL_UPPER for non private constants/static readonlys
+dotnet_naming_style.all_upper.capitalization = all_upper
+dotnet_naming_style.all_upper.word_separator = _
+
+dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_constants.required_modifiers = const
+dotnet_naming_symbols.public_constants.applicable_kinds = field
+dotnet_naming_rule.public_const_all_upper.severity = warning
+dotnet_naming_rule.public_const_all_upper.symbols = public_constants
+dotnet_naming_rule.public_const_all_upper.style = all_upper
+
+dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.public_static_readonly.applicable_kinds = field
+dotnet_naming_rule.public_static_readonly_all_upper.severity = warning
+dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly
+dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper
+
+#Roslyn formating options
+
+#Formatting - indentation options
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_open_brace = all
+#csharp_new_line_before_members_in_anonymous_types = true
+#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing
+csharp_new_line_between_query_expression_clauses = true
+
+#Formatting - organize using options
+dotnet_sort_system_directives_first = true
+
+#Formatting - spacing options
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#Roslyn language styles
+
+#Style - this. qualification
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_event = false:warning
+
+#Style - type names
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+csharp_style_var_when_type_is_apparent = true:none
+csharp_style_var_for_built_in_types = true:none
+csharp_style_var_elsewhere = true:silent
+
+#Style - modifiers
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning
+
+#Style - parentheses
+# Skipped because roslyn cannot separate +-*/ with << >>
+
+#Style - expression bodies
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+csharp_style_expression_bodied_local_functions = true:silent
+
+#Style - expression preferences
+dotnet_style_object_initializer = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_compound_assignment = true:warning
+
+#Style - null/type checks
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_throw_expression = true:silent
+csharp_style_conditional_delegate_call = true:warning
+
+#Style - unused
+dotnet_style_readonly_field = true:silent
+dotnet_code_quality_unused_parameters = non_public:silent
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+
+#Style - variable declaration
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = true:warning
+
+#Style - other C# 7.x features
+dotnet_style_prefer_inferred_tuple_names = true:warning
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+
+#Style - C# 8 features
+csharp_prefer_static_local_function = true:warning
+csharp_prefer_simple_using_statement = true:silent
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_switch_expression = false:none
+
+#Supressing roslyn built-in analyzers
+# Suppress: EC112
+
+#Private method is unused
+dotnet_diagnostic.IDE0051.severity = silent
+#Private member is unused
+dotnet_diagnostic.IDE0052.severity = silent
+
+#Rules for disposable
+dotnet_diagnostic.IDE0067.severity = none
+dotnet_diagnostic.IDE0068.severity = none
+dotnet_diagnostic.IDE0069.severity = none
+
+#Disable operator overloads requiring alternate named methods
+dotnet_diagnostic.CA2225.severity = none
+
+# Banned APIs
+dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.gitignore b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore
new file mode 100644
index 0000000000..940794e60f
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/.gitignore
@@ -0,0 +1,288 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/.template.config/template.json b/Templates/Rulesets/ruleset-scrolling-empty/.template.config/template.json
new file mode 100644
index 0000000000..3eb99a1f9d
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/.template.config/template.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "ppy Pty Ltd",
+ "classifications": [
+ "Console"
+ ],
+ "name": "osu! ruleset (scrolling)",
+ "identity": "ppy.osu.Game.Templates.Rulesets.Scrolling",
+ "shortName": "ruleset-scrolling",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "EmptyScrolling",
+ "preferNameDirectory": true
+}
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/launch.json
new file mode 100644
index 0000000000..24e4873ed6
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.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/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Debug)",
+ "env": {},
+ "console": "internalConsole"
+ },
+ {
+ "name": "VisualTests (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Release)",
+ "env": {},
+ "console": "internalConsole"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/.vscode/tasks.json
new file mode 100644
index 0000000000..00d0dc7d9b
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.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.Rulesets.EmptyScrolling.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.Rulesets.EmptyScrolling.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/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.cs
new file mode 100644
index 0000000000..aed6abb6bf
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuGame.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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Platform;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Tests
+{
+ public class TestSceneOsuGame : OsuTestScene
+ {
+ [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
+ };
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuPlayer.cs
new file mode 100644
index 0000000000..9460576196
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/TestSceneOsuPlayer.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 NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Tests
+{
+ [TestFixture]
+ public class TestSceneOsuPlayer : PlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new EmptyScrollingRuleset();
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.cs
new file mode 100644
index 0000000000..65cfb2bff4
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/VisualTestRunner.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;
+using osu.Framework;
+using osu.Framework.Platform;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Tests
+{
+ public static class VisualTestRunner
+ {
+ [STAThread]
+ public static int Main(string[] args)
+ {
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ {
+ host.Run(new OsuTestBrowser());
+ return 0;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
new file mode 100644
index 0000000000..c9f87a8551
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj
@@ -0,0 +1,26 @@
+
+
+ osu.Game.Rulesets.EmptyScrolling.Tests.VisualTestRunner
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ WinExe
+ net5.0
+ osu.Game.Rulesets.EmptyScrolling.Tests
+
+
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln
new file mode 100644
index 0000000000..97361e1a7b
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln
@@ -0,0 +1,96 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29123.88
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.EmptyScrolling", "osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.EmptyScrolling.Tests", "osu.Game.Rulesets.EmptyScrolling.Tests\osu.Game.Rulesets.EmptyScrolling.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ VisualTests|Any CPU = VisualTests|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668}
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+EndGlobal
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings
new file mode 100644
index 0000000000..aa8f8739c1
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.sln.DotSettings
@@ -0,0 +1,934 @@
+
+ True
+ True
+ True
+ True
+ ExplicitlyExcluded
+ ExplicitlyExcluded
+ SOLUTION
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
+ WARNING
+ WARNING
+ True
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ 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
+ HINT
+ WARNING
+ HINT
+ SUGGESTION
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ ERROR
+ WARNING
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ 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
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+
+ True
+ WARNING
+ WARNING
+ 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)
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ Explicit
+ ExpressionBody
+ BlockBody
+ True
+ NEXT_LINE
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ NEXT_LINE
+ MULTILINE
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ True
+ NEXT_LINE
+ NEVER
+ NEVER
+ True
+ False
+ True
+ NEVER
+ False
+ False
+ True
+ False
+ False
+ True
+ True
+ False
+ False
+ CHOP_IF_LONG
+ True
+ 200
+ CHOP_IF_LONG
+ False
+ False
+ AABB
+ API
+ BPM
+ GC
+ GL
+ GLSL
+ HID
+ HTML
+ HUD
+ ID
+ IL
+ IOS
+ IP
+ IPC
+ JIT
+ LTRB
+ MD5
+ NS
+ OS
+ PM
+ 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
+ TestFolder
+ 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
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Beatmaps/EmptyScrollingBeatmapConverter.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Beatmaps/EmptyScrollingBeatmapConverter.cs
new file mode 100644
index 0000000000..02fb9a9dd5
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Beatmaps/EmptyScrollingBeatmapConverter.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.Collections.Generic;
+using System.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.EmptyScrolling.Objects;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Beatmaps
+{
+ public class EmptyScrollingBeatmapConverter : BeatmapConverter
+ {
+ public EmptyScrollingBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
+ {
+ }
+
+ // todo: Check for conversion types that should be supported (ie. Beatmap.HitObjects.Any(h => h is IHasXPosition))
+ // https://github.com/ppy/osu/tree/master/osu.Game/Rulesets/Objects/Types
+ public override bool CanConvert() => true;
+
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
+ {
+ yield return new EmptyScrollingHitObject
+ {
+ Samples = original.Samples,
+ StartTime = original.StartTime,
+ };
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.cs
new file mode 100644
index 0000000000..7f29c4e712
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingDifficultyCalculator.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.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;
+
+namespace osu.Game.Rulesets.EmptyScrolling
+{
+ public class EmptyScrollingDifficultyCalculator : DifficultyCalculator
+ {
+ public EmptyScrollingDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
+ {
+ }
+
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
+ {
+ return new DifficultyAttributes(mods, skills, 0);
+ }
+
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty();
+
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingInputManager.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingInputManager.cs
new file mode 100644
index 0000000000..632e04f301
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingInputManager.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.ComponentModel;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.EmptyScrolling
+{
+ public class EmptyScrollingInputManager : RulesetInputManager
+ {
+ public EmptyScrollingInputManager(RulesetInfo ruleset)
+ : base(ruleset, 0, SimultaneousBindingMode.Unique)
+ {
+ }
+ }
+
+ public enum EmptyScrollingAction
+ {
+ [Description("Button 1")]
+ Button1,
+
+ [Description("Button 2")]
+ Button2,
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.cs
new file mode 100644
index 0000000000..c1d4de52b7
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/EmptyScrollingRuleset.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.Collections.Generic;
+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.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.EmptyScrolling.Beatmaps;
+using osu.Game.Rulesets.EmptyScrolling.Mods;
+using osu.Game.Rulesets.EmptyScrolling.UI;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.EmptyScrolling
+{
+ public class EmptyScrollingRuleset : Ruleset
+ {
+ public override string Description => "a very emptyscrolling ruleset";
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableEmptyScrollingRuleset(this, beatmap, mods);
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new EmptyScrollingBeatmapConverter(beatmap, this);
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new EmptyScrollingDifficultyCalculator(this, beatmap);
+
+ public override IEnumerable GetModsFor(ModType type)
+ {
+ switch (type)
+ {
+ case ModType.Automation:
+ return new[] { new EmptyScrollingModAutoplay() };
+
+ default:
+ return new Mod[] { null };
+ }
+ }
+
+ public override string ShortName => "emptyscrolling";
+
+ public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
+ {
+ new KeyBinding(InputKey.Z, EmptyScrollingAction.Button1),
+ new KeyBinding(InputKey.X, EmptyScrollingAction.Button2),
+ };
+
+ public override Drawable CreateIcon() => new SpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Text = ShortName[0].ToString(),
+ Font = OsuFont.Default.With(size: 18),
+ };
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.cs
new file mode 100644
index 0000000000..6dad1ff43b
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Mods/EmptyScrollingModAutoplay.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.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.EmptyScrolling.Objects;
+using osu.Game.Rulesets.EmptyScrolling.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+using System.Collections.Generic;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Mods
+{
+ public class EmptyScrollingModAutoplay : ModAutoplay
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score
+ {
+ ScoreInfo = new ScoreInfo
+ {
+ User = new User { Username = "sample" },
+ },
+ Replay = new EmptyScrollingAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.cs
new file mode 100644
index 0000000000..b5ff0cde7c
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/Drawables/DrawableEmptyScrollingHitObject.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.Graphics;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables
+{
+ public class DrawableEmptyScrollingHitObject : DrawableHitObject
+ {
+ public DrawableEmptyScrollingHitObject(EmptyScrollingHitObject hitObject)
+ : base(hitObject)
+ {
+ Size = new Vector2(40);
+ Origin = Anchor.Centre;
+
+ // todo: add visuals.
+ }
+
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
+ {
+ if (timeOffset >= 0)
+ // todo: implement judgement logic
+ ApplyResult(r => r.Type = HitResult.Perfect);
+ }
+
+ protected override void UpdateHitStateTransforms(ArmedState state)
+ {
+ const double duration = 1000;
+
+ switch (state)
+ {
+ case ArmedState.Hit:
+ this.FadeOut(duration, Easing.OutQuint).Expire();
+ break;
+
+ case ArmedState.Miss:
+
+ this.FadeColour(Color4.Red, duration);
+ this.FadeOut(duration, Easing.InQuint).Expire();
+ break;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.cs
new file mode 100644
index 0000000000..9b469be496
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Objects/EmptyScrollingHitObject.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.Rulesets.Judgements;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Objects
+{
+ public class EmptyScrollingHitObject : HitObject
+ {
+ public override Judgement CreateJudgement() => new Judgement();
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.cs
new file mode 100644
index 0000000000..7923918842
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingAutoGenerator.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.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Replays;
+using osu.Game.Rulesets.EmptyScrolling.Objects;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Replays
+{
+ public class EmptyScrollingAutoGenerator : AutoGenerator
+ {
+ protected Replay Replay;
+ protected List Frames => Replay.Frames;
+
+ public new Beatmap Beatmap => (Beatmap)base.Beatmap;
+
+ public EmptyScrollingAutoGenerator(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ Replay = new Replay();
+ }
+
+ public override Replay Generate()
+ {
+ Frames.Add(new EmptyScrollingReplayFrame());
+
+ foreach (EmptyScrollingHitObject hitObject in Beatmap.HitObjects)
+ {
+ Frames.Add(new EmptyScrollingReplayFrame
+ {
+ Time = hitObject.StartTime
+ // todo: add required inputs and extra frames.
+ });
+ }
+
+ return Replay;
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.cs
new file mode 100644
index 0000000000..4b998cfca3
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingFramedReplayInputHandler.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 System.Linq;
+using osu.Framework.Input.StateChanges;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Replays
+{
+ public class EmptyScrollingFramedReplayInputHandler : FramedReplayInputHandler
+ {
+ public EmptyScrollingFramedReplayInputHandler(Replay replay)
+ : base(replay)
+ {
+ }
+
+ protected override bool IsImportant(EmptyScrollingReplayFrame frame) => frame.Actions.Any();
+
+ public override void CollectPendingInputs(List inputs)
+ {
+ inputs.Add(new ReplayState
+ {
+ PressedActions = CurrentFrame?.Actions ?? new List(),
+ });
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingReplayFrame.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingReplayFrame.cs
new file mode 100644
index 0000000000..2f19cffd2a
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/Replays/EmptyScrollingReplayFrame.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.Collections.Generic;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.EmptyScrolling.Replays
+{
+ public class EmptyScrollingReplayFrame : ReplayFrame
+ {
+ public List Actions = new List();
+
+ public EmptyScrollingReplayFrame(EmptyScrollingAction? button = null)
+ {
+ if (button.HasValue)
+ Actions.Add(button.Value);
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/DrawableEmptyScrollingRuleset.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/DrawableEmptyScrollingRuleset.cs
new file mode 100644
index 0000000000..620a4abc51
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/DrawableEmptyScrollingRuleset.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.Collections.Generic;
+using osu.Framework.Allocation;
+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.EmptyScrolling.Objects;
+using osu.Game.Rulesets.EmptyScrolling.Objects.Drawables;
+using osu.Game.Rulesets.EmptyScrolling.Replays;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.EmptyScrolling.UI
+{
+ [Cached]
+ public class DrawableEmptyScrollingRuleset : DrawableScrollingRuleset
+ {
+ public DrawableEmptyScrollingRuleset(EmptyScrollingRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
+ : base(ruleset, beatmap, mods)
+ {
+ Direction.Value = ScrollingDirection.Left;
+ TimeRange.Value = 6000;
+ }
+
+ protected override Playfield CreatePlayfield() => new EmptyScrollingPlayfield();
+
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new EmptyScrollingFramedReplayInputHandler(replay);
+
+ public override DrawableHitObject CreateDrawableRepresentation(EmptyScrollingHitObject h) => new DrawableEmptyScrollingHitObject(h);
+
+ protected override PassThroughInputManager CreateInputManager() => new EmptyScrollingInputManager(Ruleset?.RulesetInfo);
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/EmptyScrollingPlayfield.cs b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/EmptyScrollingPlayfield.cs
new file mode 100644
index 0000000000..56620e44b3
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/UI/EmptyScrollingPlayfield.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;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.EmptyScrolling.UI
+{
+ [Cached]
+ public class EmptyScrollingPlayfield : ScrollingPlayfield
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ AddRangeInternal(new Drawable[]
+ {
+ HitObjectContainer,
+ });
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
new file mode 100644
index 0000000000..9dce3c9a0a
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj
@@ -0,0 +1,15 @@
+
+
+ netstandard2.1
+ osu.Game.Rulesets.Sample
+ Library
+ AnyCPU
+ osu.Game.Rulesets.EmptyScrolling
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-example/.editorconfig b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig
new file mode 100644
index 0000000000..f3badda9b3
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/.editorconfig
@@ -0,0 +1,200 @@
+# EditorConfig is awesome: http://editorconfig.org
+root = true
+
+[*.cs]
+end_of_line = crlf
+insert_final_newline = true
+indent_style = space
+indent_size = 4
+trim_trailing_whitespace = true
+
+#Roslyn naming styles
+
+#PascalCase for public and protected members
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+dotnet_naming_symbols.public_members.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.public_members_pascalcase.severity = error
+dotnet_naming_rule.public_members_pascalcase.symbols = public_members
+dotnet_naming_rule.public_members_pascalcase.style = pascalcase
+
+#camelCase for private members
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_symbols.private_members.applicable_accessibilities = private
+dotnet_naming_symbols.private_members.applicable_kinds = property,method,field,event
+dotnet_naming_rule.private_members_camelcase.severity = warning
+dotnet_naming_rule.private_members_camelcase.symbols = private_members
+dotnet_naming_rule.private_members_camelcase.style = camelcase
+
+dotnet_naming_symbols.local_function.applicable_kinds = local_function
+dotnet_naming_rule.local_function_camelcase.severity = warning
+dotnet_naming_rule.local_function_camelcase.symbols = local_function
+dotnet_naming_rule.local_function_camelcase.style = camelcase
+
+#all_lower for private and local constants/static readonlys
+dotnet_naming_style.all_lower.capitalization = all_lower
+dotnet_naming_style.all_lower.word_separator = _
+
+dotnet_naming_symbols.private_constants.applicable_accessibilities = private
+dotnet_naming_symbols.private_constants.required_modifiers = const
+dotnet_naming_symbols.private_constants.applicable_kinds = field
+dotnet_naming_rule.private_const_all_lower.severity = warning
+dotnet_naming_rule.private_const_all_lower.symbols = private_constants
+dotnet_naming_rule.private_const_all_lower.style = all_lower
+
+dotnet_naming_symbols.private_static_readonly.applicable_accessibilities = private
+dotnet_naming_symbols.private_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.private_static_readonly.applicable_kinds = field
+dotnet_naming_rule.private_static_readonly_all_lower.severity = warning
+dotnet_naming_rule.private_static_readonly_all_lower.symbols = private_static_readonly
+dotnet_naming_rule.private_static_readonly_all_lower.style = all_lower
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+dotnet_naming_rule.local_const_all_lower.severity = warning
+dotnet_naming_rule.local_const_all_lower.symbols = local_constants
+dotnet_naming_rule.local_const_all_lower.style = all_lower
+
+#ALL_UPPER for non private constants/static readonlys
+dotnet_naming_style.all_upper.capitalization = all_upper
+dotnet_naming_style.all_upper.word_separator = _
+
+dotnet_naming_symbols.public_constants.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_constants.required_modifiers = const
+dotnet_naming_symbols.public_constants.applicable_kinds = field
+dotnet_naming_rule.public_const_all_upper.severity = warning
+dotnet_naming_rule.public_const_all_upper.symbols = public_constants
+dotnet_naming_rule.public_const_all_upper.style = all_upper
+
+dotnet_naming_symbols.public_static_readonly.applicable_accessibilities = public,internal,protected,protected_internal,private_protected
+dotnet_naming_symbols.public_static_readonly.required_modifiers = static,readonly
+dotnet_naming_symbols.public_static_readonly.applicable_kinds = field
+dotnet_naming_rule.public_static_readonly_all_upper.severity = warning
+dotnet_naming_rule.public_static_readonly_all_upper.symbols = public_static_readonly
+dotnet_naming_rule.public_static_readonly_all_upper.style = all_upper
+
+#Roslyn formating options
+
+#Formatting - indentation options
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = false
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+#Formatting - new line options
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_open_brace = all
+#csharp_new_line_before_members_in_anonymous_types = true
+#csharp_new_line_before_members_in_object_initializers = true # Currently no effect in VS/dotnet format (16.4), and makes Rider confusing
+csharp_new_line_between_query_expression_clauses = true
+
+#Formatting - organize using options
+dotnet_sort_system_directives_first = true
+
+#Formatting - spacing options
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+
+#Formatting - wrapping options
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#Roslyn language styles
+
+#Style - this. qualification
+dotnet_style_qualification_for_field = false:warning
+dotnet_style_qualification_for_property = false:warning
+dotnet_style_qualification_for_method = false:warning
+dotnet_style_qualification_for_event = false:warning
+
+#Style - type names
+dotnet_style_predefined_type_for_locals_parameters_members = true:warning
+dotnet_style_predefined_type_for_member_access = true:warning
+csharp_style_var_when_type_is_apparent = true:none
+csharp_style_var_for_built_in_types = true:none
+csharp_style_var_elsewhere = true:silent
+
+#Style - modifiers
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning
+csharp_preferred_modifier_order = public,private,protected,internal,new,abstract,virtual,sealed,override,static,readonly,extern,unsafe,volatile,async:warning
+
+#Style - parentheses
+# Skipped because roslyn cannot separate +-*/ with << >>
+
+#Style - expression bodies
+csharp_style_expression_bodied_accessors = true:warning
+csharp_style_expression_bodied_constructors = false:none
+csharp_style_expression_bodied_indexers = true:warning
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = true:warning
+csharp_style_expression_bodied_properties = true:warning
+csharp_style_expression_bodied_local_functions = true:silent
+
+#Style - expression preferences
+dotnet_style_object_initializer = true:warning
+dotnet_style_collection_initializer = true:warning
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
+dotnet_style_prefer_auto_properties = true:warning
+dotnet_style_prefer_conditional_expression_over_assignment = true:silent
+dotnet_style_prefer_conditional_expression_over_return = true:silent
+dotnet_style_prefer_compound_assignment = true:warning
+
+#Style - null/type checks
+dotnet_style_coalesce_expression = true:warning
+dotnet_style_null_propagation = true:warning
+csharp_style_pattern_matching_over_is_with_cast_check = true:warning
+csharp_style_pattern_matching_over_as_with_null_check = true:warning
+csharp_style_throw_expression = true:silent
+csharp_style_conditional_delegate_call = true:warning
+
+#Style - unused
+dotnet_style_readonly_field = true:silent
+dotnet_code_quality_unused_parameters = non_public:silent
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+csharp_style_unused_value_assignment_preference = discard_variable:warning
+
+#Style - variable declaration
+csharp_style_inlined_variable_declaration = true:warning
+csharp_style_deconstructed_variable_declaration = true:warning
+
+#Style - other C# 7.x features
+dotnet_style_prefer_inferred_tuple_names = true:warning
+csharp_prefer_simple_default_expression = true:warning
+csharp_style_pattern_local_over_anonymous_function = true:warning
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
+
+#Style - C# 8 features
+csharp_prefer_static_local_function = true:warning
+csharp_prefer_simple_using_statement = true:silent
+csharp_style_prefer_index_operator = true:warning
+csharp_style_prefer_range_operator = true:warning
+csharp_style_prefer_switch_expression = false:none
+
+#Supressing roslyn built-in analyzers
+# Suppress: EC112
+
+#Private method is unused
+dotnet_diagnostic.IDE0051.severity = silent
+#Private member is unused
+dotnet_diagnostic.IDE0052.severity = silent
+
+#Rules for disposable
+dotnet_diagnostic.IDE0067.severity = none
+dotnet_diagnostic.IDE0068.severity = none
+dotnet_diagnostic.IDE0069.severity = none
+
+#Disable operator overloads requiring alternate named methods
+dotnet_diagnostic.CA2225.severity = none
+
+# Banned APIs
+dotnet_diagnostic.RS0030.severity = error
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-example/.gitignore b/Templates/Rulesets/ruleset-scrolling-example/.gitignore
new file mode 100644
index 0000000000..940794e60f
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/.gitignore
@@ -0,0 +1,288 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+**/Properties/launchSettings.json
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Typescript v1 declaration files
+typings/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
diff --git a/Templates/Rulesets/ruleset-scrolling-example/.template.config/template.json b/Templates/Rulesets/ruleset-scrolling-example/.template.config/template.json
new file mode 100644
index 0000000000..a1c097f1c8
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/.template.config/template.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "http://json.schemastore.org/template",
+ "author": "ppy Pty Ltd",
+ "classifications": [
+ "Console"
+ ],
+ "name": "osu! ruleset (scrolling pippidon example)",
+ "identity": "ppy.osu.Game.Templates.Rulesets.Scrolling.Pippidon",
+ "shortName": "ruleset-scrolling-example",
+ "tags": {
+ "language": "C#",
+ "type": "project"
+ },
+ "sourceName": "Pippidon",
+ "preferNameDirectory": true
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/launch.json
new file mode 100644
index 0000000000..bd9db14259
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.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/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Debug)",
+ "env": {},
+ "console": "internalConsole"
+ },
+ {
+ "name": "VisualTests (Release)",
+ "type": "coreclr",
+ "request": "launch",
+ "program": "dotnet",
+ "args": [
+ "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Pippidon.Tests.dll"
+ ],
+ "cwd": "${workspaceRoot}",
+ "preLaunchTask": "Build (Release)",
+ "env": {},
+ "console": "internalConsole"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/.vscode/tasks.json
new file mode 100644
index 0000000000..0ee07c1036
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.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.Rulesets.Pippidon.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.Rulesets.Pippidon.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/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.cs
new file mode 100644
index 0000000000..270d906b01
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuGame.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.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Platform;
+using osu.Game.Tests.Visual;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Pippidon.Tests
+{
+ public class TestSceneOsuGame : OsuTestScene
+ {
+ [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
+ };
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.cs
new file mode 100644
index 0000000000..f00528900c
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/TestSceneOsuPlayer.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 NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Pippidon.Tests
+{
+ [TestFixture]
+ public class TestSceneOsuPlayer : PlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new PippidonRuleset();
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.cs
new file mode 100644
index 0000000000..fd6bd9b714
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/VisualTestRunner.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;
+using osu.Framework;
+using osu.Framework.Platform;
+using osu.Game.Tests;
+
+namespace osu.Game.Rulesets.Pippidon.Tests
+{
+ public static class VisualTestRunner
+ {
+ [STAThread]
+ public static int Main(string[] args)
+ {
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
+ {
+ host.Run(new OsuTestBrowser());
+ return 0;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
new file mode 100644
index 0000000000..afa7b03536
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj
@@ -0,0 +1,26 @@
+
+
+ osu.Game.Rulesets.Pippidon.Tests.VisualTestRunner
+
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+ WinExe
+ net5.0
+ osu.Game.Rulesets.Pippidon.Tests
+
+
\ No newline at end of file
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln
new file mode 100644
index 0000000000..bccffcd7ff
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln
@@ -0,0 +1,96 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29123.88
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Rulesets.Pippidon", "osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj", "{5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{B4577C85-CB83-462A-BCE3-22FFEB16311D}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ VisualTests|Any CPU = VisualTests|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.ActiveCfg = Release|Any CPU
+ {5AE1F0F1-DAFA-46E7-959C-DA233B7C87E9}.VisualTests|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.ActiveCfg = Debug|Any CPU
+ {B4577C85-CB83-462A-BCE3-22FFEB16311D}.VisualTests|Any CPU.Build.0 = Debug|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {671B0BEC-2403-45B0-9357-2C97CC517668}
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ Policies = $0
+ $0.TextStylePolicy = $1
+ $1.EolMarker = Windows
+ $1.inheritsSet = VisualStudio
+ $1.inheritsScope = text/plain
+ $1.scope = text/x-csharp
+ $0.CSharpFormattingPolicy = $2
+ $2.IndentSwitchSection = True
+ $2.NewLinesForBracesInProperties = True
+ $2.NewLinesForBracesInAccessors = True
+ $2.NewLinesForBracesInAnonymousMethods = True
+ $2.NewLinesForBracesInControlBlocks = True
+ $2.NewLinesForBracesInAnonymousTypes = True
+ $2.NewLinesForBracesInObjectCollectionArrayInitializers = True
+ $2.NewLinesForBracesInLambdaExpressionBody = True
+ $2.NewLineForElse = True
+ $2.NewLineForCatch = True
+ $2.NewLineForFinally = True
+ $2.NewLineForMembersInObjectInit = True
+ $2.NewLineForMembersInAnonymousTypes = True
+ $2.NewLineForClausesInQuery = True
+ $2.SpacingAfterMethodDeclarationName = False
+ $2.SpaceAfterMethodCallName = False
+ $2.SpaceBeforeOpenSquareBracket = False
+ $2.inheritsSet = Mono
+ $2.inheritsScope = text/x-csharp
+ $2.scope = text/x-csharp
+ EndGlobalSection
+EndGlobal
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
new file mode 100644
index 0000000000..aa8f8739c1
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.sln.DotSettings
@@ -0,0 +1,934 @@
+
+ True
+ True
+ True
+ True
+ ExplicitlyExcluded
+ ExplicitlyExcluded
+ SOLUTION
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ HINT
+ WARNING
+ WARNING
+ True
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ 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
+ HINT
+ WARNING
+ HINT
+ SUGGESTION
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ ERROR
+ WARNING
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ HINT
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ HINT
+ HINT
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ 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
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ WARNING
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ HINT
+ WARNING
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ HINT
+ DO_NOT_SHOW
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+ WARNING
+
+ True
+ WARNING
+ WARNING
+ 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)
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ RequiredForMultiline
+ Explicit
+ ExpressionBody
+ BlockBody
+ True
+ NEXT_LINE
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ NEXT_LINE
+ MULTILINE
+ True
+ True
+ True
+ True
+ NEXT_LINE
+ 1
+ 1
+ True
+ NEXT_LINE
+ NEVER
+ NEVER
+ True
+ False
+ True
+ NEVER
+ False
+ False
+ True
+ False
+ False
+ True
+ True
+ False
+ False
+ CHOP_IF_LONG
+ True
+ 200
+ CHOP_IF_LONG
+ False
+ False
+ AABB
+ API
+ BPM
+ GC
+ GL
+ GLSL
+ HID
+ HTML
+ HUD
+ ID
+ IL
+ IOS
+ IP
+ IPC
+ JIT
+ LTRB
+ MD5
+ NS
+ OS
+ PM
+ 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
+ TestFolder
+ 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
+ True
+ True
+ True
+ True
+ True
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs
new file mode 100644
index 0000000000..8f0b31ef1b
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.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.Linq;
+using System.Threading;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Rulesets.Pippidon.Objects;
+using osu.Game.Rulesets.Pippidon.UI;
+using osuTK;
+
+namespace osu.Game.Rulesets.Pippidon.Beatmaps
+{
+ public class PippidonBeatmapConverter : BeatmapConverter
+ {
+ private readonly float minPosition;
+ private readonly float maxPosition;
+
+ public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
+ : base(beatmap, ruleset)
+ {
+ minPosition = beatmap.HitObjects.Min(getUsablePosition);
+ maxPosition = beatmap.HitObjects.Max(getUsablePosition);
+ }
+
+ public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition && h is IHasYPosition);
+
+ protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
+ {
+ yield return new PippidonHitObject
+ {
+ Samples = original.Samples,
+ StartTime = original.StartTime,
+ Lane = getLane(original)
+ };
+ }
+
+ private int getLane(HitObject hitObject) => (int)MathHelper.Clamp(
+ (getUsablePosition(hitObject) - minPosition) / (maxPosition - minPosition) * PippidonPlayfield.LANE_COUNT, 0, PippidonPlayfield.LANE_COUNT - 1);
+
+ private float getUsablePosition(HitObject h) => (h as IHasYPosition)?.Y ?? ((IHasXPosition)h).X;
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.cs
new file mode 100644
index 0000000000..8ea334c99c
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Mods/PippidonModAutoplay.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.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Pippidon.Objects;
+using osu.Game.Rulesets.Pippidon.Replays;
+using osu.Game.Scoring;
+using osu.Game.Users;
+
+namespace osu.Game.Rulesets.Pippidon.Mods
+{
+ public class PippidonModAutoplay : ModAutoplay
+ {
+ public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score
+ {
+ ScoreInfo = new ScoreInfo
+ {
+ User = new User { Username = "sample" },
+ },
+ Replay = new PippidonAutoGenerator(beatmap).Generate(),
+ };
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs
new file mode 100644
index 0000000000..e458cacef9
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/Drawables/DrawablePippidonHitObject.cs
@@ -0,0 +1,75 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Audio;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.Pippidon.UI;
+using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
+{
+ public class DrawablePippidonHitObject : DrawableHitObject
+ {
+ private BindableNumber currentLane;
+
+ public DrawablePippidonHitObject(PippidonHitObject hitObject)
+ : base(hitObject)
+ {
+ Size = new Vector2(40);
+
+ Origin = Anchor.Centre;
+ Y = hitObject.Lane * PippidonPlayfield.LANE_HEIGHT;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(PippidonPlayfield playfield, TextureStore textures)
+ {
+ AddInternal(new Sprite
+ {
+ RelativeSizeAxes = Axes.Both,
+ Texture = textures.Get("coin"),
+ });
+
+ currentLane = playfield.CurrentLane.GetBoundCopy();
+ }
+
+ public override IEnumerable GetSamples() => new[]
+ {
+ new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK)
+ };
+
+ protected override void CheckForResult(bool userTriggered, double timeOffset)
+ {
+ if (timeOffset >= 0)
+ ApplyResult(r => r.Type = currentLane.Value == HitObject.Lane ? HitResult.Perfect : HitResult.Miss);
+ }
+
+ protected override void UpdateHitStateTransforms(ArmedState state)
+ {
+ switch (state)
+ {
+ case ArmedState.Hit:
+ this.ScaleTo(5, 1500, Easing.OutQuint).FadeOut(1500, Easing.OutQuint).Expire();
+ break;
+
+ case ArmedState.Miss:
+
+ const double duration = 1000;
+
+ this.ScaleTo(0.8f, duration, Easing.OutQuint);
+ this.MoveToOffset(new Vector2(0, 10), duration, Easing.In);
+ this.FadeColour(Color4.Red, duration / 2, Easing.OutQuint).Then().FadeOut(duration / 2, Easing.InQuint).Expire();
+ break;
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.cs
new file mode 100644
index 0000000000..9dd135479f
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Objects/PippidonHitObject.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.Judgements;
+using osu.Game.Rulesets.Objects;
+
+namespace osu.Game.Rulesets.Pippidon.Objects
+{
+ public class PippidonHitObject : HitObject
+ {
+ ///
+ /// Range = [-1,1]
+ ///
+ public int Lane;
+
+ public override Judgement CreateJudgement() => new Judgement();
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.cs
new file mode 100644
index 0000000000..f6340f6c25
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonDifficultyCalculator.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.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;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonDifficultyCalculator : DifficultyCalculator
+ {
+ public PippidonDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
+ : base(ruleset, beatmap)
+ {
+ }
+
+ protected override DifficultyAttributes CreateDifficultyAttributes(IBeatmap beatmap, Mod[] mods, Skill[] skills, double clockRate)
+ {
+ return new DifficultyAttributes(mods, skills, 0);
+ }
+
+ protected override IEnumerable CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate) => Enumerable.Empty();
+
+ protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods) => new Skill[0];
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.cs
new file mode 100644
index 0000000000..c9e6e6faaa
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonInputManager.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.ComponentModel;
+using osu.Framework.Input.Bindings;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonInputManager : RulesetInputManager
+ {
+ public PippidonInputManager(RulesetInfo ruleset)
+ : base(ruleset, 0, SimultaneousBindingMode.Unique)
+ {
+ }
+ }
+
+ public enum PippidonAction
+ {
+ [Description("Move up")]
+ MoveUp,
+
+ [Description("Move down")]
+ MoveDown,
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs
new file mode 100644
index 0000000000..ede00f1510
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.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.Collections.Generic;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input.Bindings;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Difficulty;
+using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Pippidon.Beatmaps;
+using osu.Game.Rulesets.Pippidon.Mods;
+using osu.Game.Rulesets.Pippidon.UI;
+using osu.Game.Rulesets.UI;
+
+namespace osu.Game.Rulesets.Pippidon
+{
+ public class PippidonRuleset : Ruleset
+ {
+ public override string Description => "gather the osu!coins";
+
+ public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawablePippidonRuleset(this, beatmap, mods);
+
+ public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new PippidonBeatmapConverter(beatmap, this);
+
+ public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new PippidonDifficultyCalculator(this, beatmap);
+
+ public override IEnumerable GetModsFor(ModType type)
+ {
+ switch (type)
+ {
+ case ModType.Automation:
+ return new[] { new PippidonModAutoplay() };
+
+ default:
+ return new Mod[] { null };
+ }
+ }
+
+ public override string ShortName => "pippidon";
+
+ public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[]
+ {
+ new KeyBinding(InputKey.W, PippidonAction.MoveUp),
+ new KeyBinding(InputKey.S, PippidonAction.MoveDown),
+ };
+
+ public override Drawable CreateIcon() => new Sprite
+ {
+ Margin = new MarginPadding { Top = 3 },
+ Texture = new TextureStore(new TextureLoaderStore(CreateResourceStore()), false).Get("Textures/coin"),
+ };
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.cs
new file mode 100644
index 0000000000..bd99cdcdbd
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonAutoGenerator.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;
+using System.Collections.Generic;
+using osu.Game.Beatmaps;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Pippidon.Objects;
+using osu.Game.Rulesets.Pippidon.UI;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.Pippidon.Replays
+{
+ public class PippidonAutoGenerator : AutoGenerator
+ {
+ protected Replay Replay;
+ protected List Frames => Replay.Frames;
+
+ public new Beatmap Beatmap => (Beatmap)base.Beatmap;
+
+ public PippidonAutoGenerator(IBeatmap beatmap)
+ : base(beatmap)
+ {
+ Replay = new Replay();
+ }
+
+ public override Replay Generate()
+ {
+ int currentLane = 0;
+
+ Frames.Add(new PippidonReplayFrame());
+
+ foreach (PippidonHitObject hitObject in Beatmap.HitObjects)
+ {
+ if (currentLane == hitObject.Lane)
+ continue;
+
+ int totalTravel = Math.Abs(hitObject.Lane - currentLane);
+ var direction = hitObject.Lane > currentLane ? PippidonAction.MoveDown : PippidonAction.MoveUp;
+
+ double time = hitObject.StartTime - 5;
+
+ if (totalTravel == PippidonPlayfield.LANE_COUNT - 1)
+ addFrame(time, direction == PippidonAction.MoveDown ? PippidonAction.MoveUp : PippidonAction.MoveDown);
+ else
+ {
+ time -= totalTravel * KEY_UP_DELAY;
+
+ for (int i = 0; i < totalTravel; i++)
+ {
+ addFrame(time, direction);
+ time += KEY_UP_DELAY;
+ }
+ }
+
+ currentLane = hitObject.Lane;
+ }
+
+ return Replay;
+ }
+
+ private void addFrame(double time, PippidonAction direction)
+ {
+ Frames.Add(new PippidonReplayFrame(direction) { Time = time });
+ Frames.Add(new PippidonReplayFrame { Time = time + KEY_UP_DELAY }); //Release the keys as well
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.cs
new file mode 100644
index 0000000000..7652357b4d
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonFramedReplayInputHandler.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 System.Linq;
+using osu.Framework.Input.StateChanges;
+using osu.Game.Replays;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.Pippidon.Replays
+{
+ public class PippidonFramedReplayInputHandler : FramedReplayInputHandler
+ {
+ public PippidonFramedReplayInputHandler(Replay replay)
+ : base(replay)
+ {
+ }
+
+ protected override bool IsImportant(PippidonReplayFrame frame) => frame.Actions.Any();
+
+ public override void CollectPendingInputs(List inputs)
+ {
+ inputs.Add(new ReplayState
+ {
+ PressedActions = CurrentFrame?.Actions ?? new List(),
+ });
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.cs
new file mode 100644
index 0000000000..468ac9c725
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Replays/PippidonReplayFrame.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.Collections.Generic;
+using osu.Game.Rulesets.Replays;
+
+namespace osu.Game.Rulesets.Pippidon.Replays
+{
+ public class PippidonReplayFrame : ReplayFrame
+ {
+ public List Actions = new List();
+
+ public PippidonReplayFrame(PippidonAction? button = null)
+ {
+ if (button.HasValue)
+ Actions.Add(button.Value);
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3
new file mode 100644
index 0000000000..90b13d1f73
Binary files /dev/null and b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Samples/Gameplay/normal-hitnormal.mp3 differ
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png
new file mode 100644
index 0000000000..e79d2528ec
Binary files /dev/null and b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/character.png differ
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png
new file mode 100644
index 0000000000..3cd89c6ce6
Binary files /dev/null and b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Resources/Textures/coin.png differ
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.cs
new file mode 100644
index 0000000000..9a73dd7790
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/DrawablePippidonRuleset.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.Collections.Generic;
+using osu.Framework.Allocation;
+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.Pippidon.Objects;
+using osu.Game.Rulesets.Pippidon.Objects.Drawables;
+using osu.Game.Rulesets.Pippidon.Replays;
+using osu.Game.Rulesets.UI;
+using osu.Game.Rulesets.UI.Scrolling;
+
+namespace osu.Game.Rulesets.Pippidon.UI
+{
+ [Cached]
+ public class DrawablePippidonRuleset : DrawableScrollingRuleset
+ {
+ public DrawablePippidonRuleset(PippidonRuleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null)
+ : base(ruleset, beatmap, mods)
+ {
+ Direction.Value = ScrollingDirection.Left;
+ TimeRange.Value = 6000;
+ }
+
+ protected override Playfield CreatePlayfield() => new PippidonPlayfield();
+
+ protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new PippidonFramedReplayInputHandler(replay);
+
+ public override DrawableHitObject CreateDrawableRepresentation(PippidonHitObject h) => new DrawablePippidonHitObject(h);
+
+ protected override PassThroughInputManager CreateInputManager() => new PippidonInputManager(Ruleset?.RulesetInfo);
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.cs
new file mode 100644
index 0000000000..dd0a20f1b4
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonCharacter.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 osu.Framework.Allocation;
+using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Input.Bindings;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics.Containers;
+using osuTK;
+
+namespace osu.Game.Rulesets.Pippidon.UI
+{
+ public class PippidonCharacter : BeatSyncedContainer, IKeyBindingHandler
+ {
+ public readonly BindableInt LanePosition = new BindableInt
+ {
+ Value = 0,
+ MinValue = 0,
+ MaxValue = PippidonPlayfield.LANE_COUNT - 1,
+ };
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ Size = new Vector2(PippidonPlayfield.LANE_HEIGHT);
+
+ Child = new Sprite
+ {
+ FillMode = FillMode.Fit,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(1.2f),
+ RelativeSizeAxes = Axes.Both,
+ Texture = textures.Get("character")
+ };
+
+ LanePosition.BindValueChanged(e => { this.MoveToY(e.NewValue * PippidonPlayfield.LANE_HEIGHT); });
+ }
+
+ protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
+ {
+ if (effectPoint.KiaiMode)
+ {
+ bool direction = beatIndex % 2 == 1;
+ double duration = timingPoint.BeatLength / 2;
+
+ Child.RotateTo(direction ? 10 : -10, duration * 2, Easing.InOutSine);
+
+ Child.Animate(i => i.MoveToY(-10, duration, Easing.Out))
+ .Then(i => i.MoveToY(0, duration, Easing.In));
+ }
+ else
+ {
+ Child.ClearTransforms();
+ Child.RotateTo(0, 500, Easing.Out);
+ Child.MoveTo(Vector2.Zero, 500, Easing.Out);
+ }
+ }
+
+ public bool OnPressed(PippidonAction action)
+ {
+ switch (action)
+ {
+ case PippidonAction.MoveUp:
+ changeLane(-1);
+ return true;
+
+ case PippidonAction.MoveDown:
+ changeLane(1);
+ return true;
+
+ default:
+ return false;
+ }
+ }
+
+ public void OnReleased(PippidonAction action)
+ {
+ }
+
+ private void changeLane(int change) => LanePosition.Value = (LanePosition.Value + change + PippidonPlayfield.LANE_COUNT) % PippidonPlayfield.LANE_COUNT;
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
new file mode 100644
index 0000000000..0e50030162
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/UI/PippidonPlayfield.cs
@@ -0,0 +1,128 @@
+// Copyright (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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Textures;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Rulesets.UI.Scrolling;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Pippidon.UI
+{
+ [Cached]
+ public class PippidonPlayfield : ScrollingPlayfield
+ {
+ public const float LANE_HEIGHT = 70;
+
+ public const int LANE_COUNT = 6;
+
+ public BindableInt CurrentLane => pippidon.LanePosition;
+
+ private PippidonCharacter pippidon;
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures)
+ {
+ AddRangeInternal(new Drawable[]
+ {
+ new LaneContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Child = new Container
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Padding = new MarginPadding
+ {
+ Left = 200,
+ Top = LANE_HEIGHT / 2,
+ Bottom = LANE_HEIGHT / 2
+ },
+ Children = new Drawable[]
+ {
+ HitObjectContainer,
+ pippidon = new PippidonCharacter
+ {
+ Origin = Anchor.Centre,
+ },
+ }
+ },
+ },
+ });
+ }
+
+ private class LaneContainer : BeatSyncedContainer
+ {
+ private OsuColour colours;
+ private FillFlowContainer fill;
+
+ private readonly Container content = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ };
+
+ protected override Container Content => content;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ this.colours = colours;
+
+ InternalChildren = new Drawable[]
+ {
+ fill = new FillFlowContainer
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Colour = colours.BlueLight,
+ Direction = FillDirection.Vertical,
+ },
+ content,
+ };
+
+ for (int i = 0; i < LANE_COUNT; i++)
+ {
+ fill.Add(new Lane
+ {
+ RelativeSizeAxes = Axes.X,
+ Height = LANE_HEIGHT,
+ });
+ }
+ }
+
+ private class Lane : CompositeDrawable
+ {
+ public Lane()
+ {
+ InternalChildren = new Drawable[]
+ {
+ new Box
+ {
+ Colour = Color4.White,
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ Height = 0.95f,
+ },
+ };
+ }
+ }
+
+ protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
+ {
+ if (effectPoint.KiaiMode)
+ fill.FlashColour(colours.PinkLight, 800, Easing.In);
+ }
+ }
+ }
+}
diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
new file mode 100644
index 0000000000..61b859f45b
--- /dev/null
+++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj
@@ -0,0 +1,15 @@
+
+
+ netstandard2.1
+ osu.Game.Rulesets.Sample
+ Library
+ AnyCPU
+ osu.Game.Rulesets.Pippidon
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Templates/osu.Game.Templates.csproj b/Templates/osu.Game.Templates.csproj
new file mode 100644
index 0000000000..31a24a301f
--- /dev/null
+++ b/Templates/osu.Game.Templates.csproj
@@ -0,0 +1,24 @@
+
+
+ Template
+ ppy.osu.Game.Templates
+ osu! templates
+ ppy Pty Ltd
+ https://github.com/ppy/osu/blob/master/LICENCE
+ https://github.com/ppy/osu/blob/master/Templates
+ https://github.com/ppy/osu
+ Automated release.
+ Copyright (c) 2021 ppy Pty Ltd
+ Templates to use when creating a ruleset for consumption in osu!.
+ dotnet-new;templates;osu
+ netstandard2.1
+ true
+ false
+ content
+
+
+
+
+
+
+
diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml
index bb4482f501..adf98848bc 100644
--- a/appveyor_deploy.yml
+++ b/appveyor_deploy.yml
@@ -1,21 +1,86 @@
clone_depth: 1
version: '{build}'
image: Visual Studio 2019
-dotnet_csproj:
- patch: true
- file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
- version: $(APPVEYOR_REPO_TAG_NAME)
-before_build:
- - ps: dotnet --info # Useful when version mismatch between CI and local
- - ps: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
test: off
skip_non_tags: true
configuration: Release
-build:
- project: build\Desktop.proj # Skipping Xamarin Release that's slow and covered by fastlane
- parallel: true
- verbosity: minimal
- publish_nuget: true
+
+environment:
+ matrix:
+ - job_name: osu-game
+ - job_name: osu-ruleset
+ job_depends_on: osu-game
+ - job_name: taiko-ruleset
+ job_depends_on: osu-game
+ - job_name: catch-ruleset
+ job_depends_on: osu-game
+ - job_name: mania-ruleset
+ job_depends_on: osu-game
+ - job_name: templates
+ job_depends_on: osu-game
+
+nuget:
+ project_feed: true
+
+for:
+ -
+ matrix:
+ only:
+ - job_name: osu-game
+ build_script:
+ - cmd: dotnet pack osu.Game\osu.Game.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
+ -
+ matrix:
+ only:
+ - job_name: osu-ruleset
+ build_script:
+ - cmd: dotnet remove osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj reference osu.Game\osu.Game.csproj
+ - cmd: dotnet add osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+ - cmd: dotnet pack osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
+ -
+ matrix:
+ only:
+ - job_name: taiko-ruleset
+ build_script:
+ - cmd: dotnet remove osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj reference osu.Game\osu.Game.csproj
+ - cmd: dotnet add osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+ - cmd: dotnet pack osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
+ -
+ matrix:
+ only:
+ - job_name: catch-ruleset
+ build_script:
+ - cmd: dotnet remove osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj reference osu.Game\osu.Game.csproj
+ - cmd: dotnet add osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+ - cmd: dotnet pack osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
+ -
+ matrix:
+ only:
+ - job_name: mania-ruleset
+ build_script:
+ - cmd: dotnet remove osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj reference osu.Game\osu.Game.csproj
+ - cmd: dotnet add osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+ - cmd: dotnet pack osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
+ -
+ matrix:
+ only:
+ - job_name: templates
+ build_script:
+ - cmd: dotnet remove Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj reference osu.Game\osu.Game.csproj
+ - cmd: dotnet remove Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj reference osu.Game\osu.Game.csproj
+ - cmd: dotnet remove Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj reference osu.Game\osu.Game.csproj
+ - cmd: dotnet remove Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj reference osu.Game\osu.Game.csproj
+
+ - cmd: dotnet add Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+ - cmd: dotnet add Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+ - cmd: dotnet add Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+ - cmd: dotnet add Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
+
+ - cmd: dotnet pack Templates\osu.Game.Templates.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
+
+artifacts:
+ - path: '**\*.nupkg'
+
deploy:
- provider: Environment
- name: nuget
+ name: nuget
\ No newline at end of file
diff --git a/build/InspectCode.cake b/build/InspectCode.cake
index c8f4f37c94..6836d9071b 100644
--- a/build/InspectCode.cake
+++ b/build/InspectCode.cake
@@ -1,7 +1,4 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.36"
-#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3"
-#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
-var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
///////////////////////////////////////////////////////////////////////////////
// ARGUMENTS
@@ -18,23 +15,15 @@ var desktopSlnf = rootDirectory.CombineWithFilePath("osu.Desktop.slnf");
// TASKS
///////////////////////////////////////////////////////////////////////////////
-// windows only because both inspectcode and nvika depend on net45
Task("InspectCode")
- .WithCriteria(IsRunningOnWindows())
.Does(() => {
- InspectCode(desktopSlnf, new InspectCodeSettings {
- CachesHome = "inspectcode",
- OutputFile = "inspectcodereport.xml",
- ArgumentCustomization = arg => {
- if (AppVeyor.IsRunningOnAppVeyor) // Don't flood CI output
- arg.Append("--verbosity:WARN");
- return arg;
- },
- });
+ var inspectcodereport = "inspectcodereport.xml";
+ var cacheDir = "inspectcode";
+ var verbosity = AppVeyor.IsRunningOnAppVeyor ? "WARN" : "INFO"; // Don't flood CI output
- int returnCode = StartProcess(nVikaToolPath, $@"parsereport ""inspectcodereport.xml"" --treatwarningsaserrors");
- if (returnCode != 0)
- throw new Exception($"inspectcode failed with return code {returnCode}");
+ DotNetCoreTool(rootDirectory.FullPath,
+ "jb", $@"inspectcode ""{desktopSlnf}"" --output=""{inspectcodereport}"" --caches-home=""{cacheDir}"" --verbosity={verbosity}");
+ DotNetCoreTool(rootDirectory.FullPath, "nvika", $@"parsereport ""{inspectcodereport}"" --treatwarningsaserrors");
});
Task("CodeFileSanity")
diff --git a/fastlane/Fastfile b/fastlane/Fastfile
index 4fd0e5e8c7..cc5abf5b03 100644
--- a/fastlane/Fastfile
+++ b/fastlane/Fastfile
@@ -48,9 +48,12 @@ desc 'Deploy to play store'
desc 'Compile the project'
lane :build do |options|
- nuget_restore(
- project_path: 'osu.sln'
- )
+ nuget_restore(project_path: 'osu.Android/osu.Android.csproj')
+ nuget_restore(project_path: 'osu.Game/osu.Game.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj')
souyuz(
build_configuration: 'Release',
@@ -107,13 +110,16 @@ platform :ios do
desc 'Compile the project'
lane :build do
- nuget_restore(
- project_path: 'osu.sln'
- )
+ nuget_restore(project_path: 'osu.iOS/osu.iOS.csproj')
+ nuget_restore(project_path: 'osu.Game/osu.Game.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Catch/osu.Game.Rulesets.Catch.csproj')
+ nuget_restore(project_path: 'osu.Game.Rulesets.Mania/osu.Game.Rulesets.Mania.csproj')
souyuz(
platform: "ios",
- plist_path: "../osu.iOS/Info.plist"
+ plist_path: "osu.iOS/Info.plist"
)
end
@@ -127,7 +133,7 @@ platform :ios do
end
lane :update_version do |options|
- options[:plist_path] = '../osu.iOS/Info.plist'
+ options[:plist_path] = 'osu.iOS/Info.plist'
app_version(options)
end
diff --git a/global.json b/global.json
deleted file mode 100644
index bdb90eb0e9..0000000000
--- a/global.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "sdk": {
- "allowPrerelease": false,
- "rollForward": "minor",
- "version": "3.1.100"
- },
- "msbuild-sdks": {
- "Microsoft.Build.Traversal": "2.0.48"
- }
-}
\ No newline at end of file
diff --git a/osu.Android.props b/osu.Android.props
index 596e5bfa8b..73ee1d9d10 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,7 +51,7 @@
-
-
+
+
diff --git a/osu.Android/GameplayScreenRotationLocker.cs b/osu.Android/GameplayScreenRotationLocker.cs
new file mode 100644
index 0000000000..25bd659a5d
--- /dev/null
+++ b/osu.Android/GameplayScreenRotationLocker.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 Android.Content.PM;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game;
+
+namespace osu.Android
+{
+ public class GameplayScreenRotationLocker : Component
+ {
+ private Bindable localUserPlaying;
+
+ [Resolved]
+ private OsuGameActivity gameActivity { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGame game)
+ {
+ localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
+ localUserPlaying.BindValueChanged(updateLock, true);
+ }
+
+ private void updateLock(ValueChangedEvent userPlaying)
+ {
+ gameActivity.RunOnUiThread(() =>
+ {
+ gameActivity.RequestedOrientation = userPlaying.NewValue ? ScreenOrientation.Locked : ScreenOrientation.FullUser;
+ });
+ }
+ }
+}
diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs
index 2e5fa59d20..cffcea22c2 100644
--- a/osu.Android/OsuGameActivity.cs
+++ b/osu.Android/OsuGameActivity.cs
@@ -1,18 +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;
+using System.Linq;
+using System.Threading.Tasks;
using Android.App;
+using Android.Content;
using Android.Content.PM;
+using Android.Net;
using Android.OS;
+using Android.Provider;
using Android.Views;
using osu.Framework.Android;
+using osu.Game.Database;
namespace osu.Android
{
- [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)]
+ [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
+ [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
+ [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
+ [IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed" })]
+ [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
public class OsuGameActivity : AndroidGameActivity
{
- protected override Framework.Game CreateGame() => new OsuGameAndroid();
+ private static readonly string[] osu_url_schemes = { "osu", "osump" };
+
+ private OsuGameAndroid game;
+
+ protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
protected override void OnCreate(Bundle savedInstanceState)
{
@@ -23,8 +39,76 @@ namespace osu.Android
base.OnCreate(savedInstanceState);
+ // OnNewIntent() only fires for an activity if it's *re-launched* while it's on top of the activity stack.
+ // on first launch we still have to fire manually.
+ // reference: https://developer.android.com/reference/android/app/Activity#onNewIntent(android.content.Intent)
+ handleIntent(Intent);
+
Window.AddFlags(WindowManagerFlags.Fullscreen);
Window.AddFlags(WindowManagerFlags.KeepScreenOn);
}
+
+ protected override void OnNewIntent(Intent intent) => handleIntent(intent);
+
+ private void handleIntent(Intent intent)
+ {
+ switch (intent.Action)
+ {
+ case Intent.ActionDefault:
+ if (intent.Scheme == ContentResolver.SchemeContent)
+ handleImportFromUris(intent.Data);
+ else if (osu_url_schemes.Contains(intent.Scheme))
+ game.HandleLink(intent.DataString);
+ break;
+
+ case Intent.ActionSend:
+ case Intent.ActionSendMultiple:
+ {
+ var uris = new List();
+ for (int i = 0; i < intent.ClipData?.ItemCount; i++)
+ {
+ var content = intent.ClipData?.GetItemAt(i);
+ if (content != null)
+ uris.Add(content.Uri);
+ }
+ handleImportFromUris(uris.ToArray());
+ break;
+ }
+ }
+ }
+
+ private void handleImportFromUris(params Uri[] uris) => Task.Factory.StartNew(async () =>
+ {
+ var tasks = new List();
+
+ await Task.WhenAll(uris.Select(async uri =>
+ {
+ // there are more performant overloads of this method, but this one is the most backwards-compatible
+ // (dates back to API 1).
+ var cursor = ContentResolver?.Query(uri, null, null, null, null);
+
+ if (cursor == null)
+ return;
+
+ cursor.MoveToFirst();
+
+ var filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName);
+ string filename = cursor.GetString(filenameColumn);
+
+ // SharpCompress requires archive streams to be seekable, which the stream opened by
+ // OpenInputStream() seems to not necessarily be.
+ // copy to an arbitrary-access memory stream to be able to proceed with the import.
+ var copy = new MemoryStream();
+ using (var stream = ContentResolver.OpenInputStream(uri))
+ await stream.CopyToAsync(copy).ConfigureAwait(false);
+
+ lock (tasks)
+ {
+ tasks.Add(new ImportTask(copy, filename));
+ }
+ })).ConfigureAwait(false);
+
+ await game.Import(tasks.ToArray()).ConfigureAwait(false);
+ }, TaskCreationOptions.LongRunning);
}
}
diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs
index 19ed7ffcf5..21d6336b2c 100644
--- a/osu.Android/OsuGameAndroid.cs
+++ b/osu.Android/OsuGameAndroid.cs
@@ -3,6 +3,8 @@
using System;
using Android.App;
+using Android.OS;
+using osu.Framework.Allocation;
using osu.Game;
using osu.Game.Updater;
@@ -10,6 +12,15 @@ namespace osu.Android
{
public class OsuGameAndroid : OsuGame
{
+ [Cached]
+ private readonly OsuGameActivity gameActivity;
+
+ public OsuGameAndroid(OsuGameActivity activity)
+ : base(null)
+ {
+ gameActivity = activity;
+ }
+
public override Version AssemblyVersion
{
get
@@ -18,9 +29,32 @@ namespace osu.Android
try
{
- // todo: needs checking before play store redeploy.
- string versionName = packageInfo.VersionName;
- // undo play store version garbling
+ // We store the osu! build number in the "VersionCode" field to better support google play releases.
+ // If we were to use the main build number, it would require a new submission each time (similar to TestFlight).
+ // In order to do this, we should split it up and pad the numbers to still ensure sequential increase over time.
+ //
+ // We also need to be aware that older SDK versions store this as a 32bit int.
+ //
+ // Basic conversion format (as done in Fastfile): 2020.606.0 -> 202006060
+
+ // https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated
+ string versionName = string.Empty;
+
+ if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
+ {
+ versionName = packageInfo.LongVersionCode.ToString();
+ // ensure we only read the trailing portion of long (the part we are interested in).
+ versionName = versionName.Substring(versionName.Length - 9);
+ }
+ else
+ {
+#pragma warning disable CS0618 // Type or member is obsolete
+ // this is required else older SDKs will report missing method exception.
+ versionName = packageInfo.VersionCode.ToString();
+#pragma warning restore CS0618 // Type or member is obsolete
+ }
+
+ // undo play store version garbling (as mentioned above).
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
}
catch
@@ -31,6 +65,12 @@ namespace osu.Android
}
}
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ LoadComponentAsync(new GameplayScreenRotationLocker(), Add);
+ }
+
protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager();
}
-}
\ No newline at end of file
+}
diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj
index 0598a50530..54857ac87d 100644
--- a/osu.Android/osu.Android.csproj
+++ b/osu.Android/osu.Android.csproj
@@ -20,7 +20,13 @@
d8
r8
+
+ None
+ cjk;mideast;other;rare;west
+ true
+
+
@@ -52,5 +58,10 @@
+
+
+ 5.0.0
+
+
-
+
\ No newline at end of file
diff --git a/osu.Desktop.slnf b/osu.Desktop.slnf
index d2c14d321a..503e5935f5 100644
--- a/osu.Desktop.slnf
+++ b/osu.Desktop.slnf
@@ -15,7 +15,16 @@
"osu.Game.Tests\\osu.Game.Tests.csproj",
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
- "osu.Game\\osu.Game.csproj"
+ "osu.Game\\osu.Game.csproj",
+
+ "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
+ "Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
+ "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
+ "Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
+ "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
+ "Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
+ "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
+ "Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj"
]
}
-}
\ No newline at end of file
+}
diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs
index 08cc0e7f5f..832d26b0ef 100644
--- a/osu.Desktop/DiscordRichPresence.cs
+++ b/osu.Desktop/DiscordRichPresence.cs
@@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Logging;
+using osu.Game.Configuration;
using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Users;
@@ -26,18 +27,20 @@ namespace osu.Desktop
[Resolved]
private IBindable ruleset { get; set; }
- private Bindable user;
+ private IBindable user;
private readonly IBindable status = new Bindable();
private readonly IBindable activity = new Bindable();
+ private readonly Bindable privacyMode = new Bindable();
+
private readonly RichPresence presence = new RichPresence
{
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
};
[BackgroundDependencyLoader]
- private void load(IAPIProvider provider)
+ private void load(IAPIProvider provider, OsuConfigManager config)
{
client = new DiscordRpcClient(client_id)
{
@@ -51,6 +54,8 @@ namespace osu.Desktop
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
+ config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
+
(user = provider.LocalUser.GetBoundCopy()).BindValueChanged(u =>
{
status.UnbindBindings();
@@ -63,6 +68,7 @@ namespace osu.Desktop
ruleset.BindValueChanged(_ => updateStatus());
status.BindValueChanged(_ => updateStatus());
activity.BindValueChanged(_ => updateStatus());
+ privacyMode.BindValueChanged(_ => updateStatus());
client.Initialize();
}
@@ -78,7 +84,7 @@ namespace osu.Desktop
if (!client.IsInitialized)
return;
- if (status.Value is UserStatusOffline)
+ if (status.Value is UserStatusOffline || privacyMode.Value == DiscordRichPresenceMode.Off)
{
client.ClearPresence();
return;
@@ -96,7 +102,10 @@ namespace osu.Desktop
}
// update user information
- presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.Ranks.Global > 0 ? $" (rank #{user.Value.Statistics.Ranks.Global:N0})" : string.Empty);
+ if (privacyMode.Value == DiscordRichPresenceMode.Limited)
+ presence.Assets.LargeImageText = string.Empty;
+ else
+ presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
// update ruleset
presence.Assets.SmallImageKey = ruleset.Value.ID <= 3 ? $"mode_{ruleset.Value.ID}" : "mode_custom";
@@ -135,6 +144,9 @@ namespace osu.Desktop
case UserActivity.Editing edit:
return edit.Beatmap.ToString();
+
+ case UserActivity.InLobby lobby:
+ return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
}
return string.Empty;
diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs
index cd31df316a..0c21c75290 100644
--- a/osu.Desktop/OsuGameDesktop.cs
+++ b/osu.Desktop/OsuGameDesktop.cs
@@ -2,9 +2,11 @@
// 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.Runtime.Versioning;
using System.Threading.Tasks;
using Microsoft.Win32;
using osu.Desktop.Overlays;
@@ -16,6 +18,9 @@ using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Updater;
+using osu.Desktop.Windows;
+using osu.Framework.Threading;
+using osu.Game.IO;
namespace osu.Desktop
{
@@ -30,7 +35,7 @@ namespace osu.Desktop
noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false;
}
- public override Storage GetStorageForStableInstall()
+ public override StableStorage GetStorageForStableInstall()
{
try
{
@@ -38,7 +43,7 @@ namespace osu.Desktop
{
string stablePath = getStableInstallPath();
if (!string.IsNullOrEmpty(stablePath))
- return new DesktopStorage(stablePath, desktopHost);
+ return new StableStorage(stablePath, desktopHost);
}
}
catch (Exception)
@@ -55,16 +60,16 @@ namespace osu.Desktop
string stableInstallPath;
- try
+ if (OperatingSystem.IsWindows())
{
- using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
- stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString()?.Split('"')[1].Replace("osu!.exe", "");
+ try
+ {
+ stableInstallPath = getStableInstallPathFromRegistry();
- if (checkExists(stableInstallPath))
- return stableInstallPath;
- }
- catch
- {
+ if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath))
+ return stableInstallPath;
+ }
+ catch { }
}
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
@@ -78,6 +83,13 @@ namespace osu.Desktop
return null;
}
+ [SupportedOSPlatform("windows")]
+ private string getStableInstallPathFromRegistry()
+ {
+ using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
+ return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
+ }
+
protected override UpdateManager CreateUpdateManager()
{
switch (RuntimeInfo.OS)
@@ -98,6 +110,9 @@ namespace osu.Desktop
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add);
LoadComponentAsync(new DiscordRichPresence(), Add);
+
+ if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
+ LoadComponentAsync(new GameplayWinKeyBlocker(), Add);
}
protected override void ScreenChanged(IScreen lastScreen, IScreen newScreen)
@@ -121,32 +136,49 @@ namespace osu.Desktop
{
base.SetHost(host);
- switch (host.Window)
- {
- // Legacy osuTK DesktopGameWindow
- case DesktopGameWindow desktopGameWindow:
- desktopGameWindow.CursorState |= CursorState.Hidden;
- desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
- desktopGameWindow.Title = Name;
- desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames);
- break;
+ var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
- // SDL2 DesktopWindow
- case DesktopWindow desktopWindow:
- desktopWindow.CursorState.Value |= CursorState.Hidden;
- desktopWindow.Title = Name;
- desktopWindow.DragDrop += f => fileDrop(new[] { f });
- break;
- }
+ var desktopWindow = (SDL2DesktopWindow)host.Window;
+
+ desktopWindow.CursorState |= CursorState.Hidden;
+ desktopWindow.SetIconFromStream(iconStream);
+ desktopWindow.Title = Name;
+ desktopWindow.DragDrop += f => fileDrop(new[] { f });
}
+ private readonly List importableFiles = new List();
+ private ScheduledDelegate importSchedule;
+
private void fileDrop(string[] filePaths)
{
- var firstExtension = Path.GetExtension(filePaths.First());
+ lock (importableFiles)
+ {
+ var firstExtension = Path.GetExtension(filePaths.First());
- if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
+ if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
- Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
+ importableFiles.AddRange(filePaths);
+
+ Logger.Log($"Adding {filePaths.Length} files for import");
+
+ // File drag drop operations can potentially trigger hundreds or thousands of these calls on some platforms.
+ // In order to avoid spawning multiple import tasks for a single drop operation, debounce a touch.
+ importSchedule?.Cancel();
+ importSchedule = Scheduler.AddDelayed(handlePendingImports, 100);
+ }
+ }
+
+ private void handlePendingImports()
+ {
+ lock (importableFiles)
+ {
+ Logger.Log($"Handling batch import of {importableFiles.Count} files");
+
+ var paths = importableFiles.ToArray();
+ importableFiles.Clear();
+
+ Task.Factory.StartNew(() => Import(paths), TaskCreationOptions.LongRunning);
+ }
}
}
}
diff --git a/osu.Desktop/Overlays/VersionManager.cs b/osu.Desktop/Overlays/VersionManager.cs
index 8c759f8487..e4a3451651 100644
--- a/osu.Desktop/Overlays/VersionManager.cs
+++ b/osu.Desktop/Overlays/VersionManager.cs
@@ -26,9 +26,11 @@ namespace osu.Desktop.Overlays
Alpha = 0;
+ FillFlowContainer mainFill;
+
Children = new Drawable[]
{
- new FillFlowContainer
+ mainFill = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
@@ -55,23 +57,30 @@ namespace osu.Desktop.Overlays
},
}
},
- new OsuSpriteText
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Font = OsuFont.Numeric.With(size: 12),
- Colour = colours.Yellow,
- Text = @"Development Build"
- },
- new Sprite
- {
- Anchor = Anchor.TopCentre,
- Origin = Anchor.TopCentre,
- Texture = textures.Get(@"Menu/dev-build-footer"),
- },
}
}
};
+
+ if (!game.IsDeployedBuild)
+ {
+ mainFill.AddRange(new Drawable[]
+ {
+ new OsuSpriteText
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Font = OsuFont.Numeric.With(size: 12),
+ Colour = colours.Yellow,
+ Text = @"Development Build"
+ },
+ new Sprite
+ {
+ Anchor = Anchor.TopCentre,
+ Origin = Anchor.TopCentre,
+ Texture = textures.Get(@"Menu/dev-build-footer"),
+ },
+ });
+ }
}
protected override void PopIn()
diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs
index 285a813d97..d06c4b6746 100644
--- a/osu.Desktop/Program.cs
+++ b/osu.Desktop/Program.cs
@@ -22,9 +22,8 @@ namespace osu.Desktop
{
// Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory;
- bool useSdl = args.Contains("--sdl");
- using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true, useSdl: useSdl))
+ using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
{
host.ExceptionThrown += handleException;
diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs
index dd50b05c75..47cd39dc5a 100644
--- a/osu.Desktop/Updater/SquirrelUpdateManager.cs
+++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs
@@ -29,31 +29,44 @@ namespace osu.Desktop.Updater
private static readonly Logger logger = Logger.GetLogger("updater");
+ ///
+ /// Whether an update has been downloaded but not yet applied.
+ ///
+ private bool updatePending;
+
[BackgroundDependencyLoader]
- private void load(NotificationOverlay notification, OsuGameBase game)
+ private void load(NotificationOverlay notification)
{
notificationOverlay = notification;
- if (game.IsDeployedBuild)
- {
- Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
- Schedule(() => Task.Run(() => checkForUpdateAsync()));
- }
+ Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
}
- private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
+ protected override async Task PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
+
+ private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{
// should we schedule a retry on completion of this check?
bool scheduleRecheck = true;
try
{
- updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
+ updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true).ConfigureAwait(false);
+
+ var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
- var info = await updateManager.CheckForUpdate(!useDeltaPatching);
if (info.ReleasesToApply.Count == 0)
+ {
+ if (updatePending)
+ {
+ // the user may have dismissed the completion notice, so show it again.
+ notificationOverlay.Post(new UpdateCompleteNotification(this));
+ return true;
+ }
+
// no updates available. bail and retry later.
- return;
+ return false;
+ }
if (notification == null)
{
@@ -66,14 +79,15 @@ namespace osu.Desktop.Updater
try
{
- await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f);
+ await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.Progress = 0;
notification.Text = @"Installing update...";
- await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
+ await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f).ConfigureAwait(false);
notification.State = ProgressNotificationState.Completed;
+ updatePending = true;
}
catch (Exception e)
{
@@ -83,7 +97,7 @@ 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);
+ await checkForUpdateAsync(false, notification).ConfigureAwait(false);
scheduleRecheck = false;
}
else
@@ -102,9 +116,11 @@ namespace osu.Desktop.Updater
if (scheduleRecheck)
{
// check again in 30 minutes.
- Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
+ Scheduler.AddDelayed(async () => await checkForUpdateAsync().ConfigureAwait(false), 60000 * 30);
}
}
+
+ return true;
}
protected override void Dispose(bool isDisposing)
@@ -113,10 +129,27 @@ namespace osu.Desktop.Updater
updateManager?.Dispose();
}
+ private class UpdateCompleteNotification : ProgressCompletionNotification
+ {
+ [Resolved]
+ private OsuGame game { get; set; }
+
+ public UpdateCompleteNotification(SquirrelUpdateManager updateManager)
+ {
+ Text = @"Update ready to install. Click to restart!";
+
+ Activated = () =>
+ {
+ updateManager.PrepareUpdateAsync()
+ .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
+ return true;
+ };
+ }
+ }
+
private class UpdateProgressNotification : ProgressNotification
{
private readonly SquirrelUpdateManager updateManager;
- private OsuGame game;
public UpdateProgressNotification(SquirrelUpdateManager updateManager)
{
@@ -125,23 +158,12 @@ namespace osu.Desktop.Updater
protected override Notification CreateCompletionNotification()
{
- return new ProgressCompletionNotification
- {
- Text = @"Update ready to install. Click to restart!",
- Activated = () =>
- {
- updateManager.PrepareUpdateAsync()
- .ContinueWith(_ => updateManager.Schedule(() => game.GracefullyExit()));
- return true;
- }
- };
+ return new UpdateCompleteNotification(updateManager);
}
[BackgroundDependencyLoader]
- private void load(OsuColour colours, OsuGame game)
+ private void load(OsuColour colours)
{
- this.game = game;
-
IconContent.AddRange(new Drawable[]
{
new Box
diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs
new file mode 100644
index 0000000000..efc3f21149
--- /dev/null
+++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.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.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Platform;
+using osu.Game;
+using osu.Game.Configuration;
+
+namespace osu.Desktop.Windows
+{
+ public class GameplayWinKeyBlocker : Component
+ {
+ private Bindable disableWinKey;
+ private Bindable localUserPlaying;
+
+ [Resolved]
+ private GameHost host { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuGame game, OsuConfigManager config)
+ {
+ localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
+ localUserPlaying.BindValueChanged(_ => updateBlocking());
+
+ disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey);
+ disableWinKey.BindValueChanged(_ => updateBlocking(), true);
+ }
+
+ private void updateBlocking()
+ {
+ bool shouldDisable = disableWinKey.Value && localUserPlaying.Value;
+
+ if (shouldDisable)
+ host.InputThread.Scheduler.Add(WindowsKey.Disable);
+ else
+ host.InputThread.Scheduler.Add(WindowsKey.Enable);
+ }
+ }
+}
diff --git a/osu.Desktop/Windows/WindowsKey.cs b/osu.Desktop/Windows/WindowsKey.cs
new file mode 100644
index 0000000000..f19d741107
--- /dev/null
+++ b/osu.Desktop/Windows/WindowsKey.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.Runtime.InteropServices;
+
+namespace osu.Desktop.Windows
+{
+ internal class WindowsKey
+ {
+ private delegate int LowLevelKeyboardProcDelegate(int nCode, int wParam, ref KdDllHookStruct lParam);
+
+ private static bool isBlocked;
+
+ private const int wh_keyboard_ll = 13;
+ private const int wm_keydown = 256;
+ private const int wm_syskeyup = 261;
+
+ //Resharper disable once NotAccessedField.Local
+ private static LowLevelKeyboardProcDelegate keyboardHookDelegate; // keeping a reference alive for the GC
+ private static IntPtr keyHook;
+
+ [StructLayout(LayoutKind.Explicit)]
+ private readonly struct KdDllHookStruct
+ {
+ [FieldOffset(0)]
+ public readonly int VkCode;
+
+ [FieldOffset(8)]
+ public readonly int Flags;
+ }
+
+ private static int lowLevelKeyboardProc(int nCode, int wParam, ref KdDllHookStruct lParam)
+ {
+ if (wParam >= wm_keydown && wParam <= wm_syskeyup)
+ {
+ switch (lParam.VkCode)
+ {
+ case 0x5B: // left windows key
+ case 0x5C: // right windows key
+ return 1;
+ }
+ }
+
+ return callNextHookEx(0, nCode, wParam, ref lParam);
+ }
+
+ internal static void Disable()
+ {
+ if (keyHook != IntPtr.Zero || isBlocked)
+ return;
+
+ keyHook = setWindowsHookEx(wh_keyboard_ll, (keyboardHookDelegate = lowLevelKeyboardProc), Marshal.GetHINSTANCE(System.Reflection.Assembly.GetExecutingAssembly().GetModules()[0]), 0);
+
+ isBlocked = true;
+ }
+
+ internal static void Enable()
+ {
+ if (keyHook == IntPtr.Zero || !isBlocked)
+ return;
+
+ keyHook = unhookWindowsHookEx(keyHook);
+ keyboardHookDelegate = null;
+
+ keyHook = IntPtr.Zero;
+
+ isBlocked = false;
+ }
+
+ [DllImport(@"user32.dll", EntryPoint = @"SetWindowsHookExA")]
+ private static extern IntPtr setWindowsHookEx(int idHook, LowLevelKeyboardProcDelegate lpfn, IntPtr hMod, int dwThreadId);
+
+ [DllImport(@"user32.dll", EntryPoint = @"UnhookWindowsHookEx")]
+ private static extern IntPtr unhookWindowsHookEx(IntPtr hHook);
+
+ [DllImport(@"user32.dll", EntryPoint = @"CallNextHookEx")]
+ private static extern int callNextHookEx(int hHook, int nCode, int wParam, ref KdDllHookStruct lParam);
+ }
+}
diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj
index 7a99c70999..3e0f0cb7f6 100644
--- a/osu.Desktop/osu.Desktop.csproj
+++ b/osu.Desktop/osu.Desktop.csproj
@@ -1,9 +1,9 @@
- netcoreapp3.1
+ net5.0
WinExe
true
- click the circles. to the beat.
+ A free-to-win rhythm game. Rhythm is just a *click* away!
osu!
osu!lazer
osu!lazer
@@ -24,16 +24,13 @@
-
-
+
+
+
-
-
-
-
-
-
+
+
diff --git a/osu.Desktop/osu.nuspec b/osu.Desktop/osu.nuspec
index a919d54f38..fa182f8e70 100644
--- a/osu.Desktop/osu.nuspec
+++ b/osu.Desktop/osu.nuspec
@@ -9,10 +9,9 @@
https://osu.ppy.sh/
https://puu.sh/tYyXZ/9a01a5d1b0.ico
false
- click the circles. to the beat.
- click the circles.
+ A free-to-win rhythm game. Rhythm is just a *click* away!
testing
- Copyright (c) 2020 ppy Pty Ltd
+ Copyright (c) 2021 ppy Pty Ltd
en-AU
diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
index 41e726e05c..ea43d9a54c 100644
--- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
+++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj
@@ -1,15 +1,15 @@
- netcoreapp3.1
+ net5.0
Exe
false
-
-
+
+
diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
index 88b420ffad..94fdba4a3e 100644
--- a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
+++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj
@@ -14,6 +14,11 @@
Properties\AndroidManifest.xml
armeabi-v7a;x86;arm64-v8a
+
+ None
+ cjk;mideast;other;rare;west
+ true
+
@@ -35,5 +40,10 @@
osu.Game
+
+
+ 5.0.0
+
+
\ 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 67d27c33eb..9aaaf418c2 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/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Debug/net5.0/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
- "${workspaceRoot}/bin/Release/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll"
+ "${workspaceRoot}/bin/Release/net5.0/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",
diff --git a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json
index 18a6f8ca70..d8feacc8a7 100644
--- a/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json
+++ b/osu.Game.Rulesets.Catch.Tests/.vscode/tasks.json
@@ -9,11 +9,10 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Game.Rulesets.Catch.Tests.csproj",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-p:GenerateFullPaths=true",
+ "-m",
+ "-verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
@@ -24,24 +23,14 @@
"command": "dotnet",
"args": [
"build",
- "--no-restore",
"osu.Game.Rulesets.Catch.Tests.csproj",
- "/p:Configuration=Release",
- "/p:GenerateFullPaths=true",
- "/m",
- "/verbosity:m"
+ "-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.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
index f4749be370..33fdcdaf1e 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs
@@ -8,13 +8,13 @@ using NUnit.Framework;
using osu.Framework.Utils;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
+ [Timeout(10000)]
public class CatchBeatmapConversionTest : BeatmapConversionTest
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
@@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestCase("hardrock-stream", new[] { typeof(CatchModHardRock) })]
[TestCase("hardrock-repeat-slider", new[] { typeof(CatchModHardRock) })]
[TestCase("hardrock-spinner", new[] { typeof(CatchModHardRock) })]
+ [TestCase("right-bound-hr-offset", new[] { typeof(CatchModHardRock) })]
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
protected override IEnumerable CreateConvertValue(HitObject hitObject)
@@ -83,7 +84,7 @@ namespace osu.Game.Rulesets.Catch.Tests
public float Position
{
- get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position;
+ get => HitObject?.EffectiveX ?? position;
set => position = value;
}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
index ee416e5a38..5580358f89 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchDifficultyCalculatorTest.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
+using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Tests.Beatmaps;
@@ -17,6 +18,10 @@ namespace osu.Game.Rulesets.Catch.Tests
public void Test(double expected, string name)
=> base.Test(expected, name);
+ [TestCase(5.169743871843191d, "diffcalc-test")]
+ public void TestClockRateAdjusted(double expected, string name)
+ => Test(expected, name, new CatchModDoubleTime());
+
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/CatchLegacyModConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
index 04e6dea376..eae07daa3d 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchLegacyModConversionTest.cs
@@ -12,17 +12,32 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class CatchLegacyModConversionTest : LegacyModConversionTest
{
- [TestCase(LegacyMods.Easy, new[] { typeof(CatchModEasy) })]
- [TestCase(LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) })]
- [TestCase(LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) })]
- [TestCase(LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) })]
+ private static readonly object[][] catch_mod_mapping =
+ {
+ new object[] { LegacyMods.NoFail, new[] { typeof(CatchModNoFail) } },
+ new object[] { LegacyMods.Easy, new[] { typeof(CatchModEasy) } },
+ new object[] { LegacyMods.Hidden, new[] { typeof(CatchModHidden) } },
+ new object[] { LegacyMods.HardRock, new[] { typeof(CatchModHardRock) } },
+ new object[] { LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) } },
+ new object[] { LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime) } },
+ new object[] { LegacyMods.Relax, new[] { typeof(CatchModRelax) } },
+ new object[] { LegacyMods.HalfTime, new[] { typeof(CatchModHalfTime) } },
+ new object[] { LegacyMods.Nightcore, new[] { typeof(CatchModNightcore) } },
+ new object[] { LegacyMods.Flashlight, new[] { typeof(CatchModFlashlight) } },
+ new object[] { LegacyMods.Autoplay, new[] { typeof(CatchModAutoplay) } },
+ new object[] { LegacyMods.Perfect, new[] { typeof(CatchModPerfect) } },
+ new object[] { LegacyMods.Cinema, new[] { typeof(CatchModCinema) } },
+ new object[] { LegacyMods.HardRock | LegacyMods.DoubleTime, new[] { typeof(CatchModHardRock), typeof(CatchModDoubleTime) } }
+ };
+
+ [TestCaseSource(nameof(catch_mod_mapping))]
+ [TestCase(LegacyMods.Cinema | LegacyMods.Autoplay, new[] { typeof(CatchModCinema) })]
[TestCase(LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModNightcore) })]
- [TestCase(LegacyMods.Flashlight | LegacyMods.Nightcore | LegacyMods.DoubleTime, new[] { typeof(CatchModFlashlight), typeof(CatchModNightcore) })]
- [TestCase(LegacyMods.Perfect, new[] { typeof(CatchModPerfect) })]
- [TestCase(LegacyMods.SuddenDeath, new[] { typeof(CatchModSuddenDeath) })]
[TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath, new[] { typeof(CatchModPerfect) })]
- [TestCase(LegacyMods.Perfect | LegacyMods.SuddenDeath | LegacyMods.DoubleTime, new[] { typeof(CatchModDoubleTime), typeof(CatchModPerfect) })]
- public new void Test(LegacyMods legacyMods, Type[] expectedMods) => base.Test(legacyMods, expectedMods);
+ public new void TestFromLegacy(LegacyMods legacyMods, Type[] expectedMods) => base.TestFromLegacy(legacyMods, expectedMods);
+
+ [TestCaseSource(nameof(catch_mod_mapping))]
+ public new void TestToLegacy(LegacyMods legacyMods, Type[] givenMods) => base.TestToLegacy(legacyMods, givenMods);
protected override Ruleset CreateRuleset() => new CatchRuleset();
}
diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
index 7deeec527f..e70def7f8b 100644
--- a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
+++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
@@ -4,6 +4,7 @@
using NUnit.Framework;
using osu.Framework.IO.Stores;
using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -17,7 +18,8 @@ namespace osu.Game.Rulesets.Catch.Tests
{
var store = new NamespacedResourceStore(new DllResourceStore(GetType().Assembly), "Resources/special-skin");
var rawSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, store);
- var skin = new CatchLegacySkinTransformer(rawSkin);
+ var skinSource = new SkinProvidingContainer(rawSkin);
+ var skin = new CatchLegacySkinTransformer(skinSource);
Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig(CatchSkinColour.HyperDash)?.Value);
Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value);
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
index 47e91e50d4..3e06e78dba 100644
--- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs
@@ -13,8 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
{
public class TestSceneCatchModPerfect : ModPerfectTestScene
{
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+
public TestSceneCatchModPerfect()
- : base(new CatchRuleset(), new CatchModPerfect())
+ : base(new CatchModPerfect())
{
}
diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs
new file mode 100644
index 0000000000..c01aff0aa0
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.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 System.Collections.Generic;
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Tests.Mods
+{
+ public class TestSceneCatchModRelax : ModTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+
+ [Test]
+ public void TestModRelax() => CreateModTest(new ModTestData
+ {
+ Mod = new CatchModRelax(),
+ Autoplay = false,
+ PassCondition = passCondition,
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new Fruit
+ {
+ X = CatchPlayfield.CENTER_X,
+ StartTime = 0
+ },
+ new Fruit
+ {
+ X = 0,
+ StartTime = 1000
+ },
+ new Fruit
+ {
+ X = CatchPlayfield.WIDTH,
+ StartTime = 2000
+ },
+ new JuiceStream
+ {
+ X = CatchPlayfield.CENTER_X,
+ StartTime = 3000,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 })
+ }
+ }
+ }
+ });
+
+ private bool passCondition()
+ {
+ var playfield = this.ChildrenOfType().Single();
+
+ switch (Player.ScoreProcessor.Combo.Value)
+ {
+ case 0:
+ InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
+ break;
+
+ case 1:
+ InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomLeft);
+ break;
+
+ case 2:
+ InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.BottomRight);
+ break;
+
+ case 3:
+ InputManager.MoveMouseTo(playfield.ScreenSpaceDrawQuad.Centre);
+ break;
+ }
+
+ return Player.ScoreProcessor.Combo.Value >= 6;
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png
new file mode 100644
index 0000000000..8304617d8c
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-0.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-1.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-1.png
new file mode 100644
index 0000000000..c3b85eb873
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-1.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-2.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-2.png
new file mode 100644
index 0000000000..7f65eb7ca7
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-2.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png
new file mode 100644
index 0000000000..82bec3babe
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-3.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-4.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-4.png
new file mode 100644
index 0000000000..5e38c75a9d
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-4.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-5.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-5.png
new file mode 100644
index 0000000000..a562d9f2ac
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-5.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png
new file mode 100644
index 0000000000..b4cf81f26e
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-6.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png
new file mode 100644
index 0000000000..a23f5379b2
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-7.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-8.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-8.png
new file mode 100644
index 0000000000..430b18509d
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-8.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-9.png b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-9.png
new file mode 100644
index 0000000000..add1202c31
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/old-skin/score-9.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-0@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-0@2x.png
new file mode 100644
index 0000000000..508cc85e4a
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-0@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-1@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-1@2x.png
new file mode 100644
index 0000000000..84f74e1ec9
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-1@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-2@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-2@2x.png
new file mode 100644
index 0000000000..49625c6623
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-2@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-3@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-3@2x.png
new file mode 100644
index 0000000000..623b24612f
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-3@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-4@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-4@2x.png
new file mode 100644
index 0000000000..a33286dc8f
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-4@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-5@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-5@2x.png
new file mode 100644
index 0000000000..d8250b0c63
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-5@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-6@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-6@2x.png
new file mode 100644
index 0000000000..75d3cbd3bd
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-6@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-7@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-7@2x.png
new file mode 100644
index 0000000000..cfe2021df4
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-7@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-8@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-8@2x.png
new file mode 100644
index 0000000000..ba9492c7f8
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-8@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-9@2x.png b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-9@2x.png
new file mode 100644
index 0000000000..a7b6b81570
Binary files /dev/null and b/osu.Game.Rulesets.Catch.Tests/Resources/special-skin/score-9@2x.png differ
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
index ed7bfb9a44..f552c3c27b 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System.Collections.Generic;
using System.Linq;
+using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
@@ -12,13 +14,8 @@ using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class TestSceneAutoJuiceStream : PlayerTestScene
+ public class TestSceneAutoJuiceStream : TestSceneCatchPlayer
{
- public TestSceneAutoJuiceStream()
- : base(new CatchRuleset())
- {
- }
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
@@ -32,18 +29,22 @@ namespace osu.Game.Rulesets.Catch.Tests
for (int i = 0; i < 100; i++)
{
- float width = (i % 10 + 1) / 20f;
+ float width = (i % 10 + 1) / 20f * CatchPlayfield.WIDTH;
beatmap.HitObjects.Add(new JuiceStream
{
- X = 0.5f - width / 2,
+ X = CatchPlayfield.CENTER_X - width / 2,
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
- new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
+ new Vector2(width, 0)
}),
StartTime = i * 2000,
- NewCombo = i % 8 == 0
+ NewCombo = i % 8 == 0,
+ Samples = new List(new[]
+ {
+ new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100)
+ })
});
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
index 27a2d5bd0a..e89a95ae37 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs
@@ -4,18 +4,12 @@
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 TestSceneBananaShower : PlayerTestScene
+ public class TestSceneBananaShower : TestSceneCatchPlayer
{
- public TestSceneBananaShower()
- : base(new CatchRuleset())
- {
- }
-
[Test]
public void TestBananaShower()
{
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs
new file mode 100644
index 0000000000..1248409b2a
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.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.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Catch.Mods;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Objects;
+using osu.Game.Rulesets.Objects.Types;
+using osu.Game.Tests.Visual;
+using osuTK;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneCatchModHidden : ModTestScene
+ {
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ LocalConfig.SetValue(OsuSetting.IncreaseFirstObjectVisibility, false);
+ }
+
+ [Test]
+ public void TestJuiceStream()
+ {
+ CreateModTest(new ModTestData
+ {
+ Beatmap = new Beatmap
+ {
+ HitObjects = new List
+ {
+ new JuiceStream
+ {
+ StartTime = 1000,
+ Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(0, -192) }),
+ X = CatchPlayfield.WIDTH / 2
+ }
+ }
+ },
+ Mod = new CatchModHidden(),
+ PassCondition = () => Player.Results.Count > 0
+ && Player.ChildrenOfType().Single().Alpha > 0
+ && Player.ChildrenOfType().Last().Alpha > 0
+ });
+ }
+
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
index 9836a7811a..31d0831fae 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs
@@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Catch.Tests
[TestFixture]
public class TestSceneCatchPlayer : PlayerTestScene
{
- public TestSceneCatchPlayer()
- : base(new CatchRuleset())
- {
- }
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs
new file mode 100644
index 0000000000..64695153b5
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.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 NUnit.Framework;
+using osu.Game.Tests.Visual;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ [TestFixture]
+ public class TestSceneCatchPlayerLegacySkin : LegacySkinPlayerTestScene
+ {
+ protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
index 9ce46ad6ba..1ff31697b8 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs
@@ -4,18 +4,13 @@
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
-using osu.Game.Tests.Visual;
+using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneCatchStacker : PlayerTestScene
+ public class TestSceneCatchStacker : TestSceneCatchPlayer
{
- public TestSceneCatchStacker()
- : base(new CatchRuleset())
- {
- }
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
@@ -28,7 +23,14 @@ namespace osu.Game.Rulesets.Catch.Tests
};
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 });
+ {
+ beatmap.HitObjects.Add(new Fruit
+ {
+ X = (0.5f + i / 2048f * (i % 10 - 5)) * CatchPlayfield.WIDTH,
+ StartTime = i * 100,
+ NewCombo = i % 8 == 0
+ });
+ }
return beatmap;
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
index 6eeda2c731..48efd73222 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs
@@ -1,26 +1,295 @@
// Copyright (c) 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.Rulesets.Catch.UI;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Catch.Judgements;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osu.Game.Skinning;
+using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneCatcher : CatchSkinnableTestScene
+ public class TestSceneCatcher : OsuTestScene
{
- [BackgroundDependencyLoader]
- private void load()
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ private Container droppedObjectContainer;
+
+ private TestCatcher catcher;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
{
- SetContents(() => new Catcher(new Container())
+ var difficulty = new BeatmapDifficulty
+ {
+ CircleSize = 0,
+ };
+
+ var trailContainer = new Container();
+ droppedObjectContainer = new Container();
+ catcher = new TestCatcher(trailContainer, droppedObjectContainer, difficulty);
+
+ Child = new Container
{
- RelativePositionAxes = Axes.None,
Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ trailContainer,
+ droppedObjectContainer,
+ catcher
+ }
+ };
+ });
+
+ [Test]
+ public void TestCatcherHyperStateReverted()
+ {
+ DrawableCatchHitObject drawableObject1 = null;
+ DrawableCatchHitObject drawableObject2 = null;
+ JudgementResult result1 = null;
+ JudgementResult result2 = null;
+ AddStep("catch hyper fruit", () =>
+ {
+ attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
});
+ AddStep("catch normal fruit", () =>
+ {
+ attemptCatch(new Fruit(), out drawableObject2, out result2);
+ });
+ AddStep("revert second result", () =>
+ {
+ catcher.OnRevertResult(drawableObject2, result2);
+ });
+ checkHyperDash(true);
+ AddStep("revert first result", () =>
+ {
+ catcher.OnRevertResult(drawableObject1, result1);
+ });
+ checkHyperDash(false);
+ }
+
+ [Test]
+ public void TestCatcherAnimationStateReverted()
+ {
+ DrawableCatchHitObject drawableObject = null;
+ JudgementResult result = null;
+ AddStep("catch kiai fruit", () =>
+ {
+ attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
+ });
+ checkState(CatcherAnimationState.Kiai);
+ AddStep("revert result", () =>
+ {
+ catcher.OnRevertResult(drawableObject, result);
+ });
+ checkState(CatcherAnimationState.Idle);
+ }
+
+ [Test]
+ public void TestCatcherCatchWidth()
+ {
+ var halfWidth = Catcher.CalculateCatchWidth(new BeatmapDifficulty { CircleSize = 0 }) / 2;
+ AddStep("catch fruit", () =>
+ {
+ attemptCatch(new Fruit { X = -halfWidth + 1 });
+ attemptCatch(new Fruit { X = halfWidth - 1 });
+ });
+ checkPlate(2);
+ AddStep("miss fruit", () =>
+ {
+ attemptCatch(new Fruit { X = -halfWidth - 1 });
+ attemptCatch(new Fruit { X = halfWidth + 1 });
+ });
+ checkPlate(2);
+ }
+
+ [Test]
+ public void TestFruitChangesCatcherState()
+ {
+ AddStep("miss fruit", () => attemptCatch(new Fruit { X = 100 }));
+ checkState(CatcherAnimationState.Fail);
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ checkState(CatcherAnimationState.Idle);
+ AddStep("catch kiai fruit", () => attemptCatch(new TestKiaiFruit()));
+ checkState(CatcherAnimationState.Kiai);
+ }
+
+ [Test]
+ public void TestNormalFruitResetsHyperDashState()
+ {
+ AddStep("catch hyper fruit", () => attemptCatch(new Fruit
+ {
+ HyperDashTarget = new Fruit { X = 100 }
+ }));
+ checkHyperDash(true);
+ AddStep("catch normal fruit", () => attemptCatch(new Fruit()));
+ checkHyperDash(false);
+ }
+
+ [Test]
+ public void TestTinyDropletMissPreservesCatcherState()
+ {
+ AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
+ {
+ HyperDashTarget = new Fruit { X = 100 }
+ }));
+ AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
+ AddStep("miss tiny droplet", () => attemptCatch(new TinyDroplet { X = 100 }));
+ // catcher state and hyper dash state is preserved
+ checkState(CatcherAnimationState.Kiai);
+ checkHyperDash(true);
+ }
+
+ [Test]
+ public void TestBananaMissPreservesCatcherState()
+ {
+ AddStep("catch hyper kiai fruit", () => attemptCatch(new TestKiaiFruit
+ {
+ HyperDashTarget = new Fruit { X = 100 }
+ }));
+ AddStep("miss banana", () => attemptCatch(new Banana { X = 100 }));
+ // catcher state is preserved but hyper dash state is reset
+ checkState(CatcherAnimationState.Kiai);
+ checkHyperDash(false);
+ }
+
+ [Test]
+ public void TestCatcherStacking()
+ {
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ checkPlate(1);
+ AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
+ checkPlate(10);
+ AddAssert("caught objects are stacked", () =>
+ catcher.CaughtObjects.All(obj => obj.Y <= 0) &&
+ catcher.CaughtObjects.Any(obj => obj.Y == 0) &&
+ catcher.CaughtObjects.Any(obj => obj.Y < -20));
+ }
+
+ [Test]
+ public void TestCatcherExplosionAndDropping()
+ {
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddStep("catch tiny droplet", () => attemptCatch(new TinyDroplet()));
+ AddAssert("tiny droplet is exploded", () => catcher.CaughtObjects.Count() == 1 && droppedObjectContainer.Count == 1);
+ AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
+ AddStep("catch more fruits", () => attemptCatch(new Fruit(), 9));
+ AddStep("explode", () => catcher.Explode());
+ AddAssert("fruits are exploded", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
+ AddUntilStep("wait explosion", () => !droppedObjectContainer.Any());
+ AddStep("catch fruits", () => attemptCatch(new Fruit(), 10));
+ AddStep("drop", () => catcher.Drop());
+ AddAssert("fruits are dropped", () => !catcher.CaughtObjects.Any() && droppedObjectContainer.Count == 10);
+ }
+
+ [Test]
+ public void TestHitLightingColour()
+ {
+ var fruitColour = SkinConfiguration.DefaultComboColours[1];
+ AddStep("enable hit lighting", () => config.SetValue(OsuSetting.HitLighting, true));
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddAssert("correct hit lighting colour", () =>
+ catcher.ChildrenOfType().First()?.ObjectColour == fruitColour);
+ }
+
+ [Test]
+ public void TestHitLightingDisabled()
+ {
+ AddStep("disable hit lighting", () => config.SetValue(OsuSetting.HitLighting, false));
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddAssert("no hit lighting", () => !catcher.ChildrenOfType().Any());
+ }
+
+ private void checkPlate(int count) => AddAssert($"{count} objects on the plate", () => catcher.CaughtObjects.Count() == count);
+
+ private void checkState(CatcherAnimationState state) => AddAssert($"catcher state is {state}", () => catcher.CurrentState == state);
+
+ private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
+
+ private void attemptCatch(CatchHitObject hitObject, int count = 1)
+ {
+ for (var i = 0; i < count; i++)
+ attemptCatch(hitObject, out _, out _);
+ }
+
+ private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
+ {
+ hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ drawableObject = createDrawableObject(hitObject);
+ result = createResult(hitObject);
+ applyResult(drawableObject, result);
+ }
+
+ private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
+ {
+ // Load DHO to set colour of hit explosion correctly
+ Add(drawableObject);
+ drawableObject.OnLoadComplete += _ =>
+ {
+ catcher.OnNewResult(drawableObject, result);
+ drawableObject.Expire();
+ };
+ }
+
+ private JudgementResult createResult(CatchHitObject hitObject)
+ {
+ return new CatchJudgementResult(hitObject, hitObject.CreateJudgement())
+ {
+ Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss
+ };
+ }
+
+ private DrawableCatchHitObject createDrawableObject(CatchHitObject hitObject)
+ {
+ switch (hitObject)
+ {
+ case Banana banana:
+ return new DrawableBanana(banana);
+
+ case Droplet droplet:
+ return new DrawableDroplet(droplet);
+
+ case Fruit fruit:
+ return new DrawableFruit(fruit);
+
+ default:
+ throw new ArgumentOutOfRangeException(nameof(hitObject));
+ }
+ }
+
+ public class TestCatcher : Catcher
+ {
+ public IEnumerable CaughtObjects => this.ChildrenOfType();
+
+ public TestCatcher(Container trailsTarget, Container droppedObjectTarget, BeatmapDifficulty difficulty)
+ : base(trailsTarget, droppedObjectTarget, difficulty)
+ {
+ }
+ }
+
+ public class TestKiaiFruit : Fruit
+ {
+ protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
+ {
+ controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
+ base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
+ }
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
index 2b30edb70b..1cbfa6338e 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs
@@ -6,17 +6,16 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
-using osu.Game.Rulesets.Catch.Beatmaps;
+using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
-using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -25,61 +24,73 @@ namespace osu.Game.Rulesets.Catch.Tests
{
private RulesetInfo catchRuleset;
+ [Resolved]
+ private OsuConfigManager config { get; set; }
+
+ private Catcher catcher => this.ChildrenOfType().First();
+
+ private float circleSize;
+
public TestSceneCatcherArea()
{
- AddSliderStep("CircleSize", 0, 8, 5, createCatcher);
- AddToggleStep("Hyperdash", t =>
- CreatedDrawables.OfType().Select(i => i.Child)
- .OfType().ForEach(c => c.ToggleHyperDash(t)));
+ AddSliderStep("circle size", 0, 8, 5, createCatcher);
+ AddToggleStep("hyper dash", t => this.ChildrenOfType().ForEach(area => area.ToggleHyperDash(t)));
- AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
- {
- X = this.ChildrenOfType().First().MovableCatcher.X
- }), 20);
- AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
- {
- X = this.ChildrenOfType().First().MovableCatcher.X,
- LastInCombo = true,
- }), 20);
- AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
- {
- X = this.ChildrenOfType().First().MovableCatcher.X,
- }), 20);
- AddRepeatStep("miss fruit", () => catchFruit(new Fruit
- {
- X = this.ChildrenOfType().First().MovableCatcher.X + 100,
- LastInCombo = true,
- }, true), 20);
+ AddStep("catch fruit", () => attemptCatch(new Fruit()));
+ AddStep("catch fruit last in combo", () => attemptCatch(new Fruit { LastInCombo = true }));
+ AddStep("catch kiai fruit", () => attemptCatch(new TestSceneCatcher.TestKiaiFruit()));
+ AddStep("miss last in combo", () => attemptCatch(new Fruit { X = 100, LastInCombo = true }));
}
- private void catchFruit(Fruit fruit, bool miss = false)
+ private void attemptCatch(Fruit fruit)
{
- this.ChildrenOfType().ForEach(area =>
+ fruit.X = fruit.OriginalX + catcher.X;
+ fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty
+ {
+ CircleSize = circleSize
+ });
+
+ foreach (var area in this.ChildrenOfType())
{
DrawableFruit drawable = new DrawableFruit(fruit);
area.Add(drawable);
Schedule(() =>
{
- area.AttemptCatch(fruit);
- area.OnResult(drawable, new JudgementResult(fruit, new CatchJudgement()) { Type = miss ? HitResult.Miss : HitResult.Great });
+ area.OnNewResult(drawable, new CatchJudgementResult(fruit, new CatchJudgement())
+ {
+ Type = area.MovableCatcher.CanCatch(fruit) ? HitResult.Great : HitResult.Miss
+ });
drawable.Expire();
});
- });
+ }
}
private void createCatcher(float size)
{
- SetContents(() => new CatchInputManager(catchRuleset)
+ circleSize = size;
+
+ SetContents(() =>
{
- RelativeSizeAxes = Axes.Both,
- Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
+ var droppedObjectContainer = new Container
{
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.TopLeft,
- CreateDrawableRepresentation = ((DrawableRuleset)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
- },
+ RelativeSizeAxes = Axes.Both
+ };
+
+ return new CatchInputManager(catchRuleset)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Children = new Drawable[]
+ {
+ droppedObjectContainer,
+ new TestCatcherArea(droppedObjectContainer, new BeatmapDifficulty { CircleSize = size })
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.TopCentre,
+ }
+ }
+ };
});
}
@@ -89,26 +100,13 @@ namespace osu.Game.Rulesets.Catch.Tests
catchRuleset = rulesets.GetRuleset(2);
}
- public class TestFruit : Fruit
- {
- public TestFruit(bool kiai)
- {
- var kiaiCpi = new ControlPointInfo();
- kiaiCpi.Add(0, new EffectControlPoint { KiaiMode = kiai });
-
- ApplyDefaultsToSelf(kiaiCpi, new BeatmapDifficulty());
- }
- }
-
private class TestCatcherArea : CatcherArea
{
- public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
- : base(beatmapDifficulty)
+ public TestCatcherArea(Container droppedObjectContainer, BeatmapDifficulty beatmapDifficulty)
+ : base(droppedObjectContainer, beatmapDifficulty)
{
}
- public new Catcher MovableCatcher => base.MovableCatcher;
-
public void ToggleHyperDash(bool status) => MovableCatcher.SetHyperDashState(status ? 2 : 1);
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.cs
new file mode 100644
index 0000000000..c7b322c8a0
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneComboCounter.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 System.Linq;
+using NUnit.Framework;
+using osu.Framework.Graphics;
+using osu.Framework.Utils;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Rulesets.Catch.UI;
+using osu.Game.Rulesets.Judgements;
+using osu.Game.Rulesets.Scoring;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneComboCounter : CatchSkinnableTestScene
+ {
+ private ScoreProcessor scoreProcessor;
+
+ private Color4 judgedObjectColour = Color4.White;
+
+ [SetUp]
+ public void SetUp() => Schedule(() =>
+ {
+ scoreProcessor = new ScoreProcessor();
+
+ SetContents(() => new CatchComboDisplay
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Scale = new Vector2(2.5f),
+ });
+ });
+
+ [Test]
+ public void TestCatchComboCounter()
+ {
+ AddRepeatStep("perform hit", () => performJudgement(HitResult.Great), 20);
+ AddStep("perform miss", () => performJudgement(HitResult.Miss));
+
+ AddStep("randomize judged object colour", () =>
+ {
+ judgedObjectColour = new Color4(
+ RNG.NextSingle(1f),
+ RNG.NextSingle(1f),
+ RNG.NextSingle(1f),
+ 1f
+ );
+ });
+ }
+
+ private void performJudgement(HitResult type, Judgement judgement = null)
+ {
+ var judgedObject = new DrawableFruit(new Fruit()) { AccentColour = { Value = judgedObjectColour } };
+
+ var result = new JudgementResult(judgedObject.HitObject, judgement ?? new Judgement()) { Type = type };
+ scoreProcessor.ApplyResult(result);
+
+ foreach (var counter in CreatedDrawables.Cast())
+ counter.OnNewResult(judgedObject, result);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
index a7094c00be..3e4995482d 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs
@@ -136,7 +136,7 @@ namespace osu.Game.Rulesets.Catch.Tests
if (juice.NestedHitObjects.Last() is CatchHitObject tail)
tail.LastInCombo = true; // usually the (Catch)BeatmapProcessor would do this for us when necessary
- addToPlayfield(new DrawableJuiceStream(juice, drawableRuleset.CreateDrawableRepresentation));
+ addToPlayfield(new DrawableJuiceStream(juice));
}
private void spawnBananas(bool hit = false)
@@ -158,8 +158,8 @@ namespace osu.Game.Rulesets.Catch.Tests
private float getXCoords(bool hit)
{
- const float x_offset = 0.2f;
- float xCoords = drawableRuleset.Playfield.Width / 2;
+ const float x_offset = 0.2f * CatchPlayfield.WIDTH;
+ float xCoords = CatchPlayfield.CENTER_X;
if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield)
catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset;
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
index c07e4fdad3..3a651605d3 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitObjects.cs
@@ -1,12 +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 NUnit.Framework;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
-using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -17,83 +19,69 @@ namespace osu.Game.Rulesets.Catch.Tests
{
base.LoadComplete();
- foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
- AddStep($"show {rep}", () => SetContents(() => createDrawable(rep)));
+ AddStep("show pear", () => SetContents(() => createDrawableFruit(0)));
+ AddStep("show grape", () => SetContents(() => createDrawableFruit(1)));
+ AddStep("show pineapple / apple", () => SetContents(() => createDrawableFruit(2)));
+ AddStep("show raspberry / orange", () => SetContents(() => createDrawableFruit(3)));
- AddStep("show droplet", () => SetContents(createDrawableDroplet));
+ AddStep("show banana", () => SetContents(createDrawableBanana));
+ AddStep("show droplet", () => SetContents(() => createDrawableDroplet()));
AddStep("show tiny droplet", () => SetContents(createDrawableTinyDroplet));
- foreach (FruitVisualRepresentation rep in Enum.GetValues(typeof(FruitVisualRepresentation)))
- AddStep($"show hyperdash {rep}", () => SetContents(() => createDrawable(rep, true)));
+ AddStep("show hyperdash pear", () => SetContents(() => createDrawableFruit(0, true)));
+ AddStep("show hyperdash grape", () => SetContents(() => createDrawableFruit(1, true)));
+ AddStep("show hyperdash pineapple / apple", () => SetContents(() => createDrawableFruit(2, true)));
+ AddStep("show hyperdash raspberry / orange", () => SetContents(() => createDrawableFruit(3, true)));
+
+ AddStep("show hyperdash droplet", () => SetContents(() => createDrawableDroplet(true)));
}
- private Drawable createDrawableTinyDroplet()
+ private Drawable createDrawableFruit(int indexInBeatmap, bool hyperdash = false) =>
+ new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
+ {
+ IndexInBeatmap = indexInBeatmap,
+ HyperDashBindable = { Value = hyperdash }
+ }));
+
+ private Drawable createDrawableBanana() =>
+ new TestDrawableCatchHitObjectSpecimen(new DrawableBanana(new Banana()));
+
+ private Drawable createDrawableDroplet(bool hyperdash = false) =>
+ new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
+ {
+ HyperDashBindable = { Value = hyperdash }
+ }));
+
+ private Drawable createDrawableTinyDroplet() => new TestDrawableCatchHitObjectSpecimen(new DrawableTinyDroplet(new TinyDroplet()));
+ }
+
+ public class TestDrawableCatchHitObjectSpecimen : CompositeDrawable
+ {
+ public readonly ManualClock ManualClock;
+
+ public TestDrawableCatchHitObjectSpecimen(DrawableCatchHitObject d)
{
- var droplet = new TinyDroplet
+ AutoSizeAxes = Axes.Both;
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+
+ ManualClock = new ManualClock();
+ Clock = new FramedClock(ManualClock);
+
+ var hitObject = d.HitObject;
+ hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
+ hitObject.Scale = 1.5f;
+ hitObject.StartTime = 500;
+
+ d.Anchor = Anchor.Centre;
+ d.HitObjectApplied += _ =>
{
- StartTime = Clock.CurrentTime,
- Scale = 1.5f,
+ d.LifetimeStart = double.NegativeInfinity;
+ d.LifetimeEnd = double.PositiveInfinity;
};
- return new DrawableTinyDroplet(droplet)
- {
- Anchor = Anchor.Centre,
- RelativePositionAxes = Axes.None,
- Position = Vector2.Zero,
- Alpha = 1,
- LifetimeStart = double.NegativeInfinity,
- LifetimeEnd = double.PositiveInfinity,
- };
- }
-
- private Drawable createDrawableDroplet()
- {
- var droplet = new Droplet
- {
- StartTime = Clock.CurrentTime,
- Scale = 1.5f,
- };
-
- return new DrawableDroplet(droplet)
- {
- Anchor = Anchor.Centre,
- RelativePositionAxes = Axes.None,
- Position = Vector2.Zero,
- Alpha = 1,
- LifetimeStart = double.NegativeInfinity,
- LifetimeEnd = double.PositiveInfinity,
- };
- }
-
- private Drawable createDrawable(FruitVisualRepresentation rep, bool hyperdash = false)
- {
- Fruit fruit = new TestCatchFruit(rep)
- {
- Scale = 1.5f,
- HyperDashTarget = hyperdash ? new Banana() : null
- };
-
- return new DrawableFruit(fruit)
- {
- Anchor = Anchor.Centre,
- RelativePositionAxes = Axes.None,
- Position = Vector2.Zero,
- Alpha = 1,
- LifetimeStart = double.NegativeInfinity,
- LifetimeEnd = double.PositiveInfinity,
- };
- }
-
- public class TestCatchFruit : Fruit
- {
- public TestCatchFruit(FruitVisualRepresentation rep)
- {
- VisualRepresentation = rep;
- StartTime = 1000000000000;
- }
-
- public override FruitVisualRepresentation VisualRepresentation { get; }
+ InternalChild = d;
}
}
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.cs
new file mode 100644
index 0000000000..c888dc0a65
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitRandomness.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 NUnit.Framework;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+using osu.Game.Tests.Visual;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneFruitRandomness : OsuTestScene
+ {
+ private readonly DrawableFruit drawableFruit;
+ private readonly DrawableBanana drawableBanana;
+
+ public TestSceneFruitRandomness()
+ {
+ drawableFruit = new DrawableFruit(new Fruit());
+ drawableBanana = new DrawableBanana(new Banana());
+
+ Add(new TestDrawableCatchHitObjectSpecimen(drawableFruit) { X = -200 });
+ Add(new TestDrawableCatchHitObjectSpecimen(drawableBanana));
+
+ AddSliderStep("start time", 500, 600, 0, x =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = x;
+ });
+ }
+
+ [Test]
+ public void TestFruitRandomness()
+ {
+ // Use values such that the banana colour changes (2/3 of the integers are okay)
+ const int initial_start_time = 500;
+ const int another_start_time = 501;
+
+ float fruitRotation = 0;
+ float bananaRotation = 0;
+ Vector2 bananaSize = new Vector2();
+ Color4 bananaColour = new Color4();
+
+ AddStep("Initialize start time", () =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
+
+ fruitRotation = drawableFruit.DisplayRotation;
+ bananaRotation = drawableBanana.DisplayRotation;
+ bananaSize = drawableBanana.DisplaySize;
+ bananaColour = drawableBanana.AccentColour.Value;
+ });
+
+ AddStep("change start time", () =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = another_start_time;
+ });
+
+ AddAssert("fruit rotation is changed", () => drawableFruit.DisplayRotation != fruitRotation);
+ AddAssert("banana rotation is changed", () => drawableBanana.DisplayRotation != bananaRotation);
+ AddAssert("banana size is changed", () => drawableBanana.DisplaySize != bananaSize);
+ AddAssert("banana colour is changed", () => drawableBanana.AccentColour.Value != bananaColour);
+
+ AddStep("reset start time", () =>
+ {
+ drawableFruit.HitObject.StartTime = drawableBanana.HitObject.StartTime = initial_start_time;
+ });
+
+ AddAssert("rotation and size restored", () =>
+ drawableFruit.DisplayRotation == fruitRotation &&
+ drawableBanana.DisplayRotation == bananaRotation &&
+ drawableBanana.DisplaySize == bananaSize &&
+ drawableBanana.AccentColour.Value == bananaColour);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.cs
new file mode 100644
index 0000000000..125e0c674c
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneFruitVisualChange.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.Bindables;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Objects.Drawables;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneFruitVisualChange : TestSceneFruitObjects
+ {
+ private readonly Bindable indexInBeatmap = new Bindable();
+ private readonly Bindable hyperDash = new Bindable();
+
+ protected override void LoadComplete()
+ {
+ AddStep("fruit changes visual and hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableFruit(new Fruit
+ {
+ IndexInBeatmapBindable = { BindTarget = indexInBeatmap },
+ HyperDashBindable = { BindTarget = hyperDash },
+ }))));
+
+ AddStep("droplet changes hyper", () => SetContents(() => new TestDrawableCatchHitObjectSpecimen(new DrawableDroplet(new Droplet
+ {
+ HyperDashBindable = { BindTarget = hyperDash },
+ }))));
+
+ Scheduler.AddDelayed(() => indexInBeatmap.Value++, 250, true);
+ Scheduler.AddDelayed(() => hyperDash.Value = !hyperDash.Value, 1000, true);
+ }
+ }
+}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
index 0a142a52f8..db09b2bc6b 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs
@@ -6,41 +6,56 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
-using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
- public class TestSceneHyperDash : PlayerTestScene
+ public class TestSceneHyperDash : TestSceneCatchPlayer
{
- public TestSceneHyperDash()
- : base(new CatchRuleset())
- {
- }
-
protected override bool Autoplay => true;
+ private int hyperDashCount;
+ private bool inHyperDash;
+
[Test]
public void TestHyperDash()
{
- AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
- AddUntilStep("wait for right movement", () => getCatcher().Scale.X > 0); // don't check hyperdashing as it happens too fast.
-
- AddUntilStep("wait for left movement", () => getCatcher().Scale.X < 0);
-
- for (int i = 0; i < 3; i++)
+ AddStep("reset count", () =>
{
- AddUntilStep("wait for right hyperdash", () => getCatcher().Scale.X > 0 && getCatcher().HyperDashing);
- AddUntilStep("wait for left hyperdash", () => getCatcher().Scale.X < 0 && getCatcher().HyperDashing);
+ inHyperDash = false;
+ hyperDashCount = 0;
+
+ // this needs to be done within the frame stable context due to how quickly hyperdash state changes occur.
+ Player.DrawableRuleset.FrameStableComponents.OnUpdate += d =>
+ {
+ var catcher = Player.ChildrenOfType().FirstOrDefault()?.MovableCatcher;
+
+ if (catcher == null)
+ return;
+
+ if (catcher.HyperDashing != inHyperDash)
+ {
+ inHyperDash = catcher.HyperDashing;
+ if (catcher.HyperDashing)
+ hyperDashCount++;
+ }
+ };
+ });
+
+ AddAssert("First note is hyperdash", () => Beatmap.Value.Beatmap.HitObjects[0] is Fruit f && f.HyperDash);
+
+ for (int i = 0; i < 9; i++)
+ {
+ int count = i + 1;
+ AddUntilStep($"wait for hyperdash #{count}", () => hyperDashCount >= count);
}
}
- private Catcher getCatcher() => Player.ChildrenOfType().First().MovableCatcher;
-
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
{
var beatmap = new Beatmap
@@ -52,14 +67,16 @@ namespace osu.Game.Rulesets.Catch.Tests
}
};
+ beatmap.ControlPointInfo.Add(0, new TimingControlPoint());
+
// Should produce a hyper-dash (edge case test)
- beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56 / 512f, NewCombo = true });
- beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308 / 512f, NewCombo = true });
+ beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true });
+ beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true });
double startTime = 3000;
- const float left_x = 0.02f;
- const float right_x = 0.98f;
+ const float left_x = 0.02f * CatchPlayfield.WIDTH;
+ const float right_x = 0.98f * CatchPlayfield.WIDTH;
createObjects(() => new Fruit { X = left_x });
createObjects(() => new TestJuiceStream(right_x), 1);
@@ -69,6 +86,20 @@ namespace osu.Game.Rulesets.Catch.Tests
createObjects(() => new Fruit { X = right_x });
createObjects(() => new TestJuiceStream(left_x), 1);
+ beatmap.ControlPointInfo.Add(startTime, new TimingControlPoint
+ {
+ BeatLength = 50
+ });
+
+ createObjects(() => new TestJuiceStream(left_x)
+ {
+ Path = new SliderPath(new[]
+ {
+ new PathControlPoint(Vector2.Zero),
+ new PathControlPoint(new Vector2(512, 0))
+ })
+ }, 1);
+
return beatmap;
void createObjects(Func createObject, int count = 3)
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
index 1e708cce4b..683a776dcc 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawables;
using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
@@ -117,13 +118,16 @@ namespace osu.Game.Rulesets.Catch.Tests
AddStep("create hyper-dashing catcher", () =>
{
- Child = setupSkinHierarchy(catcherArea = new CatcherArea
+ Child = setupSkinHierarchy(catcherArea = new CatcherArea(new Container())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(4f),
}, skin);
+ });
+ AddStep("get trails container", () =>
+ {
trails = catcherArea.OfType().Single();
catcherArea.MovableCatcher.SetHyperDashState(2);
});
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
index cbc87459e1..269e783899 100644
--- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs
@@ -5,20 +5,15 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Catch.Tests
{
- public class TestSceneJuiceStream : PlayerTestScene
+ public class TestSceneJuiceStream : TestSceneCatchPlayer
{
- public TestSceneJuiceStream()
- : base(new CatchRuleset())
- {
- }
-
[Test]
public void TestJuiceStreamEndingCombo()
{
@@ -36,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
new JuiceStream
{
- X = 0.5f,
+ X = CatchPlayfield.CENTER_X,
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
@@ -46,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests
},
new Banana
{
- X = 0.5f,
+ X = CatchPlayfield.CENTER_X,
StartTime = 1000,
NewCombo = true
}
diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs
new file mode 100644
index 0000000000..eea83ef7c1
--- /dev/null
+++ b/osu.Game.Rulesets.Catch.Tests/TestSceneLegacyBeatmapSkin.cs
@@ -0,0 +1,151 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System.Linq;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Testing;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Rulesets.Catch.Objects;
+using osu.Game.Rulesets.Catch.Skinning;
+using osu.Game.Skinning;
+using osu.Game.Tests.Beatmaps;
+using osuTK.Graphics;
+
+namespace osu.Game.Rulesets.Catch.Tests
+{
+ public class TestSceneLegacyBeatmapSkin : LegacyBeatmapSkinColourTest
+ {
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config)
+ {
+ config.BindWith(OsuSetting.BeatmapSkins, BeatmapSkins);
+ config.BindWith(OsuSetting.BeatmapColours, BeatmapColours);
+ }
+
+ [TestCase(true, true)]
+ [TestCase(true, false)]
+ [TestCase(false, true)]
+ [TestCase(false, false)]
+ public override void TestBeatmapComboColours(bool userHasCustomColours, bool useBeatmapSkin)
+ {
+ TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
+ base.TestBeatmapComboColours(userHasCustomColours, useBeatmapSkin);
+ AddAssert("is beatmap skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestBeatmapSkin.Colours));
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public override void TestBeatmapComboColoursOverride(bool useBeatmapSkin)
+ {
+ TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
+ base.TestBeatmapComboColoursOverride(useBeatmapSkin);
+ AddAssert("is user custom skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public override void TestBeatmapComboColoursOverrideWithDefaultColours(bool useBeatmapSkin)
+ {
+ TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
+ base.TestBeatmapComboColoursOverrideWithDefaultColours(useBeatmapSkin);
+ AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
+ }
+
+ [TestCase(true, true)]
+ [TestCase(false, true)]
+ [TestCase(true, false)]
+ [TestCase(false, false)]
+ public override void TestBeatmapNoComboColours(bool useBeatmapSkin, bool useBeatmapColour)
+ {
+ TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
+ base.TestBeatmapNoComboColours(useBeatmapSkin, useBeatmapColour);
+ AddAssert("is default user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(SkinConfiguration.DefaultComboColours));
+ }
+
+ [TestCase(true, true)]
+ [TestCase(false, true)]
+ [TestCase(true, false)]
+ [TestCase(false, false)]
+ public override void TestBeatmapNoComboColoursSkinOverride(bool useBeatmapSkin, bool useBeatmapColour)
+ {
+ TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, false);
+ base.TestBeatmapNoComboColoursSkinOverride(useBeatmapSkin, useBeatmapColour);
+ AddAssert("is custom user skin colours", () => TestPlayer.UsableComboColours.SequenceEqual(TestSkin.Colours));
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestBeatmapHyperDashColours(bool useBeatmapSkin)
+ {
+ TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
+ ConfigureTest(useBeatmapSkin, true, true);
+ AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestBeatmapSkin.HYPER_DASH_COLOUR);
+ AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestBeatmapSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
+ AddAssert("is custom hyper dash fruit colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashFruitColour == TestBeatmapSkin.HYPER_DASH_FRUIT_COLOUR);
+ }
+
+ [TestCase(true)]
+ [TestCase(false)]
+ public void TestBeatmapHyperDashColoursOverride(bool useBeatmapSkin)
+ {
+ TestBeatmap = new CatchCustomSkinWorkingBeatmap(audio, true);
+ ConfigureTest(useBeatmapSkin, false, true);
+ AddAssert("is custom hyper dash colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashColour == TestSkin.HYPER_DASH_COLOUR);
+ AddAssert("is custom hyper dash after image colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashAfterImageColour == TestSkin.HYPER_DASH_AFTER_IMAGE_COLOUR);
+ AddAssert("is custom hyper dash fruit colours", () => ((CatchExposedPlayer)TestPlayer).UsableHyperDashFruitColour == TestSkin.HYPER_DASH_FRUIT_COLOUR);
+ }
+
+ protected override ExposedPlayer CreateTestPlayer(bool userHasCustomColours) => new CatchExposedPlayer(userHasCustomColours);
+
+ private class CatchExposedPlayer : ExposedPlayer
+ {
+ public CatchExposedPlayer(bool userHasCustomColours)
+ : base(userHasCustomColours)
+ {
+ }
+
+ public Color4 UsableHyperDashColour =>
+ GameplayClockContainer.ChildrenOfType()
+ .First()
+ .GetConfig(new SkinCustomColourLookup(CatchSkinColour.HyperDash))?
+ .Value ?? Color4.Red;
+
+ public Color4 UsableHyperDashAfterImageColour =>
+ GameplayClockContainer.ChildrenOfType()
+ .First()
+ .GetConfig(new SkinCustomColourLookup(CatchSkinColour.HyperDashAfterImage))?
+ .Value ?? Color4.Red;
+
+ public Color4 UsableHyperDashFruitColour =>
+ GameplayClockContainer.ChildrenOfType()
+ .First()
+ .GetConfig(new SkinCustomColourLookup(CatchSkinColour.HyperDashFruit))?
+ .Value ?? Color4.Red;
+ }
+
+ private class CatchCustomSkinWorkingBeatmap : CustomSkinWorkingBeatmap
+ {
+ public CatchCustomSkinWorkingBeatmap(AudioManager audio, bool hasColours)
+ : base(createBeatmap(), audio, hasColours)
+ {
+ }
+
+ private static IBeatmap createBeatmap() =>
+ new Beatmap
+ {
+ BeatmapInfo =
+ {
+ BeatmapSet = new BeatmapSetInfo(),
+ Ruleset = new CatchRuleset().RulesetInfo
+ },
+ HitObjects = { new Fruit { StartTime = 1816, X = 56, NewCombo = true } }
+ };
+ }
+ }
+}
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 cbd3dc5518..c2d9a923d9 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
- netcoreapp3.1
+ net5.0
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
index 18cc300ff9..f009c10a9c 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmap.cs
@@ -3,7 +3,6 @@
using System.Collections.Generic;
using System.Linq;
-using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
@@ -23,19 +22,19 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
Name = @"Fruit Count",
Content = fruits.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
},
new BeatmapStatistic
{
Name = @"Juice Stream Count",
Content = juiceStreams.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
},
new BeatmapStatistic
{
Name = @"Banana Shower Count",
Content = bananaShowers.ToString(),
- Icon = FontAwesome.Regular.Circle
+ CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
}
};
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
index 0de2060e2d..34964fc4ae 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs
@@ -5,7 +5,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
using System.Linq;
-using osu.Game.Rulesets.Catch.UI;
+using System.Threading;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
using osu.Framework.Extensions.IEnumerableExtensions;
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
- protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap)
+ protected override IEnumerable ConvertHitObject(HitObject obj, IBeatmap beatmap, CancellationToken cancellationToken)
{
var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo;
@@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Path = curveData.Path,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
- X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
+ X = positionData?.X ?? 0,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
@@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
- X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
+ X = positionData?.X ?? 0
}.Yield();
}
}
diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
index 7c81bcdf0c..fac5d03833 100644
--- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
+++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs
@@ -5,11 +5,11 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
+using osu.Game.Rulesets.Catch.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
-using osu.Game.Rulesets.Objects.Types;
-using osu.Game.Rulesets.Catch.MathUtils;
using osu.Game.Rulesets.Mods;
+using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case BananaShower bananaShower:
foreach (var banana in bananaShower.NestedHitObjects.OfType())
{
- banana.XOffset = (float)rng.NextDouble();
+ banana.XOffset = (float)(rng.NextDouble() * CatchPlayfield.WIDTH);
rng.Next(); // osu!stable retrieved a random banana type
rng.Next(); // osu!stable retrieved a random banana rotation
rng.Next(); // osu!stable retrieved a random banana colour
@@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
case JuiceStream juiceStream:
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
- lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X / CatchPlayfield.BASE_WIDTH;
+ lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X;
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
lastStartTime = juiceStream.StartTime;
@@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
catchObject.XOffset = 0;
if (catchObject is TinyDroplet)
- catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
+ catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.OriginalX, CatchPlayfield.WIDTH - catchObject.OriginalX);
else if (catchObject is Droplet)
rng.Next(); // osu!stable retrieved a random droplet rotation
}
@@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private static void applyHardRockOffset(CatchHitObject hitObject, ref float? lastPosition, ref double lastStartTime, FastRandom rng)
{
- float offsetPosition = hitObject.X;
+ float offsetPosition = hitObject.OriginalX;
double startTime = hitObject.StartTime;
if (lastPosition == null)
@@ -126,15 +126,15 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (positionDiff == 0)
{
applyRandomOffset(ref offsetPosition, timeDiff / 4d, rng);
- hitObject.XOffset = offsetPosition - hitObject.X;
+ hitObject.XOffset = offsetPosition - hitObject.OriginalX;
return;
}
// ReSharper disable once PossibleLossOfFraction
- if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3)
+ if (Math.Abs(positionDiff) < timeDiff / 3)
applyOffset(ref offsetPosition, positionDiff);
- hitObject.XOffset = offsetPosition - hitObject.X;
+ hitObject.XOffset = offsetPosition - hitObject.OriginalX;
lastPosition = offsetPosition;
lastStartTime = startTime;
@@ -149,12 +149,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
{
bool right = rng.NextBool();
- float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
+ float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset)));
if (right)
{
// Clamp to the right bound
- if (position + rand <= 1)
+ if (position + rand <= CatchPlayfield.WIDTH)
position += rand;
else
position -= rand;
@@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
if (amount > 0)
{
// Clamp to the right bound
- if (position + amount < 1)
+ if (position + amount < CatchPlayfield.WIDTH)
position += amount;
}
else
@@ -192,41 +192,47 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
private static void initialiseHyperDash(IBeatmap beatmap)
{
- List objectWithDroplets = new List();
+ List palpableObjects = new List();
foreach (var currentObject in beatmap.HitObjects)
{
if (currentObject is Fruit fruitObject)
- objectWithDroplets.Add(fruitObject);
+ palpableObjects.Add(fruitObject);
if (currentObject is JuiceStream)
{
- foreach (var currentJuiceElement in currentObject.NestedHitObjects)
+ foreach (var juice in currentObject.NestedHitObjects)
{
- if (!(currentJuiceElement is TinyDroplet))
- objectWithDroplets.Add((CatchHitObject)currentJuiceElement);
+ if (juice is PalpableCatchHitObject palpableObject && !(juice is TinyDroplet))
+ palpableObjects.Add(palpableObject);
}
}
}
- objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
+ palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
+
+ double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
+
+ // Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins.
+ // This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible.
+ // For now, to bring gameplay (and diffcalc!) completely in-line with stable, this code also uses the full catcher size.
+ halfCatcherWidth /= Catcher.ALLOWED_CATCH_RANGE;
- double halfCatcherWidth = CatcherArea.GetCatcherSize(beatmap.BeatmapInfo.BaseDifficulty) / 2;
int lastDirection = 0;
double lastExcess = halfCatcherWidth;
- for (int i = 0; i < objectWithDroplets.Count - 1; i++)
+ for (int i = 0; i < palpableObjects.Count - 1; i++)
{
- CatchHitObject currentObject = objectWithDroplets[i];
- CatchHitObject nextObject = objectWithDroplets[i + 1];
+ var currentObject = palpableObjects[i];
+ var nextObject = palpableObjects[i + 1];
// Reset variables in-case values have changed (e.g. after applying HR)
currentObject.HyperDashTarget = null;
currentObject.DistanceToHyperDash = 0;
- int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
+ int thisDirection = nextObject.EffectiveX > currentObject.EffectiveX ? 1 : -1;
double timeToNext = nextObject.StartTime - currentObject.StartTime - 1000f / 60f / 4; // 1/4th of a frame of grace time, taken from osu-stable
- double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
+ double distanceToNext = Math.Abs(nextObject.EffectiveX - currentObject.EffectiveX) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
float distanceToHyper = (float)(timeToNext * Catcher.BASE_SPEED - distanceToNext);
if (distanceToHyper < 0)
diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs
index ca75a816f1..f4ddbd3021 100644
--- a/osu.Game.Rulesets.Catch/CatchRuleset.cs
+++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs
@@ -21,7 +21,8 @@ using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using System;
-using osu.Game.Rulesets.Catch.Skinning;
+using osu.Framework.Extensions.EnumExtensions;
+using osu.Game.Rulesets.Catch.Skinning.Legacy;
using osu.Game.Skinning;
namespace osu.Game.Rulesets.Catch
@@ -50,40 +51,40 @@ namespace osu.Game.Rulesets.Catch
public override IEnumerable ConvertFromLegacyMods(LegacyMods mods)
{
- if (mods.HasFlag(LegacyMods.Nightcore))
+ if (mods.HasFlagFast(LegacyMods.Nightcore))
yield return new CatchModNightcore();
- else if (mods.HasFlag(LegacyMods.DoubleTime))
+ else if (mods.HasFlagFast(LegacyMods.DoubleTime))
yield return new CatchModDoubleTime();
- if (mods.HasFlag(LegacyMods.Perfect))
+ if (mods.HasFlagFast(LegacyMods.Perfect))
yield return new CatchModPerfect();
- else if (mods.HasFlag(LegacyMods.SuddenDeath))
+ else if (mods.HasFlagFast(LegacyMods.SuddenDeath))
yield return new CatchModSuddenDeath();
- if (mods.HasFlag(LegacyMods.Cinema))
+ if (mods.HasFlagFast(LegacyMods.Cinema))
yield return new CatchModCinema();
- else if (mods.HasFlag(LegacyMods.Autoplay))
+ else if (mods.HasFlagFast(LegacyMods.Autoplay))
yield return new CatchModAutoplay();
- if (mods.HasFlag(LegacyMods.Easy))
+ if (mods.HasFlagFast(LegacyMods.Easy))
yield return new CatchModEasy();
- if (mods.HasFlag(LegacyMods.Flashlight))
+ if (mods.HasFlagFast(LegacyMods.Flashlight))
yield return new CatchModFlashlight();
- if (mods.HasFlag(LegacyMods.HalfTime))
+ if (mods.HasFlagFast(LegacyMods.HalfTime))
yield return new CatchModHalfTime();
- if (mods.HasFlag(LegacyMods.HardRock))
+ if (mods.HasFlagFast(LegacyMods.HardRock))
yield return new CatchModHardRock();
- if (mods.HasFlag(LegacyMods.Hidden))
+ if (mods.HasFlagFast(LegacyMods.Hidden))
yield return new CatchModHidden();
- if (mods.HasFlag(LegacyMods.NoFail))
+ if (mods.HasFlagFast(LegacyMods.NoFail))
yield return new CatchModNoFail();
- if (mods.HasFlag(LegacyMods.Relax))
+ if (mods.HasFlagFast(LegacyMods.Relax))
yield return new CatchModRelax();
}
@@ -141,11 +142,40 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
+ protected override IEnumerable