mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 07:42:57 +08:00
Merge branch 'master' into carousel
This commit is contained in:
commit
942e7729f3
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
@ -121,21 +121,12 @@ jobs:
|
||||
|
||||
build-only-ios:
|
||||
name: Build only (iOS)
|
||||
# change to macos-latest once GitHub finishes migrating all repositories to macOS 12.
|
||||
runs-on: macos-12
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# see https://github.com/actions/runner-images/issues/6771#issuecomment-1354713617
|
||||
# remove once all workflow VMs use Xcode 14.1
|
||||
- name: Set Xcode Version
|
||||
shell: bash
|
||||
run: |
|
||||
sudo xcode-select -s "/Applications/Xcode_14.1.app"
|
||||
echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.1.app" >> $GITHUB_ENV
|
||||
|
||||
- name: Install .NET 6.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
|
6
Gemfile
6
Gemfile
@ -1,6 +0,0 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
||||
|
||||
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
234
Gemfile.lock
234
Gemfile.lock
@ -1,234 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
CFPropertyList (3.0.5)
|
||||
rexml
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
artifactory (3.0.15)
|
||||
atomos (0.1.3)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.653.0)
|
||||
aws-sdk-core (3.166.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
jmespath (~> 1, >= 1.6.1)
|
||||
aws-sdk-kms (1.59.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.117.1)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
aws-sigv4 (1.5.2)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
babosa (1.0.4)
|
||||
claide (1.1.0)
|
||||
colored (1.2)
|
||||
colored2 (3.1.2)
|
||||
commander (4.6.0)
|
||||
highline (~> 2.0.0)
|
||||
declarative (0.0.20)
|
||||
digest-crc (0.6.4)
|
||||
rake (>= 12.0.0, < 14.0.0)
|
||||
domain_name (0.5.20190701)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
dotenv (2.8.1)
|
||||
emoji_regex (3.2.3)
|
||||
excon (0.93.1)
|
||||
faraday (1.10.2)
|
||||
faraday-em_http (~> 1.0)
|
||||
faraday-em_synchrony (~> 1.0)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-httpclient (~> 1.0)
|
||||
faraday-multipart (~> 1.0)
|
||||
faraday-net_http (~> 1.0)
|
||||
faraday-net_http_persistent (~> 1.0)
|
||||
faraday-patron (~> 1.0)
|
||||
faraday-rack (~> 1.0)
|
||||
faraday-retry (~> 1.0)
|
||||
ruby2_keywords (>= 0.0.4)
|
||||
faraday-cookie_jar (0.0.7)
|
||||
faraday (>= 0.8.0)
|
||||
http-cookie (~> 1.0.0)
|
||||
faraday-em_http (1.0.0)
|
||||
faraday-em_synchrony (1.0.0)
|
||||
faraday-excon (1.1.0)
|
||||
faraday-httpclient (1.0.1)
|
||||
faraday-multipart (1.0.4)
|
||||
multipart-post (~> 2)
|
||||
faraday-net_http (1.0.1)
|
||||
faraday-net_http_persistent (1.2.0)
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fastimage (2.2.6)
|
||||
fastlane (2.210.1)
|
||||
CFPropertyList (>= 2.3, < 4.0.0)
|
||||
addressable (>= 2.8, < 3.0.0)
|
||||
artifactory (~> 3.0)
|
||||
aws-sdk-s3 (~> 1.0)
|
||||
babosa (>= 1.0.3, < 2.0.0)
|
||||
bundler (>= 1.12.0, < 3.0.0)
|
||||
colored
|
||||
commander (~> 4.6)
|
||||
dotenv (>= 2.1.1, < 3.0.0)
|
||||
emoji_regex (>= 0.1, < 4.0)
|
||||
excon (>= 0.71.0, < 1.0.0)
|
||||
faraday (~> 1.0)
|
||||
faraday-cookie_jar (~> 0.0.6)
|
||||
faraday_middleware (~> 1.0)
|
||||
fastimage (>= 2.1.0, < 3.0.0)
|
||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||
google-apis-androidpublisher_v3 (~> 0.3)
|
||||
google-apis-playcustomapp_v1 (~> 0.1)
|
||||
google-cloud-storage (~> 1.31)
|
||||
highline (~> 2.0)
|
||||
json (< 3.0.0)
|
||||
jwt (>= 2.1.0, < 3)
|
||||
mini_magick (>= 4.9.4, < 5.0.0)
|
||||
multipart-post (~> 2.0.0)
|
||||
naturally (~> 2.2)
|
||||
optparse (~> 0.1.1)
|
||||
plist (>= 3.1.0, < 4.0.0)
|
||||
rubyzip (>= 2.0.0, < 3.0.0)
|
||||
security (= 0.1.3)
|
||||
simctl (~> 1.6.3)
|
||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||
terminal-table (>= 1.4.5, < 2.0.0)
|
||||
tty-screen (>= 0.6.3, < 1.0.0)
|
||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||
word_wrap (~> 1.0.0)
|
||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||
xcpretty (~> 0.3.0)
|
||||
xcpretty-travis-formatter (>= 0.0.3)
|
||||
fastlane-plugin-clean_testflight_testers (0.3.0)
|
||||
fastlane-plugin-souyuz (0.11.1)
|
||||
souyuz (= 0.11.1)
|
||||
fastlane-plugin-xamarin (0.6.3)
|
||||
gh_inspector (1.1.3)
|
||||
google-apis-androidpublisher_v3 (0.29.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-core (0.9.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
httpclient (>= 2.8.1, < 3.a)
|
||||
mini_mime (~> 1.0)
|
||||
representable (~> 3.0)
|
||||
retriable (>= 2.0, < 4.a)
|
||||
rexml
|
||||
webrick
|
||||
google-apis-iamcredentials_v1 (0.15.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-apis-playcustomapp_v1 (0.12.0)
|
||||
google-apis-core (>= 0.9.1, < 2.a)
|
||||
google-apis-storage_v1 (0.19.0)
|
||||
google-apis-core (>= 0.9.0, < 2.a)
|
||||
google-cloud-core (1.6.0)
|
||||
google-cloud-env (~> 1.0)
|
||||
google-cloud-errors (~> 1.0)
|
||||
google-cloud-env (1.6.0)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
google-cloud-errors (1.3.0)
|
||||
google-cloud-storage (1.43.0)
|
||||
addressable (~> 2.8)
|
||||
digest-crc (~> 0.4)
|
||||
google-apis-iamcredentials_v1 (~> 0.1)
|
||||
google-apis-storage_v1 (~> 0.19.0)
|
||||
google-cloud-core (~> 1.6)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
mini_mime (~> 1.0)
|
||||
googleauth (1.3.0)
|
||||
faraday (>= 0.17.3, < 3.a)
|
||||
jwt (>= 1.4, < 3.0)
|
||||
memoist (~> 0.16)
|
||||
multi_json (~> 1.11)
|
||||
os (>= 0.9, < 2.0)
|
||||
signet (>= 0.16, < 2.a)
|
||||
highline (2.0.3)
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
httpclient (2.8.3)
|
||||
jmespath (1.6.1)
|
||||
json (2.6.2)
|
||||
jwt (2.5.0)
|
||||
memoist (0.16.2)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.0.0)
|
||||
nanaimo (0.3.0)
|
||||
naturally (2.2.1)
|
||||
nokogiri (1.13.9)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
optparse (0.1.1)
|
||||
os (1.1.4)
|
||||
plist (3.6.0)
|
||||
public_suffix (5.0.0)
|
||||
racc (1.6.0)
|
||||
rake (13.0.6)
|
||||
representable (3.2.0)
|
||||
declarative (< 0.1.0)
|
||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||
uber (< 0.2.0)
|
||||
retriable (3.1.2)
|
||||
rexml (3.2.5)
|
||||
rouge (2.0.7)
|
||||
ruby2_keywords (0.0.5)
|
||||
rubyzip (2.3.2)
|
||||
security (0.1.3)
|
||||
signet (0.17.0)
|
||||
addressable (~> 2.8)
|
||||
faraday (>= 0.17.5, < 3.a)
|
||||
jwt (>= 1.5, < 3.0)
|
||||
multi_json (~> 1.10)
|
||||
simctl (1.6.8)
|
||||
CFPropertyList
|
||||
naturally
|
||||
souyuz (0.11.1)
|
||||
fastlane (>= 2.182.0)
|
||||
highline (~> 2.0)
|
||||
nokogiri (~> 1.7)
|
||||
terminal-notifier (2.0.0)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
trailblazer-option (0.1.2)
|
||||
tty-cursor (0.7.1)
|
||||
tty-screen (0.8.1)
|
||||
tty-spinner (0.9.3)
|
||||
tty-cursor (~> 0.7)
|
||||
uber (0.1.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.8.2)
|
||||
unicode-display_width (1.8.0)
|
||||
webrick (1.7.0)
|
||||
word_wrap (1.0.0)
|
||||
xcodeproj (1.22.0)
|
||||
CFPropertyList (>= 2.3.3, < 4.0)
|
||||
atomos (~> 0.1.3)
|
||||
claide (>= 1.0.2, < 2.0)
|
||||
colored2 (~> 3.1)
|
||||
nanaimo (~> 0.3.0)
|
||||
rexml (~> 3.2.4)
|
||||
xcpretty (0.3.0)
|
||||
rouge (~> 2.0.7)
|
||||
xcpretty-travis-formatter (1.0.1)
|
||||
xcpretty (~> 0.2, >= 0.0.7)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
fastlane
|
||||
fastlane-plugin-clean_testflight_testers
|
||||
fastlane-plugin-souyuz
|
||||
fastlane-plugin-xamarin
|
||||
|
||||
BUNDLED WITH
|
||||
2.0.1
|
@ -1,2 +0,0 @@
|
||||
app_identifier("sh.ppy.osulazer") # The bundle identifier of your app
|
||||
apple_id("apple-dev@ppy.sh") # Your Apple email address
|
@ -1,147 +0,0 @@
|
||||
update_fastlane
|
||||
|
||||
platform :android do
|
||||
desc 'Deploy to play store'
|
||||
lane :beta do |options|
|
||||
|
||||
update_version(
|
||||
version: options[:version],
|
||||
build: options[:build],
|
||||
)
|
||||
|
||||
build(options)
|
||||
|
||||
supply(
|
||||
apk: './osu.Android/bin/Release/sh.ppy.osulazer-Signed.apk',
|
||||
package_name: 'sh.ppy.osulazer',
|
||||
track: 'alpha', # upload to alpha, we can promote it later
|
||||
json_key: options[:json_key],
|
||||
)
|
||||
end
|
||||
|
||||
desc 'Deploy to github release'
|
||||
lane :build_github do |options|
|
||||
|
||||
update_version(
|
||||
version: options[:version],
|
||||
build: options[:build],
|
||||
)
|
||||
|
||||
build(options)
|
||||
|
||||
client = HTTPClient.new
|
||||
changelog = client.get_content 'https://gist.githubusercontent.com/peppy/aaa2ec1a323554b619671cac6dbbb776/raw'
|
||||
changelog.gsub!('$BUILD_ID', options[:build])
|
||||
|
||||
set_github_release(
|
||||
repository_name: "ppy/osu",
|
||||
api_token: ENV["GITHUB_TOKEN"],
|
||||
name: options[:build],
|
||||
tag_name: options[:build],
|
||||
is_draft: true,
|
||||
description: changelog,
|
||||
commitish: "master",
|
||||
upload_assets: ["osu.Android/bin/Release/sh.ppy.osulazer.apk"]
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
desc 'Compile the project'
|
||||
lane :build do |options|
|
||||
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',
|
||||
solution_path: 'osu.sln',
|
||||
platform: "android",
|
||||
output_path: "osu.Android/bin/Release/",
|
||||
keystore_path: options[:keystore_path],
|
||||
keystore_alias: options[:keystore_alias],
|
||||
keystore_password: ENV["KEYSTORE_PASSWORD"]
|
||||
)
|
||||
end
|
||||
|
||||
lane :update_version do |options|
|
||||
|
||||
split = options[:build].split('.')
|
||||
split[1] = split[1].to_s.rjust(4, '0')
|
||||
android_build = split.join('')
|
||||
|
||||
app_version(
|
||||
solution_path: 'osu.sln',
|
||||
version: options[:version],
|
||||
build: android_build,
|
||||
)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
platform :ios do
|
||||
desc 'Deploy to testflight'
|
||||
lane :beta do |options|
|
||||
update_version(options)
|
||||
|
||||
provision(
|
||||
type: 'appstore'
|
||||
)
|
||||
|
||||
build(
|
||||
build_configuration: 'Release',
|
||||
build_platform: 'iPhone'
|
||||
)
|
||||
|
||||
client = HTTPClient.new
|
||||
changelog = client.get_content 'https://gist.githubusercontent.com/peppy/ab89c29dcc0dce95f39eb218e8fad197/raw'
|
||||
changelog.gsub!('$BUILD_ID', options[:build])
|
||||
|
||||
pilot(
|
||||
wait_processing_interval: 900,
|
||||
changelog: changelog,
|
||||
groups: ['osu! supporters', 'public'],
|
||||
distribute_external: true,
|
||||
ipa: './osu.iOS/bin/iPhone/Release/osu.iOS.ipa'
|
||||
)
|
||||
end
|
||||
|
||||
desc 'Compile the project'
|
||||
lane :build do
|
||||
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"
|
||||
)
|
||||
end
|
||||
|
||||
desc 'Install provisioning profiles using match'
|
||||
lane :provision do |options|
|
||||
if Helper.is_ci?
|
||||
options[:readonly] = true
|
||||
end
|
||||
|
||||
match(options)
|
||||
end
|
||||
|
||||
lane :update_version do |options|
|
||||
options[:plist_path] = 'osu.iOS/Info.plist'
|
||||
app_version(options)
|
||||
end
|
||||
|
||||
lane :testflight_prune_dry do
|
||||
clean_testflight_testers(days_of_inactivity:30, dry_run: true)
|
||||
end
|
||||
|
||||
lane :testflight_prune do
|
||||
clean_testflight_testers(days_of_inactivity: 30)
|
||||
end
|
||||
end
|
@ -1 +0,0 @@
|
||||
git_url('https://github.com/peppy/apple-certificates')
|
@ -1,7 +0,0 @@
|
||||
# Autogenerated by fastlane
|
||||
#
|
||||
# Ensure this file is checked in to source control!
|
||||
|
||||
gem 'fastlane-plugin-clean_testflight_testers'
|
||||
gem 'fastlane-plugin-souyuz'
|
||||
gem 'fastlane-plugin-xamarin'
|
@ -1,109 +0,0 @@
|
||||
fastlane documentation
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android beta
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android beta
|
||||
```
|
||||
|
||||
Deploy to play store
|
||||
|
||||
### android build_github
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android build_github
|
||||
```
|
||||
|
||||
Deploy to github release
|
||||
|
||||
### android build
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android build
|
||||
```
|
||||
|
||||
Compile the project
|
||||
|
||||
### android update_version
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android update_version
|
||||
```
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## iOS
|
||||
|
||||
### ios beta
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios beta
|
||||
```
|
||||
|
||||
Deploy to testflight
|
||||
|
||||
### ios build
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios build
|
||||
```
|
||||
|
||||
Compile the project
|
||||
|
||||
### ios provision
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios provision
|
||||
```
|
||||
|
||||
Install provisioning profiles using match
|
||||
|
||||
### ios update_version
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios update_version
|
||||
```
|
||||
|
||||
|
||||
|
||||
### ios testflight_prune_dry
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios testflight_prune_dry
|
||||
```
|
||||
|
||||
|
||||
|
||||
### ios testflight_prune
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane ios testflight_prune
|
||||
```
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.1226.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.131.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -113,6 +113,7 @@ namespace osu.Game.Rulesets.Catch
|
||||
new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
|
||||
new CatchModHidden(),
|
||||
new CatchModFlashlight(),
|
||||
new ModAccuracyChallenge(),
|
||||
};
|
||||
|
||||
case ModType.Conversion:
|
||||
|
@ -0,0 +1,30 @@
|
||||
// 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.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
public partial class TestScenePlacementBeforeTrackStart : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestPlacement()
|
||||
{
|
||||
AddStep("Seek to 0", () => EditorClock.Seek(0));
|
||||
AddStep("Select note", () => InputManager.Key(Key.Number2));
|
||||
AddStep("Hover negative span", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
|
||||
});
|
||||
AddStep("Click", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// 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.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModFadeIn : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(0.1f)]
|
||||
[TestCase(0.7f)]
|
||||
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModFadeIn { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
// 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.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneManiaModHidden : ModTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(0.2f)]
|
||||
[TestCase(0.8f)]
|
||||
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModHidden { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 99 KiB |
@ -14,4 +14,6 @@ Hit200: mania/hit200@2x
|
||||
Hit300: mania/hit300@2x
|
||||
Hit300g: mania/hit300g@2x
|
||||
StageLeft: mania/stage-left
|
||||
StageRight: mania/stage-right
|
||||
StageRight: mania/stage-right
|
||||
NoteImage0L: LongNoteTailWang
|
||||
NoteImage1L: LongNoteTailWang
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
@ -30,8 +31,8 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime,
|
||||
scrollTime => new SettingDescription(
|
||||
rawValue: scrollTime,
|
||||
name: "Scroll Speed",
|
||||
value: $"{scrollTime}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime)})"
|
||||
name: RulesetSettingsStrings.ScrollSpeed,
|
||||
value: RulesetSettingsStrings.ScrollSpeedTooltip(scrollTime, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime))
|
||||
)
|
||||
)
|
||||
};
|
||||
|
@ -245,6 +245,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
|
||||
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
|
||||
new ManiaModFlashlight(),
|
||||
new ModAccuracyChallenge(),
|
||||
};
|
||||
|
||||
case ModType.Conversion:
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
@ -30,18 +31,18 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
new SettingsEnumDropdown<ManiaScrollingDirection>
|
||||
{
|
||||
LabelText = "Scrolling direction",
|
||||
LabelText = RulesetSettingsStrings.ScrollingDirection,
|
||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
},
|
||||
new SettingsSlider<double, ManiaScrollSlider>
|
||||
{
|
||||
LabelText = "Scroll speed",
|
||||
LabelText = RulesetSettingsStrings.ScrollSpeed,
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
|
||||
KeyboardStep = 5
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Timing-based note colouring",
|
||||
LabelText = RulesetSettingsStrings.TimingBasedColouring,
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
||||
}
|
||||
};
|
||||
@ -49,7 +50,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
private partial class ManiaScrollSlider : OsuSliderBar<double>
|
||||
{
|
||||
public override LocalisableString TooltipText => $"{Current.Value}ms (speed {(int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value)})";
|
||||
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip(Current.Value, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
@ -18,5 +19,13 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
|
||||
|
||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
|
||||
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0.1f,
|
||||
MaxValue = 0.7f,
|
||||
Default = 0.5f,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
@ -13,6 +14,14 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0.2f,
|
||||
MaxValue = 0.8f,
|
||||
Default = 0.5f,
|
||||
};
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
|
||||
|
||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
|
||||
|
@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
/// </summary>
|
||||
protected abstract CoverExpandDirection ExpandDirection { get; }
|
||||
|
||||
[SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")]
|
||||
public abstract BindableNumber<float> Coverage { get; }
|
||||
|
||||
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||
{
|
||||
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
|
||||
@ -36,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
c.RelativeSizeAxes = Axes.Both;
|
||||
c.Direction = ExpandDirection;
|
||||
c.Coverage = 0.5f;
|
||||
c.Coverage = Coverage.Value;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
@ -376,7 +376,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
protected override void OnFree()
|
||||
{
|
||||
slidingSample.Samples = null;
|
||||
slidingSample.ClearSamples();
|
||||
base.OnFree();
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,11 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
@ -19,8 +19,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
private readonly Box colouredBox;
|
||||
private readonly Box shadow;
|
||||
private readonly Box shadeBackground;
|
||||
private readonly Box shadeForeground;
|
||||
|
||||
public ArgonHoldNoteTailPiece()
|
||||
{
|
||||
@ -32,32 +32,25 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
shadow = new Box
|
||||
shadeBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.82f,
|
||||
Masking = true,
|
||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
colouredBox = new Box
|
||||
shadeForeground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = ArgonNotePiece.CORNER_RADIUS * 2,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -77,19 +70,13 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
|
||||
? Anchor.TopCentre
|
||||
: Anchor.BottomCentre;
|
||||
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
||||
}
|
||||
|
||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||
{
|
||||
colouredBox.Colour = ColourInfo.GradientVertical(
|
||||
accent.NewValue,
|
||||
accent.NewValue.Darken(0.1f)
|
||||
);
|
||||
|
||||
shadow.Colour = accent.NewValue.Darken(0.5f);
|
||||
shadeBackground.Colour = accent.NewValue.Darken(1.7f);
|
||||
shadeForeground.Colour = accent.NewValue.Darken(1.1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
internal partial class ArgonNotePiece : CompositeDrawable
|
||||
{
|
||||
public const float NOTE_HEIGHT = 42;
|
||||
|
||||
public const float NOTE_ACCENT_RATIO = 0.82f;
|
||||
public const float CORNER_RADIUS = 3.4f;
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = 0.82f,
|
||||
Height = NOTE_ACCENT_RATIO,
|
||||
Masking = true,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
Children = new Drawable[]
|
||||
@ -95,6 +95,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
colouredBox.Anchor = colouredBox.Origin = direction.NewValue == ScrollingDirection.Up
|
||||
? Anchor.TopCentre
|
||||
: Anchor.BottomCentre;
|
||||
|
||||
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
||||
}
|
||||
|
||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||
|
@ -54,6 +54,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
float lightScale = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
|
||||
?? 1;
|
||||
|
||||
float minimumColumnWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.MinimumColumnWidth)?.Value
|
||||
?? 1;
|
||||
|
||||
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
|
||||
// This animation is discarded and re-queried with the appropriate frame length afterwards.
|
||||
var tmp = skin.GetAnimation(lightImage, true, false);
|
||||
@ -92,7 +95,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
d.Size = Vector2.One;
|
||||
d.FillMode = FillMode.Stretch;
|
||||
// Todo: Wrap
|
||||
d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; // constant matching stable.
|
||||
// Todo: Wrap?
|
||||
});
|
||||
|
||||
if (bodySprite != null)
|
||||
|
@ -1,15 +1,18 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
public enum ManiaScrollingDirection
|
||||
{
|
||||
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionUp))]
|
||||
Up = ScrollingDirection.Up,
|
||||
|
||||
[LocalisableDescription(typeof(RulesetSettingsStrings), nameof(RulesetSettingsStrings.ScrollingDirectionDown))]
|
||||
Down = ScrollingDirection.Down
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
public partial class TestScenePathControlPointVisualiser : OsuManualInputManagerTestScene
|
||||
{
|
||||
private Slider slider;
|
||||
private PathControlPointVisualiser visualiser;
|
||||
private PathControlPointVisualiser<Slider> visualiser;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
assertControlPointPathType(3, null);
|
||||
}
|
||||
|
||||
private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser(slider, allowSelection)
|
||||
private void createVisualiser(bool allowSelection) => AddStep("create visualiser", () => Child = visualiser = new PathControlPointVisualiser<Slider>(slider, allowSelection)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
|
@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
}
|
||||
|
||||
private void assertSelectionCount(int count) =>
|
||||
AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == count);
|
||||
AddAssert($"{count} control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == count);
|
||||
|
||||
private void assertSelected(int index) =>
|
||||
AddAssert($"{(index + 1).ToOrdinalWords()} control point piece selected",
|
||||
() => this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
|
||||
() => this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[index]).IsSelected.Value);
|
||||
|
||||
private void moveMouseToRelativePosition(Vector2 relativePosition) =>
|
||||
AddStep($"move mouse to {relativePosition}", () =>
|
||||
@ -202,12 +202,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
moveMouseToControlPoint(2);
|
||||
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
|
||||
addMovementStep(new Vector2(450, 50));
|
||||
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
|
||||
assertControlPointPosition(2, new Vector2(450, 50));
|
||||
assertControlPointType(2, PathType.PerfectCurve);
|
||||
@ -236,12 +236,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
moveMouseToControlPoint(3);
|
||||
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
|
||||
addMovementStep(new Vector2(550, 50));
|
||||
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
AddAssert("three control point pieces selected", () => this.ChildrenOfType<PathControlPointPiece<Slider>>().Count(piece => piece.IsSelected.Value) == 3);
|
||||
|
||||
// note: if the head is part of the selection being moved, the entire slider is moved.
|
||||
// the unselected nodes will therefore change position relative to the slider head.
|
||||
@ -354,7 +354,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
|
||||
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
|
||||
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||
public new PathControlPointVisualiser<Slider> ControlPointVisualiser => base.ControlPointVisualiser;
|
||||
|
||||
public TestSliderBlueprint(Slider slider)
|
||||
: base(slider)
|
||||
|
@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||
public new TestSliderCircleOverlay HeadOverlay => (TestSliderCircleOverlay)base.HeadOverlay;
|
||||
public new TestSliderCircleOverlay TailOverlay => (TestSliderCircleOverlay)base.TailOverlay;
|
||||
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||
public new PathControlPointVisualiser<Slider> ControlPointVisualiser => base.ControlPointVisualiser;
|
||||
|
||||
public TestSliderBlueprint(Slider slider)
|
||||
: base(slider)
|
||||
|
@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
[Test]
|
||||
public void TestMovingUnsnappedSliderNodesSnaps()
|
||||
{
|
||||
PathControlPointPiece sliderEnd = null;
|
||||
PathControlPointPiece<Slider> sliderEnd = null;
|
||||
|
||||
assertSliderSnapped(false);
|
||||
|
||||
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||
AddStep("select slider end", () =>
|
||||
{
|
||||
sliderEnd = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
|
||||
sliderEnd = this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints.Last());
|
||||
InputManager.MoveMouseTo(sliderEnd.ScreenSpaceDrawQuad.Centre);
|
||||
});
|
||||
AddStep("move slider end", () =>
|
||||
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||
AddStep("move mouse to new point location", () =>
|
||||
{
|
||||
var firstPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
|
||||
var firstPiece = this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[0]);
|
||||
var pos = slider.Path.PositionAt(0.25d) + slider.Position;
|
||||
InputManager.MoveMouseTo(firstPiece.Parent.ToScreenSpace(pos));
|
||||
});
|
||||
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider));
|
||||
AddStep("move mouse to second control point", () =>
|
||||
{
|
||||
var secondPiece = this.ChildrenOfType<PathControlPointPiece>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
|
||||
var secondPiece = this.ChildrenOfType<PathControlPointPiece<Slider>>().Single(piece => piece.ControlPoint == slider.Path.ControlPoints[1]);
|
||||
InputManager.MoveMouseTo(secondPiece);
|
||||
});
|
||||
AddStep("quick delete", () =>
|
||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
=> Editor.ChildrenOfType<ComposeBlueprintContainer>().First();
|
||||
|
||||
private Slider? slider;
|
||||
private PathControlPointVisualiser? visualiser;
|
||||
private PathControlPointVisualiser<Slider>? visualiser;
|
||||
|
||||
private const double split_gap = 100;
|
||||
|
||||
@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep("select added slider", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
|
||||
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser<Slider>>().First();
|
||||
});
|
||||
|
||||
moveMouseToControlPoint(2);
|
||||
@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep("select added slider", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
|
||||
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser<Slider>>().First();
|
||||
});
|
||||
|
||||
moveMouseToControlPoint(2);
|
||||
@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
AddStep("select added slider", () =>
|
||||
{
|
||||
EditorBeatmap.SelectedHitObjects.Add(slider);
|
||||
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser>().First();
|
||||
visualiser = blueprintContainer.SelectionBlueprints.First(o => o.Item == slider).ChildrenOfType<PathControlPointVisualiser<Slider>>().First();
|
||||
});
|
||||
|
||||
moveMouseToControlPoint(2);
|
||||
|
@ -5,9 +5,11 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
@ -22,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
private int depthIndex;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
||||
[Test]
|
||||
public void TestHits()
|
||||
{
|
||||
@ -56,6 +61,13 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddStep("Hit stream late", () => SetContents(_ => testStream(5, true, 150)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitLighting()
|
||||
{
|
||||
AddToggleStep("toggle hit lighting", v => config.SetValue(OsuSetting.HitLighting, v));
|
||||
AddStep("Hit Big Single", () => SetContents(_ => testSingle(2, true)));
|
||||
}
|
||||
|
||||
private Drawable testSingle(float circleSize, bool auto = false, double timeOffset = 0, Vector2? positionOffset = null)
|
||||
{
|
||||
var playfield = new TestOsuPlayfield();
|
||||
|
@ -4,29 +4,38 @@
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneHitCircleKiai : TestSceneHitCircle
|
||||
public partial class TestSceneHitCircleKiai : TestSceneHitCircle, IBeatSyncProvider
|
||||
{
|
||||
private ControlPointInfo controlPoints { get; set; }
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
var controlPointInfo = new ControlPointInfo();
|
||||
controlPoints = new ControlPointInfo();
|
||||
|
||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||
controlPoints.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||
controlPoints.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
ControlPointInfo = controlPointInfo
|
||||
ControlPointInfo = controlPoints
|
||||
});
|
||||
|
||||
// track needs to be playing for BeatSyncedContainer to work.
|
||||
Beatmap.Value.Track.Start();
|
||||
});
|
||||
|
||||
ChannelAmplitudes IHasAmplitudes.CurrentAmplitudes => new ChannelAmplitudes();
|
||||
ControlPointInfo IBeatSyncProvider.ControlPoints => controlPoints;
|
||||
IClock IBeatSyncProvider.Clock => Clock;
|
||||
}
|
||||
}
|
||||
|
685
osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs
Normal file
685
osu.Game.Rulesets.Osu.Tests/TestSceneOsuTouchInput.cs
Normal file
@ -0,0 +1,685 @@
|
||||
// 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.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.States;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneOsuTouchInput : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
private TestActionKeyCounter leftKeyCounter = null!;
|
||||
|
||||
private TestActionKeyCounter rightKeyCounter = null!;
|
||||
|
||||
private OsuInputManager osuInputManager = null!;
|
||||
|
||||
private Container mainContent = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
releaseAllTouches();
|
||||
|
||||
AddStep("Create tests", () =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo)
|
||||
{
|
||||
Child = mainContent = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
Depth = float.MinValue,
|
||||
X = -100,
|
||||
},
|
||||
rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Depth = float.MinValue,
|
||||
X = 100,
|
||||
},
|
||||
new OsuCursorContainer
|
||||
{
|
||||
Depth = float.MinValue,
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new TouchVisualiser(),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStreamInputVisual()
|
||||
{
|
||||
addHitCircleAt(TouchSource.Touch1);
|
||||
addHitCircleAt(TouchSource.Touch2);
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
endTouch(TouchSource.Touch1);
|
||||
|
||||
int i = 0;
|
||||
|
||||
AddRepeatStep("Alternate", () =>
|
||||
{
|
||||
TouchSource down = i % 2 == 0 ? TouchSource.Touch3 : TouchSource.Touch4;
|
||||
TouchSource up = i % 2 == 0 ? TouchSource.Touch4 : TouchSource.Touch3;
|
||||
|
||||
// sometimes the user will end the previous touch before touching again, sometimes not.
|
||||
if (RNG.NextBool())
|
||||
{
|
||||
InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down)));
|
||||
InputManager.EndTouch(new Touch(up, getSanePositionForSource(up)));
|
||||
}
|
||||
else
|
||||
{
|
||||
InputManager.EndTouch(new Touch(up, getSanePositionForSource(up)));
|
||||
InputManager.BeginTouch(new Touch(down, getSanePositionForSource(down)));
|
||||
}
|
||||
|
||||
i++;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSimpleInput()
|
||||
{
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
// Subsequent touches should be ignored (except position).
|
||||
beginTouch(TouchSource.Touch3);
|
||||
checkPosition(TouchSource.Touch3);
|
||||
|
||||
beginTouch(TouchSource.Touch4);
|
||||
checkPosition(TouchSource.Touch4);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
||||
{
|
||||
beginTouch(TouchSource.Touch1);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
beginTouch(TouchSource.Touch1, Vector2.One);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
endTouch(TouchSource.Touch2);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
// note that touch1 was never ended, but is no longer valid for touch input due to touch 2 occurring.
|
||||
beginTouch(TouchSource.Touch1);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStreamInput()
|
||||
{
|
||||
// In this scenario, the user is tapping on the first object in a stream,
|
||||
// then using one or two fingers in empty space to continue the stream.
|
||||
|
||||
addHitCircleAt(TouchSource.Touch1);
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
// The first touch is handled as normal.
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// The second touch should release the first, and also act as a right button.
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
// Importantly, this is different from the simple case because an object was interacted with in the first touch, but not the second touch.
|
||||
// left button is automatically released.
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
// Also importantly, the positional part of the second touch is ignored.
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// In this scenario, a third touch should be allowed, and handled similarly to the second.
|
||||
beginTouch(TouchSource.Touch3);
|
||||
|
||||
assertKeyCounter(2, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
// Position is still ignored.
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
endTouch(TouchSource.Touch2);
|
||||
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkNotPressed(OsuAction.RightButton);
|
||||
// Position is still ignored.
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// User continues streaming
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(2, 2);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
// Position is still ignored.
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// In this mode a maximum of three touches should be supported.
|
||||
// A fourth touch should result in no changes anywhere.
|
||||
beginTouch(TouchSource.Touch4);
|
||||
assertKeyCounter(2, 2);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
endTouch(TouchSource.Touch4);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStreamInputWithInitialTouchDownLeft()
|
||||
{
|
||||
// In this scenario, the user is wanting to use stream input but we start with one finger still on the screen.
|
||||
// That finger is mapped to a left action.
|
||||
|
||||
addHitCircleAt(TouchSource.Touch2);
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// hits circle as right action
|
||||
beginTouch(TouchSource.Touch2);
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
endTouch(TouchSource.Touch1);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
|
||||
// stream using other two fingers while touch2 tracks
|
||||
beginTouch(TouchSource.Touch1);
|
||||
assertKeyCounter(2, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
// right button is automatically released
|
||||
checkNotPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
beginTouch(TouchSource.Touch3);
|
||||
assertKeyCounter(2, 2);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
endTouch(TouchSource.Touch1);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
assertKeyCounter(3, 2);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStreamInputWithInitialTouchDownRight()
|
||||
{
|
||||
// In this scenario, the user is wanting to use stream input but we start with one finger still on the screen.
|
||||
// That finger is mapped to a right action.
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
endTouch(TouchSource.Touch1);
|
||||
|
||||
addHitCircleAt(TouchSource.Touch1);
|
||||
|
||||
// hits circle as left action
|
||||
beginTouch(TouchSource.Touch1);
|
||||
assertKeyCounter(2, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
endTouch(TouchSource.Touch2);
|
||||
|
||||
// stream using other two fingers while touch1 tracks
|
||||
beginTouch(TouchSource.Touch2);
|
||||
assertKeyCounter(2, 2);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
// left button is automatically released
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch3);
|
||||
assertKeyCounter(3, 2);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
endTouch(TouchSource.Touch2);
|
||||
checkNotPressed(OsuAction.RightButton);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
assertKeyCounter(3, 3);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonStreamOverlappingDirectTouchesWithRelease()
|
||||
{
|
||||
// In this scenario, the user is tapping on three circles directly while correctly releasing the first touch.
|
||||
// All three should be recognised.
|
||||
|
||||
addHitCircleAt(TouchSource.Touch1);
|
||||
addHitCircleAt(TouchSource.Touch2);
|
||||
addHitCircleAt(TouchSource.Touch3);
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
endTouch(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch3);
|
||||
assertKeyCounter(2, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonStreamOverlappingDirectTouchesWithoutRelease()
|
||||
{
|
||||
// In this scenario, the user is tapping on three circles directly without releasing any touches.
|
||||
// The first two should be recognised, but a third should not (as the user already has two fingers down).
|
||||
|
||||
addHitCircleAt(TouchSource.Touch1);
|
||||
addHitCircleAt(TouchSource.Touch2);
|
||||
addHitCircleAt(TouchSource.Touch3);
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
|
||||
beginTouch(TouchSource.Touch3);
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMovementWhileDisallowed()
|
||||
{
|
||||
// aka "autopilot" mod
|
||||
|
||||
AddStep("Disallow gameplay cursor movement", () => osuInputManager.AllowUserCursorMovement = false);
|
||||
|
||||
Vector2? positionBefore = null;
|
||||
|
||||
AddStep("Store cursor position", () => positionBefore = osuInputManager.CurrentState.Mouse.Position);
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
AddAssert("Cursor position unchanged", () => osuInputManager.CurrentState.Mouse.Position, () => Is.EqualTo(positionBefore));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestActionWhileDisallowed()
|
||||
{
|
||||
// aka "relax" mod
|
||||
|
||||
AddStep("Disallow gameplay actions", () => osuInputManager.AllowGameplayInputs = false);
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(0, 0);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInputWhileMouseButtonsDisabled()
|
||||
{
|
||||
AddStep("Disable mouse buttons", () => config.SetValue(OsuSetting.MouseDisableButtons, true));
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(0, 0);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(0, 0);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
checkNotPressed(OsuAction.RightButton);
|
||||
checkPosition(TouchSource.Touch2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlternatingInput()
|
||||
{
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
endTouch(TouchSource.Touch1);
|
||||
|
||||
checkPressed(OsuAction.RightButton);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
endTouch(TouchSource.Touch2);
|
||||
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkNotPressed(OsuAction.RightButton);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPressReleaseOrder()
|
||||
{
|
||||
beginTouch(TouchSource.Touch1);
|
||||
beginTouch(TouchSource.Touch2);
|
||||
beginTouch(TouchSource.Touch3);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
// Touch 3 was ignored, but let's ensure that if 1 or 2 are released, 3 will be handled a second attempt.
|
||||
endTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
endTouch(TouchSource.Touch3);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
beginTouch(TouchSource.Touch3);
|
||||
|
||||
assertKeyCounter(2, 1);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWithDisallowedUserCursor()
|
||||
{
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
// Subsequent touches should be ignored.
|
||||
beginTouch(TouchSource.Touch3);
|
||||
beginTouch(TouchSource.Touch4);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
}
|
||||
|
||||
private void addHitCircleAt(TouchSource source)
|
||||
{
|
||||
AddStep($"Add circle at {source}", () =>
|
||||
{
|
||||
var hitCircle = new HitCircle();
|
||||
|
||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
mainContent.Add(new DrawableHitCircle(hitCircle)
|
||||
{
|
||||
Clock = new FramedClock(new ManualClock()),
|
||||
Position = mainContent.ToLocalSpace(getSanePositionForSource(source)),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void beginTouch(TouchSource source, Vector2? screenSpacePosition = null) =>
|
||||
AddStep($"Begin touch for {source}", () => InputManager.BeginTouch(new Touch(source, screenSpacePosition ??= getSanePositionForSource(source))));
|
||||
|
||||
private void endTouch(TouchSource source, Vector2? screenSpacePosition = null) =>
|
||||
AddStep($"Release touch for {source}", () => InputManager.EndTouch(new Touch(source, screenSpacePosition ??= getSanePositionForSource(source))));
|
||||
|
||||
private Vector2 getSanePositionForSource(TouchSource source)
|
||||
{
|
||||
return new Vector2(
|
||||
osuInputManager.ScreenSpaceDrawQuad.Centre.X + osuInputManager.ScreenSpaceDrawQuad.Width * (-1 + (int)source) / 8,
|
||||
osuInputManager.ScreenSpaceDrawQuad.Centre.Y - 100
|
||||
);
|
||||
}
|
||||
|
||||
private void checkPosition(TouchSource touchSource) =>
|
||||
AddAssert("Cursor position is correct", () => osuInputManager.CurrentState.Mouse.Position, () => Is.EqualTo(getSanePositionForSource(touchSource)));
|
||||
|
||||
private void assertKeyCounter(int left, int right)
|
||||
{
|
||||
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
|
||||
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
|
||||
}
|
||||
|
||||
private void releaseAllTouches()
|
||||
{
|
||||
AddStep("Release all touches", () =>
|
||||
{
|
||||
config.SetValue(OsuSetting.MouseDisableButtons, false);
|
||||
foreach (TouchSource source in InputManager.CurrentState.Touch.ActiveSources)
|
||||
InputManager.EndTouch(new Touch(source, osuInputManager.ScreenSpaceDrawQuad.Centre));
|
||||
});
|
||||
}
|
||||
|
||||
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
|
||||
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
|
||||
|
||||
public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
public OsuAction Action { get; }
|
||||
|
||||
public TestActionKeyCounter(OsuAction action)
|
||||
: base(action.ToString())
|
||||
{
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
if (e.Action == Action)
|
||||
{
|
||||
IsLit = true;
|
||||
Increment();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
if (e.Action == Action) IsLit = false;
|
||||
}
|
||||
}
|
||||
|
||||
public partial class TouchVisualiser : CompositeDrawable
|
||||
{
|
||||
private readonly Drawable?[] drawableTouches = new Drawable?[TouchState.MAX_TOUCH_COUNT];
|
||||
|
||||
public TouchVisualiser()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
protected override bool OnTouchDown(TouchDownEvent e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return false;
|
||||
|
||||
var circle = new Circle
|
||||
{
|
||||
Alpha = 0.5f,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(20),
|
||||
Position = e.Touch.Position,
|
||||
Colour = colourFor(e.Touch.Source),
|
||||
};
|
||||
|
||||
AddInternal(circle);
|
||||
drawableTouches[(int)e.Touch.Source] = circle;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnTouchMove(TouchMoveEvent e)
|
||||
{
|
||||
if (IsDisposed)
|
||||
return;
|
||||
|
||||
var circle = drawableTouches[(int)e.Touch.Source];
|
||||
|
||||
Debug.Assert(circle != null);
|
||||
|
||||
AddInternal(new FadingCircle(circle));
|
||||
circle.Position = e.Touch.Position;
|
||||
}
|
||||
|
||||
protected override void OnTouchUp(TouchUpEvent e)
|
||||
{
|
||||
var circle = drawableTouches[(int)e.Touch.Source];
|
||||
|
||||
Debug.Assert(circle != null);
|
||||
|
||||
circle.FadeOut(200, Easing.OutQuint).Expire();
|
||||
drawableTouches[(int)e.Touch.Source] = null;
|
||||
}
|
||||
|
||||
private Color4 colourFor(TouchSource source)
|
||||
{
|
||||
return Color4.FromHsv(new Vector4((float)source / TouchState.MAX_TOUCH_COUNT, 1f, 1f, 1f));
|
||||
}
|
||||
|
||||
private partial class FadingCircle : Circle
|
||||
{
|
||||
public FadingCircle(Drawable source)
|
||||
{
|
||||
Origin = Anchor.Centre;
|
||||
Size = source.Size;
|
||||
Position = source.Position;
|
||||
Colour = source.Colour;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
this.FadeOut(200).Expire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -31,10 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
[Test]
|
||||
public void TestMaximumDistanceTrackingWithoutMovement(
|
||||
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
|
||||
float circleSize,
|
||||
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
|
||||
double velocity)
|
||||
[Values(0, 5, 10)] float circleSize,
|
||||
[Values(0, 5, 10)] double velocity)
|
||||
{
|
||||
const double time_slider_start = 1000;
|
||||
|
||||
|
@ -8,34 +8,36 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Lines;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A visualisation of the line between two <see cref="PathControlPointPiece"/>s.
|
||||
/// A visualisation of the line between two <see cref="PathControlPointPiece{T}"/>s.
|
||||
/// </summary>
|
||||
public partial class PathControlPointConnectionPiece : CompositeDrawable
|
||||
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointConnectionPiece{T}"/> visualises.</typeparam>
|
||||
public partial class PathControlPointConnectionPiece<T> : CompositeDrawable where T : OsuHitObject, IHasPath
|
||||
{
|
||||
public readonly PathControlPoint ControlPoint;
|
||||
|
||||
private readonly Path path;
|
||||
private readonly Slider slider;
|
||||
private readonly T hitObject;
|
||||
public int ControlPointIndex { get; set; }
|
||||
|
||||
private IBindable<Vector2> sliderPosition;
|
||||
private IBindable<Vector2> hitObjectPosition;
|
||||
private IBindable<int> pathVersion;
|
||||
|
||||
public PathControlPointConnectionPiece(Slider slider, int controlPointIndex)
|
||||
public PathControlPointConnectionPiece(T hitObject, int controlPointIndex)
|
||||
{
|
||||
this.slider = slider;
|
||||
this.hitObject = hitObject;
|
||||
ControlPointIndex = controlPointIndex;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
ControlPoint = slider.Path.ControlPoints[controlPointIndex];
|
||||
ControlPoint = hitObject.Path.ControlPoints[controlPointIndex];
|
||||
|
||||
InternalChild = path = new SmoothPath
|
||||
{
|
||||
@ -48,10 +50,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
sliderPosition = slider.PositionBindable.GetBoundCopy();
|
||||
sliderPosition.BindValueChanged(_ => updateConnectingPath());
|
||||
hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
|
||||
hitObjectPosition.BindValueChanged(_ => updateConnectingPath());
|
||||
|
||||
pathVersion = slider.Path.Version.GetBoundCopy();
|
||||
pathVersion = hitObject.Path.Version.GetBoundCopy();
|
||||
pathVersion.BindValueChanged(_ => updateConnectingPath());
|
||||
|
||||
updateConnectingPath();
|
||||
@ -62,16 +64,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
/// </summary>
|
||||
private void updateConnectingPath()
|
||||
{
|
||||
Position = slider.StackedPosition + ControlPoint.Position;
|
||||
Position = hitObject.StackedPosition + ControlPoint.Position;
|
||||
|
||||
path.ClearVertices();
|
||||
|
||||
int nextIndex = ControlPointIndex + 1;
|
||||
if (nextIndex == 0 || nextIndex >= slider.Path.ControlPoints.Count)
|
||||
if (nextIndex == 0 || nextIndex >= hitObject.Path.ControlPoints.Count)
|
||||
return;
|
||||
|
||||
path.AddVertex(Vector2.Zero);
|
||||
path.AddVertex(slider.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
|
||||
path.AddVertex(hitObject.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
|
||||
|
||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||
}
|
||||
|
@ -29,11 +29,13 @@ using osuTK.Input;
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A visualisation of a single <see cref="PathControlPoint"/> in a <see cref="Slider"/>.
|
||||
/// A visualisation of a single <see cref="PathControlPoint"/> in an osu hit object with a path.
|
||||
/// </summary>
|
||||
public partial class PathControlPointPiece : BlueprintPiece<Slider>, IHasTooltip
|
||||
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="PathControlPointPiece{T}"/> visualises.</typeparam>
|
||||
public partial class PathControlPointPiece<T> : BlueprintPiece<T>, IHasTooltip
|
||||
where T : OsuHitObject, IHasPath
|
||||
{
|
||||
public Action<PathControlPointPiece, MouseButtonEvent> RequestSelection;
|
||||
public Action<PathControlPointPiece<T>, MouseButtonEvent> RequestSelection;
|
||||
|
||||
public Action<PathControlPoint> DragStarted;
|
||||
public Action<DragEvent> DragInProgress;
|
||||
@ -44,34 +46,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
public readonly BindableBool IsSelected = new BindableBool();
|
||||
public readonly PathControlPoint ControlPoint;
|
||||
|
||||
private readonly Slider slider;
|
||||
private readonly T hitObject;
|
||||
private readonly Container marker;
|
||||
private readonly Drawable markerRing;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
private IBindable<Vector2> sliderPosition;
|
||||
private IBindable<float> sliderScale;
|
||||
private IBindable<Vector2> hitObjectPosition;
|
||||
private IBindable<float> hitObjectScale;
|
||||
|
||||
[UsedImplicitly]
|
||||
private readonly IBindable<int> sliderVersion;
|
||||
private readonly IBindable<int> hitObjectVersion;
|
||||
|
||||
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
||||
public PathControlPointPiece(T hitObject, PathControlPoint controlPoint)
|
||||
{
|
||||
this.slider = slider;
|
||||
this.hitObject = hitObject;
|
||||
ControlPoint = controlPoint;
|
||||
|
||||
// we don't want to run the path type update on construction as it may inadvertently change the slider.
|
||||
cachePoints(slider);
|
||||
// we don't want to run the path type update on construction as it may inadvertently change the hit object.
|
||||
cachePoints(hitObject);
|
||||
|
||||
sliderVersion = slider.Path.Version.GetBoundCopy();
|
||||
hitObjectVersion = hitObject.Path.Version.GetBoundCopy();
|
||||
|
||||
// schedule ensure that updates are only applied after all operations from a single frame are applied.
|
||||
// this avoids inadvertently changing the slider path type for batch operations.
|
||||
sliderVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
|
||||
// this avoids inadvertently changing the hit object path type for batch operations.
|
||||
hitObjectVersion.BindValueChanged(_ => Scheduler.AddOnce(() =>
|
||||
{
|
||||
cachePoints(slider);
|
||||
cachePoints(hitObject);
|
||||
updatePathType();
|
||||
}));
|
||||
|
||||
@ -120,11 +122,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
sliderPosition = slider.PositionBindable.GetBoundCopy();
|
||||
sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
|
||||
hitObjectPosition = hitObject.PositionBindable.GetBoundCopy();
|
||||
hitObjectPosition.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
sliderScale = slider.ScaleBindable.GetBoundCopy();
|
||||
sliderScale.BindValueChanged(_ => updateMarkerDisplay());
|
||||
hitObjectScale = hitObject.ScaleBindable.GetBoundCopy();
|
||||
hitObjectScale.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
IsSelected.BindValueChanged(_ => updateMarkerDisplay());
|
||||
|
||||
@ -212,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
protected override void OnDragEnd(DragEndEvent e) => DragEnded?.Invoke();
|
||||
|
||||
private void cachePoints(Slider slider) => PointsInSegment = slider.Path.PointsInSegment(ControlPoint);
|
||||
private void cachePoints(T hitObject) => PointsInSegment = hitObject.Path.PointsInSegment(ControlPoint);
|
||||
|
||||
/// <summary>
|
||||
/// Handles correction of invalid path types.
|
||||
@ -239,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
/// </summary>
|
||||
private void updateMarkerDisplay()
|
||||
{
|
||||
Position = slider.StackedPosition + ControlPoint.Position;
|
||||
Position = hitObject.StackedPosition + ControlPoint.Position;
|
||||
|
||||
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||
|
||||
@ -249,7 +251,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
colour = colour.Lighten(1);
|
||||
|
||||
marker.Colour = colour;
|
||||
marker.Scale = new Vector2(slider.Scale);
|
||||
marker.Scale = new Vector2(hitObject.Scale);
|
||||
}
|
||||
|
||||
private Color4 getColourFromNodeType()
|
||||
|
@ -29,15 +29,16 @@ using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
public partial class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||
public partial class PathControlPointVisualiser<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IHasContextMenu
|
||||
where T : OsuHitObject, IHasPath
|
||||
{
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; // allow context menu to appear outside of the playfield.
|
||||
|
||||
internal readonly Container<PathControlPointPiece> Pieces;
|
||||
internal readonly Container<PathControlPointConnectionPiece> Connections;
|
||||
internal readonly Container<PathControlPointPiece<T>> Pieces;
|
||||
internal readonly Container<PathControlPointConnectionPiece<T>> Connections;
|
||||
|
||||
private readonly IBindableList<PathControlPoint> controlPoints = new BindableList<PathControlPoint>();
|
||||
private readonly Slider slider;
|
||||
private readonly T hitObject;
|
||||
private readonly bool allowSelection;
|
||||
|
||||
private InputManager inputManager;
|
||||
@ -48,17 +49,17 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
|
||||
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
||||
public PathControlPointVisualiser(T hitObject, bool allowSelection)
|
||||
{
|
||||
this.slider = slider;
|
||||
this.hitObject = hitObject;
|
||||
this.allowSelection = allowSelection;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
Connections = new Container<PathControlPointConnectionPiece> { RelativeSizeAxes = Axes.Both },
|
||||
Pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both }
|
||||
Connections = new Container<PathControlPointConnectionPiece<T>> { RelativeSizeAxes = Axes.Both },
|
||||
Pieces = new Container<PathControlPointPiece<T>> { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
}
|
||||
|
||||
@ -69,12 +70,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
inputManager = GetContainingInputManager();
|
||||
|
||||
controlPoints.CollectionChanged += onControlPointsChanged;
|
||||
controlPoints.BindTo(slider.Path.ControlPoints);
|
||||
controlPoints.BindTo(hitObject.Path.ControlPoints);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the <see cref="PathControlPointPiece"/> corresponding to the given <paramref name="pathControlPoint"/>,
|
||||
/// and deselects all other <see cref="PathControlPointPiece"/>s.
|
||||
/// Selects the <see cref="PathControlPointPiece{T}"/> corresponding to the given <paramref name="pathControlPoint"/>,
|
||||
/// and deselects all other <see cref="PathControlPointPiece{T}"/>s.
|
||||
/// </summary>
|
||||
public void SetSelectionTo(PathControlPoint pathControlPoint)
|
||||
{
|
||||
@ -124,8 +125,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool isSplittable(PathControlPointPiece p) =>
|
||||
// A slider can only be split on control points which connect two different slider segments.
|
||||
private bool isSplittable(PathControlPointPiece<T> p) =>
|
||||
// A hit object can only be split on control points which connect two different path segments.
|
||||
p.ControlPoint.Type.HasValue && p != Pieces.FirstOrDefault() && p != Pieces.LastOrDefault();
|
||||
|
||||
private void onControlPointsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
@ -150,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
var point = (PathControlPoint)e.NewItems[i];
|
||||
|
||||
Pieces.Add(new PathControlPointPiece(slider, point).With(d =>
|
||||
Pieces.Add(new PathControlPointPiece<T>(hitObject, point).With(d =>
|
||||
{
|
||||
if (allowSelection)
|
||||
d.RequestSelection = selectionRequested;
|
||||
@ -160,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
d.DragEnded = dragEnded;
|
||||
}));
|
||||
|
||||
Connections.Add(new PathControlPointConnectionPiece(slider, e.NewStartingIndex + i));
|
||||
Connections.Add(new PathControlPointConnectionPiece<T>(hitObject, e.NewStartingIndex + i));
|
||||
}
|
||||
|
||||
break;
|
||||
@ -219,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
}
|
||||
|
||||
private void selectionRequested(PathControlPointPiece piece, MouseButtonEvent e)
|
||||
private void selectionRequested(PathControlPointPiece<T> piece, MouseButtonEvent e)
|
||||
{
|
||||
if (e.Button == MouseButton.Left && inputManager.CurrentState.Keyboard.ControlPressed)
|
||||
piece.IsSelected.Toggle();
|
||||
@ -234,7 +235,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
/// </summary>
|
||||
/// <param name="piece">The control point piece that we want to change the path type of.</param>
|
||||
/// <param name="type">The path type we want to assign to the given control point piece.</param>
|
||||
private void updatePathType(PathControlPointPiece piece, PathType? type)
|
||||
private void updatePathType(PathControlPointPiece<T> piece, PathType? type)
|
||||
{
|
||||
int indexInSegment = piece.PointsInSegment.IndexOf(piece.ControlPoint);
|
||||
|
||||
@ -252,7 +253,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
break;
|
||||
}
|
||||
|
||||
slider.Path.ExpectedDistance.Value = null;
|
||||
hitObject.Path.ExpectedDistance.Value = null;
|
||||
piece.ControlPoint.Type = type;
|
||||
}
|
||||
|
||||
@ -268,9 +269,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
private void dragStarted(PathControlPoint controlPoint)
|
||||
{
|
||||
dragStartPositions = slider.Path.ControlPoints.Select(point => point.Position).ToArray();
|
||||
dragPathTypes = slider.Path.ControlPoints.Select(point => point.Type).ToArray();
|
||||
draggedControlPointIndex = slider.Path.ControlPoints.IndexOf(controlPoint);
|
||||
dragStartPositions = hitObject.Path.ControlPoints.Select(point => point.Position).ToArray();
|
||||
dragPathTypes = hitObject.Path.ControlPoints.Select(point => point.Type).ToArray();
|
||||
draggedControlPointIndex = hitObject.Path.ControlPoints.IndexOf(controlPoint);
|
||||
selectedControlPoints = new HashSet<PathControlPoint>(Pieces.Where(piece => piece.IsSelected.Value).Select(piece => piece.ControlPoint));
|
||||
|
||||
Debug.Assert(draggedControlPointIndex >= 0);
|
||||
@ -280,25 +281,25 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
|
||||
private void dragInProgress(DragEvent e)
|
||||
{
|
||||
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
|
||||
var oldPosition = slider.Position;
|
||||
double oldStartTime = slider.StartTime;
|
||||
Vector2[] oldControlPoints = hitObject.Path.ControlPoints.Select(cp => cp.Position).ToArray();
|
||||
var oldPosition = hitObject.Position;
|
||||
double oldStartTime = hitObject.StartTime;
|
||||
|
||||
if (selectedControlPoints.Contains(slider.Path.ControlPoints[0]))
|
||||
if (selectedControlPoints.Contains(hitObject.Path.ControlPoints[0]))
|
||||
{
|
||||
// Special handling for selections containing head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||
// Special handling for selections containing head control point - the position of the hit object changes which means the snapped position and time have to be taken into account
|
||||
Vector2 newHeadPosition = Parent.ToScreenSpace(e.MousePosition + (dragStartPositions[0] - dragStartPositions[draggedControlPointIndex]));
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(newHeadPosition);
|
||||
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - slider.Position;
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? newHeadPosition) - hitObject.Position;
|
||||
|
||||
slider.Position += movementDelta;
|
||||
slider.StartTime = result?.Time ?? slider.StartTime;
|
||||
hitObject.Position += movementDelta;
|
||||
hitObject.StartTime = result?.Time ?? hitObject.StartTime;
|
||||
|
||||
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
||||
for (int i = 1; i < hitObject.Path.ControlPoints.Count; i++)
|
||||
{
|
||||
var controlPoint = slider.Path.ControlPoints[i];
|
||||
// Since control points are relative to the position of the slider, all points that are _not_ selected
|
||||
var controlPoint = hitObject.Path.ControlPoints[i];
|
||||
// Since control points are relative to the position of the hit object, all points that are _not_ selected
|
||||
// need to be offset _back_ by the delta corresponding to the movement of the head point.
|
||||
// All other selected control points (if any) will move together with the head point
|
||||
// (and so they will not move at all, relative to each other).
|
||||
@ -310,7 +311,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
{
|
||||
var result = snapProvider?.FindSnappedPositionAndTime(Parent.ToScreenSpace(e.MousePosition));
|
||||
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - slider.Position;
|
||||
Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? Parent.ToScreenSpace(e.MousePosition)) - dragStartPositions[draggedControlPointIndex] - hitObject.Position;
|
||||
|
||||
for (int i = 0; i < controlPoints.Count; ++i)
|
||||
{
|
||||
@ -321,23 +322,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
}
|
||||
|
||||
// Snap the path to the current beat divisor before checking length validity.
|
||||
slider.SnapTo(snapProvider);
|
||||
hitObject.SnapTo(snapProvider);
|
||||
|
||||
if (!slider.Path.HasValidLength)
|
||||
if (!hitObject.Path.HasValidLength)
|
||||
{
|
||||
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||
slider.Path.ControlPoints[i].Position = oldControlPoints[i];
|
||||
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
||||
hitObject.Path.ControlPoints[i].Position = oldControlPoints[i];
|
||||
|
||||
slider.Position = oldPosition;
|
||||
slider.StartTime = oldStartTime;
|
||||
hitObject.Position = oldPosition;
|
||||
hitObject.StartTime = oldStartTime;
|
||||
// Snap the path length again to undo the invalid length.
|
||||
slider.SnapTo(snapProvider);
|
||||
hitObject.SnapTo(snapProvider);
|
||||
return;
|
||||
}
|
||||
|
||||
// Maintain the path types in case they got defaulted to bezier at some point during the drag.
|
||||
for (int i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||
slider.Path.ControlPoints[i].Type = dragPathTypes[i];
|
||||
for (int i = 0; i < hitObject.Path.ControlPoints.Count; i++)
|
||||
hitObject.Path.ControlPoints[i].Type = dragPathTypes[i];
|
||||
}
|
||||
|
||||
private void dragEnded() => changeHandler?.EndChange();
|
||||
|
@ -22,6 +22,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||
/// </summary>
|
||||
public Vector2 PathStartLocation => body.PathOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Offset in absolute (local) coordinates from the end of the curve.
|
||||
/// </summary>
|
||||
public Vector2 PathEndLocation => body.PathEndOffset;
|
||||
|
||||
public SliderBodyPiece()
|
||||
{
|
||||
InternalChild = body = new ManualSliderBody
|
||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
private SliderBodyPiece bodyPiece;
|
||||
private HitCirclePiece headCirclePiece;
|
||||
private HitCirclePiece tailCirclePiece;
|
||||
private PathControlPointVisualiser controlPointVisualiser;
|
||||
private PathControlPointVisualiser<Slider> controlPointVisualiser;
|
||||
|
||||
private InputManager inputManager;
|
||||
|
||||
@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
bodyPiece = new SliderBodyPiece(),
|
||||
headCirclePiece = new HitCirclePiece(),
|
||||
tailCirclePiece = new HitCirclePiece(),
|
||||
controlPointVisualiser = new PathControlPointVisualiser(HitObject, false)
|
||||
controlPointVisualiser = new PathControlPointVisualiser<Slider>(HitObject, false)
|
||||
};
|
||||
|
||||
setState(SliderPlacementState.Initial);
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
protected SliderCircleOverlay TailOverlay { get; private set; }
|
||||
|
||||
[CanBeNull]
|
||||
protected PathControlPointVisualiser ControlPointVisualiser { get; private set; }
|
||||
protected PathControlPointVisualiser<Slider> ControlPointVisualiser { get; private set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IDistanceSnapProvider snapProvider { get; set; }
|
||||
@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
{
|
||||
if (ControlPointVisualiser == null)
|
||||
{
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser(HitObject, true)
|
||||
AddInternal(ControlPointVisualiser = new PathControlPointVisualiser<Slider>(HitObject, true)
|
||||
{
|
||||
RemoveControlPointsRequested = removeControlPoints,
|
||||
SplitControlPointsRequested = splitControlPoints
|
||||
@ -409,6 +409,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathOffset)
|
||||
?? BodyPiece.ToScreenSpace(BodyPiece.PathStartLocation);
|
||||
|
||||
protected override Vector2[] ScreenSpaceAdditionalNodes => new[]
|
||||
{
|
||||
DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation)
|
||||
};
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||
|
||||
|
14
osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs
Normal file
14
osu.Game.Rulesets.Osu/Mods/OsuModAccuracyChallenge.cs
Normal file
@ -0,0 +1,14 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModAccuracyChallenge : ModAccuracyChallenge
|
||||
{
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModAutopilot)).ToArray();
|
||||
}
|
||||
}
|
@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
PathVersion.UnbindFrom(HitObject.Path.Version);
|
||||
|
||||
slidingSample.Samples = null;
|
||||
slidingSample?.ClearSamples();
|
||||
}
|
||||
|
||||
protected override void LoadSamples()
|
||||
|
@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.OnFree();
|
||||
|
||||
spinningSample.Samples = null;
|
||||
spinningSample.ClearSamples();
|
||||
}
|
||||
|
||||
protected override void LoadSamples()
|
||||
|
@ -1,17 +1,18 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.StateChanges.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
@ -28,6 +29,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
/// </remarks>
|
||||
public bool AllowGameplayInputs
|
||||
{
|
||||
get => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs;
|
||||
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowGameplayInputs = value;
|
||||
}
|
||||
|
||||
@ -40,11 +42,24 @@ namespace osu.Game.Rulesets.Osu
|
||||
protected override KeyBindingContainer<OsuAction> CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
|
||||
=> new OsuKeyBindingContainer(ruleset, variant, unique);
|
||||
|
||||
public bool CheckScreenSpaceActionPressJudgeable(Vector2 screenSpacePosition) =>
|
||||
// This is a very naive but simple approach.
|
||||
//
|
||||
// Based on user feedback of more nuanced scenarios (where touch doesn't behave as expected),
|
||||
// this can be expanded to a more complex implementation, but I'd still want to keep it as simple as we can.
|
||||
NonPositionalInputQueue.OfType<DrawableHitCircle.HitReceptor>().Any(c => c.ReceivePositionalInputAt(screenSpacePosition));
|
||||
|
||||
public OsuInputManager(RulesetInfo ruleset)
|
||||
: base(ruleset, 0, SimultaneousBindingMode.Unique)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Add(new OsuTouchInputMapper(this) { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
if ((e is MouseMoveEvent || e is TouchMoveEvent) && !AllowUserCursorMovement) return false;
|
||||
@ -52,19 +67,6 @@ namespace osu.Game.Rulesets.Osu
|
||||
return base.Handle(e);
|
||||
}
|
||||
|
||||
protected override bool HandleMouseTouchStateChange(TouchStateChangeEvent e)
|
||||
{
|
||||
if (!AllowUserCursorMovement)
|
||||
{
|
||||
// Still allow for forwarding of the "touch" part, but replace the positional data with that of the mouse.
|
||||
// Primarily relied upon by the "autopilot" osu! mod.
|
||||
var touch = new Touch(e.Touch.Source, CurrentState.Mouse.Position);
|
||||
e = new TouchStateChangeEvent(e.State, e.Input, touch, e.IsActive, null);
|
||||
}
|
||||
|
||||
return base.HandleMouseTouchStateChange(e);
|
||||
}
|
||||
|
||||
private partial class OsuKeyBindingContainer : RulesetKeyBindingContainer
|
||||
{
|
||||
private bool allowGameplayInputs = true;
|
||||
|
@ -164,7 +164,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
|
||||
new OsuModHidden(),
|
||||
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
|
||||
new OsuModStrictTracking()
|
||||
new OsuModStrictTracking(),
|
||||
new OsuModAccuracyChallenge(),
|
||||
};
|
||||
|
||||
case ModType.Conversion:
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -43,6 +44,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||
private readonly FlashPiece flash;
|
||||
private readonly Container kiaiContainer;
|
||||
|
||||
private Bindable<bool> configHitLighting = null!;
|
||||
|
||||
[Resolved]
|
||||
private DrawableHitObject drawableObject { get; set; } = null!;
|
||||
@ -64,24 +68,32 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
outerGradient = new Circle // renders the outer bright gradient
|
||||
{
|
||||
Size = new Vector2(OUTER_GRADIENT_SIZE),
|
||||
Alpha = 1,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
innerGradient = new Circle // renders the inner bright gradient
|
||||
{
|
||||
Size = new Vector2(INNER_GRADIENT_SIZE),
|
||||
Alpha = 1,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
innerFill = new Circle // renders the inner dark fill
|
||||
{
|
||||
Size = new Vector2(INNER_FILL_SIZE),
|
||||
Alpha = 1,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
kiaiContainer = new CircularContainer
|
||||
{
|
||||
Masking = true,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = Size,
|
||||
Child = new KiaiFlash
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
},
|
||||
number = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Default.With(size: 52, weight: FontWeight.Bold),
|
||||
@ -96,12 +108,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
var drawableOsuObject = (DrawableOsuHitObject)drawableObject;
|
||||
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable);
|
||||
|
||||
configHitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -117,20 +131,25 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
outerGradient.ClearTransforms(targetMember: nameof(Colour));
|
||||
outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
|
||||
|
||||
kiaiContainer.Colour = colour.NewValue;
|
||||
outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
|
||||
innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
|
||||
flash.Colour = colour.NewValue;
|
||||
|
||||
// Accent colour may be changed many times during a paused gameplay state.
|
||||
// Schedule the change to avoid transforms piling up.
|
||||
Scheduler.AddOnce(updateStateTransforms);
|
||||
Scheduler.AddOnce(() =>
|
||||
{
|
||||
ApplyTransformsAt(double.MinValue, true);
|
||||
ClearTransformsAfter(double.MinValue, true);
|
||||
|
||||
updateStateTransforms(drawableObject, drawableObject.State.Value);
|
||||
});
|
||||
}, true);
|
||||
|
||||
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
|
||||
}
|
||||
|
||||
private void updateStateTransforms() => updateStateTransforms(drawableObject, drawableObject.State.Value);
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime))
|
||||
@ -140,7 +159,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
case ArmedState.Hit:
|
||||
// Fade out time is at a maximum of 800. Must match `DrawableHitCircle`'s arbitrary lifetime spec.
|
||||
const double fade_out_time = 800;
|
||||
|
||||
const double flash_in_duration = 150;
|
||||
const double resize_duration = 400;
|
||||
|
||||
@ -171,20 +189,40 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
// gradient layers.
|
||||
border.ResizeTo(Size * shrink_size + new Vector2(border.BorderThickness), resize_duration, Easing.OutElasticHalf);
|
||||
|
||||
// Kiai flash should track the overall size but also be cleaned up quite fast, so we don't get additional
|
||||
// flashes after the hit animation is already in a mostly-completed state.
|
||||
kiaiContainer.ResizeTo(Size * shrink_size, resize_duration, Easing.OutElasticHalf);
|
||||
kiaiContainer.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||
|
||||
// The outer gradient is resize with a slight delay from the border.
|
||||
// This is to give it a bomb-like effect, with the border "triggering" its animation when getting close.
|
||||
using (BeginDelayedSequence(flash_in_duration / 12))
|
||||
{
|
||||
outerGradient.ResizeTo(OUTER_GRADIENT_SIZE * shrink_size, resize_duration, Easing.OutElasticHalf);
|
||||
|
||||
outerGradient
|
||||
.FadeColour(Color4.White, 80)
|
||||
.Then()
|
||||
.FadeOut(flash_in_duration);
|
||||
}
|
||||
|
||||
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
|
||||
if (configHitLighting.Value)
|
||||
{
|
||||
flash.HitLighting = true;
|
||||
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
|
||||
|
||||
this.FadeOut(fade_out_time, Easing.OutQuad);
|
||||
}
|
||||
else
|
||||
{
|
||||
flash.HitLighting = false;
|
||||
flash.FadeTo(1, flash_in_duration, Easing.OutQuint)
|
||||
.Then()
|
||||
.FadeOut(flash_in_duration, Easing.OutQuint);
|
||||
|
||||
this.FadeOut(fade_out_time * 0.8f, Easing.OutQuad);
|
||||
}
|
||||
|
||||
this.FadeOut(fade_out_time, Easing.OutQuad);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -215,6 +253,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
Child.AlwaysPresent = true;
|
||||
}
|
||||
|
||||
public bool HitLighting { get; set; }
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
@ -223,7 +263,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = Colour,
|
||||
Radius = OsuHitObject.OBJECT_RADIUS * 1.2f,
|
||||
Radius = OsuHitObject.OBJECT_RADIUS * (HitLighting ? 1.2f : 0.6f),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -35,14 +35,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
public void SetRotation(float currentRotation)
|
||||
{
|
||||
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
||||
if (Precision.AlmostEquals(0, Time.Elapsed))
|
||||
return;
|
||||
|
||||
// If we've gone back in time, it's fine to work with a fresh set of records for now
|
||||
if (records.Count > 0 && Time.Current < records.Last().Time)
|
||||
records.Clear();
|
||||
|
||||
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
||||
if (records.Count > 0 && Precision.AlmostEquals(Time.Current, records.Last().Time))
|
||||
return;
|
||||
|
||||
if (records.Count > 0)
|
||||
{
|
||||
var record = records.Peek();
|
||||
|
@ -31,6 +31,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
/// </summary>
|
||||
public virtual Vector2 PathOffset => path.PositionInBoundingBox(path.Vertices[0]);
|
||||
|
||||
/// <summary>
|
||||
/// Offset in absolute coordinates from the end of the curve.
|
||||
/// </summary>
|
||||
public virtual Vector2 PathEndOffset => path.PositionInBoundingBox(path.Vertices[^1]);
|
||||
|
||||
/// <summary>
|
||||
/// Used to colour the path.
|
||||
/// </summary>
|
||||
|
@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
public override Vector2 PathOffset => snakedPathOffset;
|
||||
|
||||
public override Vector2 PathEndOffset => snakedPathEndOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The top-left position of the path when fully snaked.
|
||||
/// </summary>
|
||||
@ -53,6 +55,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
/// </summary>
|
||||
private Vector2 snakedPathOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The offset of the end of path from <see cref="snakedPosition"/> when fully snaked.
|
||||
/// </summary>
|
||||
private Vector2 snakedPathEndOffset;
|
||||
|
||||
private DrawableSlider drawableSlider = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -109,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
|
||||
snakedPosition = Path.PositionInBoundingBox(Vector2.Zero);
|
||||
snakedPathOffset = Path.PositionInBoundingBox(Path.Vertices[0]);
|
||||
snakedPathEndOffset = Path.PositionInBoundingBox(Path.Vertices[^1]);
|
||||
|
||||
double lastSnakedStart = SnakedStart ?? 0;
|
||||
double lastSnakedEnd = SnakedEnd ?? 0;
|
||||
|
@ -1,11 +1,10 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -30,23 +29,23 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Snaking in sliders",
|
||||
LabelText = RulesetSettingsStrings.SnakingInSliders,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
ClassicDefault = false,
|
||||
LabelText = "Snaking out sliders",
|
||||
LabelText = RulesetSettingsStrings.SnakingOutSliders,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Cursor trail",
|
||||
LabelText = RulesetSettingsStrings.CursorTrail,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||
},
|
||||
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
||||
{
|
||||
LabelText = "Playfield border style",
|
||||
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||
Current = config.GetBindable<PlayfieldBorderStyle>(OsuRulesetSetting.PlayfieldBorderStyle),
|
||||
},
|
||||
};
|
||||
|
161
osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs
Normal file
161
osu.Game.Rulesets.Osu/UI/OsuTouchInputMapper.cs
Normal file
@ -0,0 +1,161 @@
|
||||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Input.StateChanges;
|
||||
using osu.Game.Configuration;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public partial class OsuTouchInputMapper : Drawable
|
||||
{
|
||||
/// <summary>
|
||||
/// All the active <see cref="TouchSource"/>s and the <see cref="OsuAction"/> that it triggered (if any).
|
||||
/// Ordered from oldest to newest touch chronologically.
|
||||
/// </summary>
|
||||
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
||||
|
||||
private TrackedTouch? positionTrackingTouch;
|
||||
|
||||
private readonly OsuInputManager osuInputManager;
|
||||
|
||||
private Bindable<bool> mouseDisabled = null!;
|
||||
|
||||
public OsuTouchInputMapper(OsuInputManager inputManager)
|
||||
{
|
||||
osuInputManager = inputManager;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
// The mouse button disable setting affects touch. It's a bit weird.
|
||||
// This is mostly just doing the same as what is done in RulesetInputManager to match behaviour.
|
||||
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
|
||||
}
|
||||
|
||||
// Required to handle touches outside of the playfield when screen scaling is enabled.
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
protected override void OnTouchMove(TouchMoveEvent e)
|
||||
{
|
||||
base.OnTouchMove(e);
|
||||
handleTouchMovement(e);
|
||||
}
|
||||
|
||||
protected override bool OnTouchDown(TouchDownEvent e)
|
||||
{
|
||||
OsuAction action = trackedTouches.Any(t => t.Action == OsuAction.LeftButton)
|
||||
? OsuAction.RightButton
|
||||
: OsuAction.LeftButton;
|
||||
|
||||
// Ignore any taps which trigger an action which is already handled. But track them for potential positional input in the future.
|
||||
bool shouldResultInAction = osuInputManager.AllowGameplayInputs && !mouseDisabled.Value && trackedTouches.All(t => t.Action != action);
|
||||
|
||||
// If we can actually accept as an action, check whether this tap was on a circle's receptor.
|
||||
// This case gets special handling to allow for empty-space stream tapping.
|
||||
bool isDirectCircleTouch = osuInputManager.CheckScreenSpaceActionPressJudgeable(e.ScreenSpaceTouchDownPosition);
|
||||
|
||||
var newTouch = new TrackedTouch(e.Touch.Source, shouldResultInAction ? action : null, isDirectCircleTouch);
|
||||
|
||||
updatePositionTracking(newTouch);
|
||||
|
||||
trackedTouches.Add(newTouch);
|
||||
|
||||
// Important to update position before triggering the pressed action.
|
||||
handleTouchMovement(e);
|
||||
|
||||
if (shouldResultInAction)
|
||||
osuInputManager.KeyBindingContainer.TriggerPressed(action);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a new touch, update the positional tracking state and any related operations.
|
||||
/// </summary>
|
||||
private void updatePositionTracking(TrackedTouch newTouch)
|
||||
{
|
||||
// If the new touch directly interacted with a circle's receptor, it always becomes the current touch for positional tracking.
|
||||
if (newTouch.DirectTouch)
|
||||
{
|
||||
positionTrackingTouch = newTouch;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, we only want to use the new touch for position tracking if no other touch is tracking position yet..
|
||||
if (positionTrackingTouch == null)
|
||||
{
|
||||
positionTrackingTouch = newTouch;
|
||||
return;
|
||||
}
|
||||
|
||||
// ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
|
||||
if (!positionTrackingTouch.DirectTouch)
|
||||
{
|
||||
positionTrackingTouch = newTouch;
|
||||
return;
|
||||
}
|
||||
|
||||
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
||||
// If it was a direct touch and still has its action pressed, that action should be released.
|
||||
//
|
||||
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
||||
if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction)
|
||||
{
|
||||
osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction);
|
||||
positionTrackingTouch.Action = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTouchMovement(TouchEvent touchEvent)
|
||||
{
|
||||
// Movement should only be tracked for the most recent touch.
|
||||
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
||||
return;
|
||||
|
||||
if (!osuInputManager.AllowUserCursorMovement)
|
||||
return;
|
||||
|
||||
new MousePositionAbsoluteInput { Position = touchEvent.ScreenSpaceTouch.Position }.Apply(osuInputManager.CurrentState, osuInputManager);
|
||||
}
|
||||
|
||||
protected override void OnTouchUp(TouchUpEvent e)
|
||||
{
|
||||
var tracked = trackedTouches.Single(t => t.Source == e.Touch.Source);
|
||||
|
||||
if (tracked.Action is OsuAction action)
|
||||
osuInputManager.KeyBindingContainer.TriggerReleased(action);
|
||||
|
||||
if (positionTrackingTouch == tracked)
|
||||
positionTrackingTouch = null;
|
||||
|
||||
trackedTouches.Remove(tracked);
|
||||
|
||||
base.OnTouchUp(e);
|
||||
}
|
||||
|
||||
private class TrackedTouch
|
||||
{
|
||||
public readonly TouchSource Source;
|
||||
|
||||
public OsuAction? Action;
|
||||
|
||||
public readonly bool DirectTouch;
|
||||
|
||||
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
||||
{
|
||||
Source = source;
|
||||
Action = action;
|
||||
DirectTouch = directTouch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
// 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.
|
||||
|
||||
using System.Diagnostics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
@ -9,30 +8,15 @@ using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield
|
||||
public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset<TaikoHitObject>
|
||||
{
|
||||
private DrawableTaikoRuleset? drawableTaikoRuleset;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||
{
|
||||
drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||
var drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||
drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false;
|
||||
|
||||
var playfield = (TaikoPlayfield)drawableRuleset.Playfield;
|
||||
playfield.ClassicHitTargetPosition.Value = true;
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
Debug.Assert(drawableTaikoRuleset != null);
|
||||
|
||||
// Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
|
||||
const float scroll_rate = 10;
|
||||
|
||||
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
||||
float ratio = drawableTaikoRuleset.DrawHeight / 480;
|
||||
|
||||
drawableTaikoRuleset.TimeRange.Value = (playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
new MultiMod(new TaikoModDoubleTime(), new TaikoModNightcore()),
|
||||
new TaikoModHidden(),
|
||||
new TaikoModFlashlight(),
|
||||
new ModAccuracyChallenge(),
|
||||
};
|
||||
|
||||
case ModType.Conversion:
|
||||
|
@ -43,7 +43,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
Direction.Value = ScrollingDirection.Left;
|
||||
TimeRange.Value = 7000;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -60,6 +59,19 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
KeyBindingInputManager.Add(new DrumTouchInputArea());
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// Taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened.
|
||||
const float scroll_rate = 10;
|
||||
|
||||
// Since the time range will depend on a positional value, it is referenced to the x480 pixel space.
|
||||
float ratio = DrawHeight / 480;
|
||||
|
||||
TimeRange.Value = (Playfield.HitObjectContainer.DrawWidth / ratio) * scroll_rate;
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
79
osu.Game.Tests/Editing/TestSceneSnappingNearZero.cs
Normal file
79
osu.Game.Tests/Editing/TestSceneSnappingNearZero.cs
Normal file
@ -0,0 +1,79 @@
|
||||
// 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.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
namespace osu.Game.Tests.Editing
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneSnappingNearZero
|
||||
{
|
||||
private readonly ControlPointInfo cpi = new ControlPointInfo();
|
||||
|
||||
[Test]
|
||||
public void TestOnZero()
|
||||
{
|
||||
test(0, 500, 0, 0);
|
||||
test(0, 500, 100, 0);
|
||||
test(0, 500, 250, 500);
|
||||
test(0, 500, 600, 500);
|
||||
|
||||
test(0, 500, -600, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlmostOnZero()
|
||||
{
|
||||
test(50, 500, 0, 50);
|
||||
test(50, 500, 50, 50);
|
||||
test(50, 500, 100, 50);
|
||||
test(50, 500, 299, 50);
|
||||
test(50, 500, 300, 550);
|
||||
|
||||
test(50, 500, -500, 50);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAlmostOnOne()
|
||||
{
|
||||
test(499, 500, -1, 499);
|
||||
test(499, 500, 0, 499);
|
||||
test(499, 500, 1, 499);
|
||||
test(499, 500, 499, 499);
|
||||
test(499, 500, 600, 499);
|
||||
test(499, 500, 800, 999);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnOne()
|
||||
{
|
||||
test(500, 500, -500, 0);
|
||||
test(500, 500, 0, 0);
|
||||
test(500, 500, 200, 0);
|
||||
test(500, 500, 400, 500);
|
||||
test(500, 500, 500, 500);
|
||||
test(500, 500, 600, 500);
|
||||
test(500, 500, 900, 1000);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNegative()
|
||||
{
|
||||
test(-600, 500, -600, 400);
|
||||
test(-600, 500, -100, 400);
|
||||
test(-600, 500, 0, 400);
|
||||
test(-600, 500, 200, 400);
|
||||
test(-600, 500, 400, 400);
|
||||
test(-600, 500, 600, 400);
|
||||
test(-600, 500, 1000, 900);
|
||||
}
|
||||
|
||||
private void test(double pointTime, double beatLength, double from, double expected)
|
||||
{
|
||||
cpi.Clear();
|
||||
cpi.Add(pointTime, new TimingControlPoint { BeatLength = beatLength });
|
||||
Assert.That(cpi.GetClosestSnappedTime(from, 1), Is.EqualTo(expected), $"From: {from}");
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
public void TestExactDivisors()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
|
||||
double[] divisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
|
||||
|
||||
@ -47,7 +47,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
public void TestExactDivisorsHighBPMStream()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(-50, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing)
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 50 }); // 1200 BPM 1/4 (limit testing)
|
||||
|
||||
// A 1/4 stream should land on 1/1, 1/2 and 1/4 divisors.
|
||||
double[] divisors = { 4, 4, 4, 4, 4, 4, 4, 4 };
|
||||
@ -60,7 +60,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
public void TestApproximateDivisors()
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(-1000, new TimingControlPoint { BeatLength = 1000 });
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
|
||||
double[] divisors = { 3.03d, 0.97d, 14, 13, 7.94d, 6.08d, 3.93d, 2.96d, 2.02d, 64 };
|
||||
double[] closestDivisors = { 3, 1, 16, 12, 8, 6, 4, 3, 2, 1 };
|
||||
@ -68,7 +68,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
assertClosestDivisors(divisors, closestDivisors, cpi);
|
||||
}
|
||||
|
||||
private void assertClosestDivisors(IReadOnlyList<double> divisors, IReadOnlyList<double> closestDivisors, ControlPointInfo cpi, double step = 1)
|
||||
private static void assertClosestDivisors(IReadOnlyList<double> divisors, IReadOnlyList<double> closestDivisors, ControlPointInfo cpi, double step = 1)
|
||||
{
|
||||
List<HitObject> hitobjects = new List<HitObject>();
|
||||
double offset = cpi.TimingPoints[0].Time;
|
||||
|
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/conflicting-filenames-skin.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20221024.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20221024.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230117.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-default-20230117.osk
Normal file
Binary file not shown.
@ -150,6 +150,8 @@ namespace osu.Game.Tests.Rulesets
|
||||
public IBindable<double> AggregateTempo => throw new NotImplementedException();
|
||||
|
||||
public int PlaybackConcurrency { get; set; }
|
||||
|
||||
public void AddExtension(string extension) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class TestShaderManager : ShaderManager
|
||||
|
@ -41,10 +41,14 @@ namespace osu.Game.Tests.Skins
|
||||
"Archives/modified-default-20220818.osk",
|
||||
// Covers longest combo counter
|
||||
"Archives/modified-default-20221012.osk",
|
||||
// Covers Argon variant of song progress bar
|
||||
"Archives/modified-argon-20221024.osk",
|
||||
// Covers TextElement and BeatmapInfoDrawable
|
||||
"Archives/modified-default-20221102.osk",
|
||||
// Covers BPM counter.
|
||||
"Archives/modified-default-20221205.osk"
|
||||
"Archives/modified-default-20221205.osk",
|
||||
// Covers judgement counter.
|
||||
"Archives/modified-default-20230117.osk"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,12 +1,11 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -20,29 +19,36 @@ namespace osu.Game.Tests.Skins
|
||||
public partial class TestSceneBeatmapSkinResources : OsuTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private IWorkingBeatmap beatmap;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[Test]
|
||||
public void TestRetrieveOggAudio()
|
||||
{
|
||||
var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-beatmap.osz"), "ogg-beatmap.osz")).GetResultSafely();
|
||||
IWorkingBeatmap beatmap = null!;
|
||||
|
||||
imported?.PerformRead(s =>
|
||||
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"ogg-beatmap.osz"));
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"sample")) != null);
|
||||
AddAssert("track is non-null", () =>
|
||||
{
|
||||
beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]);
|
||||
using (var track = beatmap.LoadTrack())
|
||||
return track is not TrackVirtual;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null);
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveOggTrack() => AddAssert("track is non-null", () =>
|
||||
public void TestRetrievalWithConflictingFilenames()
|
||||
{
|
||||
using (var track = beatmap.LoadTrack())
|
||||
return track is not TrackVirtual;
|
||||
});
|
||||
IWorkingBeatmap beatmap = null!;
|
||||
|
||||
AddStep("import beatmap", () => beatmap = importBeatmapFromArchives(@"conflicting-filenames-beatmap.osz"));
|
||||
AddAssert("texture is non-null", () => beatmap.Skin.GetTexture(@"spinner-osu") != null);
|
||||
AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo(@"spinner-osu")) != null);
|
||||
}
|
||||
|
||||
private IWorkingBeatmap importBeatmapFromArchives(string filename)
|
||||
{
|
||||
var imported = beatmaps.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
return imported.AsNonNull().PerformRead(s => beatmaps.GetWorkingBeatmap(s.Beatmaps[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,17 +31,24 @@ namespace osu.Game.Tests.Skins
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; } = null!;
|
||||
|
||||
private ISkin skin = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[Test]
|
||||
public void TestRetrieveOggSample()
|
||||
{
|
||||
var imported = skins.Import(new ImportTask(TestResources.OpenResource("Archives/ogg-skin.osk"), "ogg-skin.osk")).GetResultSafely();
|
||||
skin = imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
|
||||
ISkin skin = null!;
|
||||
|
||||
AddStep("import skin", () => skin = importSkinFromArchives(@"ogg-skin.osk"));
|
||||
AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"sample")) != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo("sample")) != null);
|
||||
public void TestRetrievalWithConflictingFilenames()
|
||||
{
|
||||
ISkin skin = null!;
|
||||
|
||||
AddStep("import skin", () => skin = importSkinFromArchives(@"conflicting-filenames-skin.osk"));
|
||||
AddAssert("texture is non-null", () => skin.GetTexture(@"spinner-osu") != null);
|
||||
AddAssert("sample is non-null", () => skin.GetSample(new SampleInfo(@"spinner-osu")) != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSampleRetrievalOrder()
|
||||
@ -78,6 +85,12 @@ namespace osu.Game.Tests.Skins
|
||||
});
|
||||
}
|
||||
|
||||
private Skin importSkinFromArchives(string filename)
|
||||
{
|
||||
var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
|
||||
}
|
||||
|
||||
private class TestSkin : Skin
|
||||
{
|
||||
public const string SAMPLE_NAME = "test-sample";
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
public partial class TestSceneTriangleBorderShader : OsuTestScene
|
||||
{
|
||||
private readonly TriangleBorder border;
|
||||
private readonly TestTriangle triangle;
|
||||
|
||||
public TestSceneTriangleBorderShader()
|
||||
{
|
||||
@ -25,11 +25,11 @@ namespace osu.Game.Tests.Visual.Background
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.DarkGreen
|
||||
},
|
||||
border = new TriangleBorder
|
||||
triangle = new TestTriangle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(100)
|
||||
Size = new Vector2(200)
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -38,12 +38,13 @@ namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddSliderStep("Thickness", 0f, 1f, 0.02f, t => border.Thickness = t);
|
||||
AddSliderStep("Thickness", 0f, 1f, 0.15f, t => triangle.Thickness = t);
|
||||
AddSliderStep("Texel size", 0f, 0.1f, 0f, t => triangle.TexelSize = t);
|
||||
}
|
||||
|
||||
private partial class TriangleBorder : Sprite
|
||||
private partial class TestTriangle : Sprite
|
||||
{
|
||||
private float thickness = 0.02f;
|
||||
private float thickness = 0.15f;
|
||||
|
||||
public float Thickness
|
||||
{
|
||||
@ -55,6 +56,18 @@ namespace osu.Game.Tests.Visual.Background
|
||||
}
|
||||
}
|
||||
|
||||
private float texelSize;
|
||||
|
||||
public float TexelSize
|
||||
{
|
||||
get => texelSize;
|
||||
set
|
||||
{
|
||||
texelSize = value;
|
||||
Invalidate(Invalidation.DrawNode);
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ShaderManager shaders, IRenderer renderer)
|
||||
{
|
||||
@ -62,29 +75,32 @@ namespace osu.Game.Tests.Visual.Background
|
||||
Texture = renderer.WhitePixel;
|
||||
}
|
||||
|
||||
protected override DrawNode CreateDrawNode() => new TriangleBorderDrawNode(this);
|
||||
protected override DrawNode CreateDrawNode() => new TriangleDrawNode(this);
|
||||
|
||||
private class TriangleBorderDrawNode : SpriteDrawNode
|
||||
private class TriangleDrawNode : SpriteDrawNode
|
||||
{
|
||||
public new TriangleBorder Source => (TriangleBorder)base.Source;
|
||||
public new TestTriangle Source => (TestTriangle)base.Source;
|
||||
|
||||
public TriangleBorderDrawNode(TriangleBorder source)
|
||||
public TriangleDrawNode(TestTriangle source)
|
||||
: base(source)
|
||||
{
|
||||
}
|
||||
|
||||
private float thickness;
|
||||
private float texelSize;
|
||||
|
||||
public override void ApplyState()
|
||||
{
|
||||
base.ApplyState();
|
||||
|
||||
thickness = Source.thickness;
|
||||
texelSize = Source.texelSize;
|
||||
}
|
||||
|
||||
public override void Draw(IRenderer renderer)
|
||||
{
|
||||
TextureShader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
||||
TextureShader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
||||
|
||||
base.Draw(renderer);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Framework.Graphics;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
@ -25,7 +26,10 @@ namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColourLight = Color4.White,
|
||||
ColourDark = Color4.Gray
|
||||
ColourDark = Color4.Gray,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(0.9f)
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -35,6 +39,8 @@ namespace osu.Game.Tests.Visual.Background
|
||||
base.LoadComplete();
|
||||
|
||||
AddSliderStep("Triangle scale", 0f, 10f, 1f, s => triangles.TriangleScale = s);
|
||||
AddSliderStep("Seed", 0, 1000, 0, s => triangles.Reset(s));
|
||||
AddToggleStep("Masking", m => triangles.Masking = m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,14 @@ using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
public partial class TestSceneTrianglesV2Background : OsuTestScene
|
||||
{
|
||||
private readonly TrianglesV2 triangles;
|
||||
private readonly TrianglesV2 maskedTriangles;
|
||||
private readonly Box box;
|
||||
|
||||
public TestSceneTrianglesV2Background()
|
||||
@ -31,12 +33,20 @@ namespace osu.Game.Tests.Visual.Background
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Masked"
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(500, 100),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Masking = true,
|
||||
CornerRadius = 40,
|
||||
Children = new Drawable[]
|
||||
@ -54,9 +64,43 @@ namespace osu.Game.Tests.Visual.Background
|
||||
}
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Non-masked"
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(500, 100),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Red
|
||||
},
|
||||
maskedTriangles = new TrianglesV2
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Gradient comparison box"
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(500, 100),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Masking = true,
|
||||
CornerRadius = 40,
|
||||
Child = box = new Box
|
||||
@ -75,14 +119,16 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
AddSliderStep("Spawn ratio", 0f, 10f, 1f, s =>
|
||||
{
|
||||
triangles.SpawnRatio = s;
|
||||
triangles.SpawnRatio = maskedTriangles.SpawnRatio = s;
|
||||
triangles.Reset(1234);
|
||||
maskedTriangles.Reset(1234);
|
||||
});
|
||||
AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = t);
|
||||
AddSliderStep("Thickness", 0f, 1f, 0.02f, t => triangles.Thickness = maskedTriangles.Thickness = t);
|
||||
|
||||
AddStep("White colour", () => box.Colour = triangles.Colour = Color4.White);
|
||||
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
|
||||
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
|
||||
AddStep("White colour", () => box.Colour = triangles.Colour = maskedTriangles.Colour = Color4.White);
|
||||
AddStep("Vertical gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientVertical(Color4.White, Color4.Red));
|
||||
AddStep("Horizontal gradient", () => box.Colour = triangles.Colour = maskedTriangles.Colour = ColourInfo.GradientHorizontal(Color4.White, Color4.Red));
|
||||
AddToggleStep("Masking", m => maskedTriangles.Masking = m);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("move mouse to common point", () =>
|
||||
{
|
||||
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
||||
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
||||
InputManager.MoveMouseTo(pos);
|
||||
});
|
||||
AddStep("right click", () => InputManager.Click(MouseButton.Right));
|
||||
|
@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
AddStep("move mouse to controlpoint", () =>
|
||||
{
|
||||
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
||||
var pos = blueprintContainer.ChildrenOfType<PathControlPointPiece<Slider>>().ElementAt(1).ScreenSpaceDrawQuad.Centre;
|
||||
InputManager.MoveMouseTo(pos);
|
||||
});
|
||||
AddStep("hold shift", () => InputManager.PressKey(Key.ShiftLeft));
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Rulesets;
|
||||
@ -20,6 +21,8 @@ using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Storyboards;
|
||||
@ -40,6 +43,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
private Guid currentBeatmapSetID => EditorBeatmap.BeatmapInfo.BeatmapSet?.ID ?? Guid.Empty;
|
||||
|
||||
public override void SetUpSteps()
|
||||
@ -222,7 +228,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
return beatmap != null
|
||||
&& beatmap.DifficultyName == secondDifficultyName
|
||||
&& set != null
|
||||
&& set.PerformRead(s => s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified));
|
||||
&& set.PerformRead(s =>
|
||||
s.Beatmaps.Count == 2 && s.Beatmaps.Any(b => b.DifficultyName == secondDifficultyName) && s.Beatmaps.All(b => s.Status == BeatmapOnlineStatus.LocallyModified));
|
||||
});
|
||||
}
|
||||
|
||||
@ -325,6 +332,56 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("old beatmap file not deleted", () => refetchedBeatmapSet.AsNonNull().PerformRead(s => s.Files.Count == 2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCopyDifficultyDoesNotChangeCollections()
|
||||
{
|
||||
string originalDifficultyName = Guid.NewGuid().ToString();
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = originalDifficultyName);
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
|
||||
string originalMd5 = string.Empty;
|
||||
BeatmapCollection collection = null!;
|
||||
|
||||
AddStep("setup a collection with original beatmap", () =>
|
||||
{
|
||||
collection = new BeatmapCollection("test copy");
|
||||
collection.BeatmapMD5Hashes.Add(originalMd5 = EditorBeatmap.BeatmapInfo.MD5Hash);
|
||||
|
||||
realm.Write(r =>
|
||||
{
|
||||
r.Add(collection);
|
||||
});
|
||||
});
|
||||
|
||||
AddAssert("collection contains original beatmap", () =>
|
||||
!string.IsNullOrEmpty(originalMd5) && collection.BeatmapMD5Hashes.Contains(originalMd5));
|
||||
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
AddStep("confirm creation as a copy", () => DialogOverlay.CurrentDialog.Buttons.ElementAt(1).TriggerClick());
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName != originalDifficultyName;
|
||||
});
|
||||
|
||||
AddStep("save without changes", () => Editor.Save());
|
||||
|
||||
AddAssert("collection still points to old beatmap", () => !collection.BeatmapMD5Hashes.Contains(EditorBeatmap.BeatmapInfo.MD5Hash)
|
||||
&& collection.BeatmapMD5Hashes.Contains(originalMd5));
|
||||
|
||||
AddStep("clean up collection", () =>
|
||||
{
|
||||
realm.Write(r =>
|
||||
{
|
||||
r.Remove(collection);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateMultipleNewDifficultiesSucceeds()
|
||||
{
|
||||
@ -395,5 +452,52 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateNewDifficultyForInconvertibleRuleset()
|
||||
{
|
||||
Guid setId = Guid.Empty;
|
||||
|
||||
AddStep("retrieve set ID", () => setId = EditorBeatmap.BeatmapInfo.BeatmapSet!.ID);
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddStep("try to create new taiko difficulty", () => Editor.CreateNewDifficulty(new TaikoRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName == "New Difficulty";
|
||||
});
|
||||
AddAssert("new difficulty persisted", () =>
|
||||
{
|
||||
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
|
||||
return set != null && set.PerformRead(s => s.Beatmaps.Count == 2 && s.Files.Count == 2);
|
||||
});
|
||||
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
|
||||
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = 0
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 1000
|
||||
}
|
||||
}));
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddStep("try to create new catch difficulty", () => Editor.CreateNewDifficulty(new CatchRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName == "New Difficulty (1)";
|
||||
});
|
||||
AddAssert("new difficulty persisted", () =>
|
||||
{
|
||||
var set = beatmapManager.QueryBeatmapSet(s => s.ID == setId);
|
||||
return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
double lastStarRating = 0;
|
||||
double lastLength = 0;
|
||||
|
||||
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(500, new TimingControlPoint()));
|
||||
AddStep("Add timing point", () => EditorBeatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 600 }));
|
||||
AddStep("Change to placement mode", () => InputManager.Key(Key.Number2));
|
||||
AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left));
|
||||
|
@ -20,7 +20,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
var implementation = skin is LegacySkin
|
||||
? CreateLegacyImplementation()
|
||||
: CreateDefaultImplementation();
|
||||
: skin is ArgonSkin
|
||||
? CreateArgonImplementation()
|
||||
: CreateDefaultImplementation();
|
||||
|
||||
implementation.Anchor = Anchor.Centre;
|
||||
implementation.Origin = Anchor.Centre;
|
||||
@ -29,6 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
|
||||
protected abstract Drawable CreateDefaultImplementation();
|
||||
protected virtual Drawable CreateArgonImplementation() => CreateDefaultImplementation();
|
||||
protected abstract Drawable CreateLegacyImplementation();
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ using osu.Game.Screens.Play.HUD;
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneSongProgressGraph : OsuTestScene
|
||||
public partial class TestSceneDefaultSongProgressGraph : OsuTestScene
|
||||
{
|
||||
private TestSongProgressGraph graph;
|
||||
|
||||
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
graph.Objects = objects;
|
||||
}
|
||||
|
||||
private partial class TestSongProgressGraph : SongProgressGraph
|
||||
private partial class TestSongProgressGraph : DefaultSongProgressGraph
|
||||
{
|
||||
public int CreationCount { get; private set; }
|
||||
|
@ -188,7 +188,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestInputDoesntWorkWhenHUDHidden()
|
||||
{
|
||||
SongProgressBar? getSongProgress() => hudOverlay.ChildrenOfType<SongProgressBar>().SingleOrDefault();
|
||||
ArgonSongProgress? getSongProgress() => hudOverlay.ChildrenOfType<ArgonSongProgress>().SingleOrDefault();
|
||||
|
||||
bool seeked = false;
|
||||
|
||||
@ -204,8 +204,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
Debug.Assert(progress != null);
|
||||
|
||||
progress.ShowHandle = true;
|
||||
progress.OnSeek += _ => seeked = true;
|
||||
progress.Interactive.Value = true;
|
||||
progress.ChildrenOfType<ArgonSongProgressBar>().Single().OnSeek += _ => seeked = true;
|
||||
});
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
|
182
osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
Normal file
182
osu.Game.Tests/Visual/Gameplay/TestSceneJudgementCounter.cs
Normal file
@ -0,0 +1,182 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneJudgementCounter : OsuTestScene
|
||||
{
|
||||
private ScoreProcessor scoreProcessor = null!;
|
||||
private JudgementTally judgementTally = null!;
|
||||
private TestJudgementCounterDisplay counterDisplay = null!;
|
||||
|
||||
private DependencyProvidingContainer content = null!;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
|
||||
private readonly Bindable<JudgementResult> lastJudgementResult = new Bindable<JudgementResult>();
|
||||
|
||||
private int iteration;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps() => AddStep("Create components", () =>
|
||||
{
|
||||
var ruleset = CreateRuleset();
|
||||
|
||||
Debug.Assert(ruleset != null);
|
||||
|
||||
scoreProcessor = new ScoreProcessor(ruleset);
|
||||
base.Content.Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
judgementTally = new JudgementTally(),
|
||||
content = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) },
|
||||
}
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
|
||||
private void applyOneJudgement(HitResult result)
|
||||
{
|
||||
lastJudgementResult.Value = new OsuJudgementResult(new HitObject
|
||||
{
|
||||
StartTime = iteration * 10000
|
||||
}, new OsuJudgement())
|
||||
{
|
||||
Type = result,
|
||||
};
|
||||
scoreProcessor.ApplyResult(lastJudgementResult.Value);
|
||||
|
||||
iteration++;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddJudgementsToCounters()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Great), 2);
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Miss), 2);
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.Meh), 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddWhilstHidden()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
|
||||
AddRepeatStep("Add judgement", () => applyOneJudgement(HitResult.LargeTickHit), 2);
|
||||
AddAssert("Check value added whilst hidden", () => hiddenCount() == 2);
|
||||
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeFlowDirection()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
|
||||
AddStep("Set direction vertical", () => counterDisplay.FlowDirection.Value = Direction.Vertical);
|
||||
AddStep("Set direction horizontal", () => counterDisplay.FlowDirection.Value = Direction.Horizontal);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleJudgementNames()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
|
||||
AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = false);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Assert hidden", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 0);
|
||||
AddStep("Hide judgement names", () => counterDisplay.ShowJudgementNames.Value = true);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Assert shown", () => counterDisplay.CounterFlow.Children.First().ResultName.Alpha == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHideMaxValue()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
|
||||
AddStep("Hide max judgement", () => counterDisplay.ShowMaxJudgement.Value = false);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Check max hidden", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().First().Alpha == 0);
|
||||
AddStep("Show max judgement", () => counterDisplay.ShowMaxJudgement.Value = true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaxValueStartsHidden()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay
|
||||
{
|
||||
ShowMaxJudgement = { Value = false }
|
||||
});
|
||||
AddAssert("Check max hidden", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().First().Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaxValueHiddenOnModeChange()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
|
||||
AddStep("Set max judgement to hide itself", () => counterDisplay.ShowMaxJudgement.Value = false);
|
||||
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Assert max judgement hidden", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().First().Alpha == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCycleDisplayModes()
|
||||
{
|
||||
AddStep("create counter", () => Child = counterDisplay = new TestJudgementCounterDisplay());
|
||||
|
||||
AddStep("Show basic judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Simple);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Check only basic", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Last().Alpha == 0);
|
||||
AddStep("Show normal judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.Normal);
|
||||
AddStep("Show all judgements", () => counterDisplay.Mode.Value = JudgementCounterDisplay.DisplayMode.All);
|
||||
AddWaitStep("wait some", 2);
|
||||
AddAssert("Check all visible", () => counterDisplay.CounterFlow.ChildrenOfType<JudgementCounter>().Last().Alpha == 1);
|
||||
}
|
||||
|
||||
private int hiddenCount()
|
||||
{
|
||||
var num = counterDisplay.CounterFlow.Children.First(child => child.Result.Type == HitResult.LargeTickHit);
|
||||
return num.Result.ResultCount.Value;
|
||||
}
|
||||
|
||||
private partial class TestJudgementCounterDisplay : JudgementCounterDisplay
|
||||
{
|
||||
public new FillFlowContainer<JudgementCounter> CounterFlow => base.CounterFlow;
|
||||
|
||||
public TestJudgementCounterDisplay()
|
||||
{
|
||||
Margin = new MarginPadding { Top = 100 };
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,9 +5,11 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
@ -34,6 +36,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
@ -43,6 +51,22 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
confirmClockRunning(true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTogglePauseViaBackAction()
|
||||
{
|
||||
pauseViaBackAction();
|
||||
pauseViaBackAction();
|
||||
confirmPausedWithNoOverlay();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTogglePauseViaPauseGameplayAction()
|
||||
{
|
||||
pauseViaPauseGameplayAction();
|
||||
pauseViaPauseGameplayAction();
|
||||
confirmPausedWithNoOverlay();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseWithLargeOffset()
|
||||
{
|
||||
@ -66,7 +90,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Player.OnUpdate += _ =>
|
||||
{
|
||||
double currentTime = Player.GameplayClockContainer.CurrentTime;
|
||||
alwaysGoingForward &= currentTime >= lastTime - 500;
|
||||
bool goingForward = currentTime >= lastTime - 500;
|
||||
|
||||
alwaysGoingForward &= goingForward;
|
||||
|
||||
if (!goingForward)
|
||||
Logger.Log($"Backwards time occurred ({currentTime:N1} -> {lastTime:N1})");
|
||||
|
||||
lastTime = currentTime;
|
||||
};
|
||||
});
|
||||
@ -144,7 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("disable pause support", () => Player.Configuration.AllowPause = false);
|
||||
|
||||
pauseFromUserExitKey();
|
||||
pauseViaBackAction();
|
||||
confirmExited();
|
||||
}
|
||||
|
||||
@ -156,7 +186,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
pauseAndConfirm();
|
||||
|
||||
resume();
|
||||
pauseFromUserExitKey();
|
||||
pauseViaBackAction();
|
||||
|
||||
confirmResumed();
|
||||
confirmNotExited();
|
||||
@ -170,7 +200,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
pauseAndConfirm();
|
||||
|
||||
resume();
|
||||
AddStep("pause via exit key", () => Player.ExitViaQuickExit());
|
||||
exitViaQuickExitAction();
|
||||
|
||||
confirmResumed();
|
||||
AddAssert("exited", () => !Player.IsCurrentScreen());
|
||||
@ -214,7 +244,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
confirmClockRunning(false);
|
||||
|
||||
AddStep("exit via user pause", () => Player.ExitViaPause());
|
||||
pauseViaBackAction();
|
||||
confirmExited();
|
||||
}
|
||||
|
||||
@ -224,11 +254,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||
|
||||
// will finish the fail animation and show the fail/pause screen.
|
||||
AddStep("attempt exit via pause key", () => Player.ExitViaPause());
|
||||
pauseViaBackAction();
|
||||
AddAssert("fail overlay shown", () => Player.FailOverlayVisible);
|
||||
|
||||
// will actually exit.
|
||||
AddStep("exit via pause key", () => Player.ExitViaPause());
|
||||
pauseViaBackAction();
|
||||
confirmExited();
|
||||
}
|
||||
|
||||
@ -245,7 +275,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public void TestQuickExitFromFailedGameplay()
|
||||
{
|
||||
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
|
||||
exitViaQuickExitAction();
|
||||
|
||||
confirmExited();
|
||||
}
|
||||
@ -261,7 +291,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestQuickExitFromGameplay()
|
||||
{
|
||||
AddStep("quick exit", () => Player.GameplayClockContainer.ChildrenOfType<HotkeyExitOverlay>().First().Action?.Invoke());
|
||||
exitViaQuickExitAction();
|
||||
|
||||
confirmExited();
|
||||
}
|
||||
@ -327,7 +357,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void pauseAndConfirm()
|
||||
{
|
||||
pauseFromUserExitKey();
|
||||
pauseViaBackAction();
|
||||
confirmPaused();
|
||||
}
|
||||
|
||||
@ -374,7 +404,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
private void restart() => AddStep("restart", () => Player.Restart());
|
||||
private void pauseFromUserExitKey() => AddStep("user pause", () => Player.ExitViaPause());
|
||||
private void pauseViaBackAction() => AddStep("press escape", () => InputManager.Key(Key.Escape));
|
||||
private void pauseViaPauseGameplayAction() => AddStep("press middle mouse", () => InputManager.Click(MouseButton.Middle));
|
||||
|
||||
private void exitViaQuickExitAction() => AddStep("press ctrl-tilde", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.PressKey(Key.Tilde);
|
||||
InputManager.ReleaseKey(Key.Tilde);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
|
||||
private void resume() => AddStep("resume", () => Player.Resume());
|
||||
|
||||
private void confirmPauseOverlayShown(bool isShown) =>
|
||||
@ -405,10 +445,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public bool PauseOverlayVisible => PauseOverlay.State.Value == Visibility.Visible;
|
||||
|
||||
public void ExitViaPause() => PerformExit(true);
|
||||
|
||||
public void ExitViaQuickExit() => PerformExit(false);
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnEntering(e);
|
||||
|
@ -2,14 +2,14 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
@ -28,50 +28,62 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time));
|
||||
FrameStabilityContainer frameStabilityContainer;
|
||||
|
||||
Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)
|
||||
{
|
||||
Child = frameStabilityContainer = new FrameStabilityContainer
|
||||
{
|
||||
MaxCatchUpFrames = 1
|
||||
}
|
||||
});
|
||||
|
||||
Dependencies.CacheAs<IGameplayClock>(gameplayClockContainer);
|
||||
Dependencies.CacheAs<IFrameStableClock>(frameStabilityContainer);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetupSteps()
|
||||
{
|
||||
AddStep("reset clock", () => gameplayClockContainer.Reset());
|
||||
AddStep("set hit objects", setHitObjects);
|
||||
AddStep("set hit objects", () => this.ChildrenOfType<SongProgress>().ForEach(progress => progress.Objects = Beatmap.Value.Beatmap.HitObjects));
|
||||
AddStep("hook seeking", () =>
|
||||
{
|
||||
applyToDefaultProgress(d => d.ChildrenOfType<DefaultSongProgressBar>().Single().OnSeek += t => gameplayClockContainer.Seek(t));
|
||||
applyToArgonProgress(d => d.ChildrenOfType<ArgonSongProgressBar>().Single().OnSeek += t => gameplayClockContainer.Seek(t));
|
||||
});
|
||||
AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
|
||||
AddStep("start", () => gameplayClockContainer.Start());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplay()
|
||||
public void TestBasic()
|
||||
{
|
||||
AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time));
|
||||
AddStep("start", gameplayClockContainer.Start);
|
||||
AddToggleStep("toggle seeking", b =>
|
||||
{
|
||||
applyToDefaultProgress(s => s.Interactive.Value = b);
|
||||
applyToArgonProgress(s => s.Interactive.Value = b);
|
||||
});
|
||||
|
||||
AddToggleStep("toggle graph", b =>
|
||||
{
|
||||
applyToDefaultProgress(s => s.ShowGraph.Value = b);
|
||||
applyToArgonProgress(s => s.ShowGraph.Value = b);
|
||||
});
|
||||
|
||||
AddStep("stop", gameplayClockContainer.Stop);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleSeeking()
|
||||
{
|
||||
void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
|
||||
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
|
||||
private void applyToArgonProgress(Action<ArgonSongProgress> action) =>
|
||||
this.ChildrenOfType<ArgonSongProgress>().ForEach(action);
|
||||
|
||||
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
|
||||
AddStep("hide graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = false));
|
||||
AddStep("disallow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = false));
|
||||
AddStep("allow seeking", () => applyToDefaultProgress(s => s.AllowSeeking.Value = true));
|
||||
AddStep("show graph", () => applyToDefaultProgress(s => s.ShowGraph.Value = true));
|
||||
}
|
||||
|
||||
private void setHitObjects()
|
||||
{
|
||||
var objects = new List<HitObject>();
|
||||
for (double i = 0; i < 5000; i++)
|
||||
objects.Add(new HitObject { StartTime = i });
|
||||
|
||||
this.ChildrenOfType<SongProgress>().ForEach(progress => progress.Objects = objects);
|
||||
}
|
||||
private void applyToDefaultProgress(Action<DefaultSongProgress> action) =>
|
||||
this.ChildrenOfType<DefaultSongProgress>().ForEach(action);
|
||||
|
||||
protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress();
|
||||
|
||||
protected override Drawable CreateArgonImplementation() => new ArgonSongProgress();
|
||||
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacySongProgress();
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
@ -85,6 +86,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
|
||||
|
||||
AddUntilStep("wait for join", () => MultiplayerClient.RoomJoined);
|
||||
AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType<OngoingOperationTracker>().Single().InProgress.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -15,7 +15,6 @@ using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -33,6 +32,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleItemExpiredAfterGameplay()
|
||||
{
|
||||
RunGameplay();
|
||||
|
||||
AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1);
|
||||
AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
|
||||
AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemAddedToTheEndOfQueue()
|
||||
{
|
||||
@ -45,16 +54,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleItemExpiredAfterGameplay()
|
||||
{
|
||||
RunGameplay();
|
||||
|
||||
AddUntilStep("playlist has only one item", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 1);
|
||||
AddUntilStep("playlist item is expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
|
||||
AddUntilStep("last item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNextItemSelectedAfterGameplayFinish()
|
||||
{
|
||||
@ -140,7 +139,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null);
|
||||
AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType<OngoingOperationTracker>().Single().InProgress.Value);
|
||||
|
||||
if (ruleset != null)
|
||||
AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
|
||||
|
@ -27,6 +27,28 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("first item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNewItemCreatedAfterGameplayFinished()
|
||||
{
|
||||
RunGameplay();
|
||||
|
||||
AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
|
||||
AddUntilStep("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
|
||||
AddUntilStep("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false);
|
||||
AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSettingsUpdatedWhenChangingQueueMode()
|
||||
{
|
||||
AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings
|
||||
{
|
||||
QueueMode = QueueMode.AllPlayers
|
||||
}).WaitSafely());
|
||||
|
||||
AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemStillSelectedAfterChangeToSameBeatmap()
|
||||
{
|
||||
@ -43,17 +65,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNewItemCreatedAfterGameplayFinished()
|
||||
{
|
||||
RunGameplay();
|
||||
|
||||
AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
|
||||
AddUntilStep("first playlist item expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[0].Expired == true);
|
||||
AddUntilStep("second playlist item not expired", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Expired == false);
|
||||
AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlyLastItemChangedAfterGameplayFinished()
|
||||
{
|
||||
@ -68,17 +79,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("second playlist item changed", () => MultiplayerClient.ClientAPIRoom?.Playlist[1].Beatmap != firstBeatmap);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSettingsUpdatedWhenChangingQueueMode()
|
||||
{
|
||||
AddStep("change queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings
|
||||
{
|
||||
QueueMode = QueueMode.AllPlayers
|
||||
}).WaitSafely());
|
||||
|
||||
AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddItemsAsHost()
|
||||
{
|
||||
@ -104,7 +104,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||
|
||||
BeatmapInfo otherBeatmap = null;
|
||||
AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType<OngoingOperationTracker>().Single().InProgress.Value);
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
|
||||
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
@ -120,7 +119,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for ongoing operation to complete", () => !(CurrentScreen as OnlinePlayScreen).ChildrenOfType<OngoingOperationTracker>().Single().InProgress.Value);
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("all interactive elements removed", () => this.ChildrenOfType<Player>().All(p =>
|
||||
!p.ChildrenOfType<PlayerSettingsOverlay>().Any() &&
|
||||
!p.ChildrenOfType<HoldForMenuButton>().Any() &&
|
||||
p.ChildrenOfType<SongProgressBar>().SingleOrDefault()?.ShowHandle == false));
|
||||
p.ChildrenOfType<ArgonSongProgressBar>().SingleOrDefault()?.Interactive == false));
|
||||
|
||||
AddStep("restore config hud visibility", () => config.SetValue(OsuSetting.HUDVisibilityMode, originalConfigValue));
|
||||
}
|
||||
|
@ -377,6 +377,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest]
|
||||
/*
|
||||
* On a slight investigation, this is occurring due to the ready button
|
||||
* not receiving the click input generated by the manual input manager.
|
||||
*
|
||||
* TearDown : System.TimeoutException : "wait for ready button to be enabled" timed out
|
||||
* --TearDown
|
||||
* at osu.Framework.Testing.Drawables.Steps.UntilStepButton.<>c__DisplayClass11_0.<.ctor>b__0()
|
||||
* at osu.Framework.Testing.Drawables.Steps.StepButton.PerformStep(Boolean userTriggered)
|
||||
* at osu.Framework.Testing.TestScene.runNextStep(Action onCompletion, Action`1 onError, Func`2 stopCondition)
|
||||
*/
|
||||
public void TestUserSetToIdleWhenBeatmapDeleted()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
@ -398,6 +409,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect()
|
||||
{
|
||||
PlaylistItem? item = null;
|
||||
@ -438,6 +450,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect()
|
||||
{
|
||||
PlaylistItem? item = null;
|
||||
@ -478,6 +491,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestPlayStartsWithCorrectModsWhileAtSongSelect()
|
||||
{
|
||||
PlaylistItem? item = null;
|
||||
@ -651,6 +665,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestGameplayFlow()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
@ -678,6 +693,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestGameplayExitFlow()
|
||||
{
|
||||
Bindable<double>? holdDelay = null;
|
||||
@ -715,6 +731,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestGameplayDoesntStartWithNonLoadedUser()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
@ -796,6 +813,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestSpectatingStateResetOnBackButtonDuringGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
@ -831,6 +849,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestSpectatingStateNotResetOnBackButtonOutsideOfGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
@ -869,6 +888,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestItemAddedByOtherUserDuringGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
@ -899,6 +919,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest] // See above
|
||||
public void TestItemAddedAndDeletedByOtherUserDuringGameplay()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
|
@ -563,6 +563,18 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("featured artist filter is off", () => !getBeatmapListingOverlay().ChildrenOfType<BeatmapSearchGeneralFilterRow>().First().Current.Contains(SearchGeneral.FeaturedArtists));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatmapListingLinkSearchOnInitialOpen()
|
||||
{
|
||||
BeatmapListingOverlay getBeatmapListingOverlay() => Game.ChildrenOfType<BeatmapListingOverlay>().FirstOrDefault();
|
||||
|
||||
AddStep("open beatmap overlay with test query", () => Game.SearchBeatmapSet("test"));
|
||||
|
||||
AddUntilStep("wait for beatmap overlay to load", () => getBeatmapListingOverlay()?.State.Value == Visibility.Visible);
|
||||
|
||||
AddAssert("beatmap overlay sorted by relevance", () => getBeatmapListingOverlay().ChildrenOfType<BeatmapListingSortTabControl>().Single().Current.Value == SortCriteria.Relevance);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMainOverlaysClosesNotificationOverlay()
|
||||
{
|
||||
|
@ -219,7 +219,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("Click gameplay scene button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text == "Gameplay"));
|
||||
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<SkinEditorSceneLibrary.SceneButton>().First(b => b.Text.ToString() == "Gameplay"));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestBeatmapLink()
|
||||
{
|
||||
AddUntilStep("Beatmap overlay displayed", () => Game.ChildrenOfType<BeatmapSetOverlay>().FirstOrDefault()?.State.Value == Visibility.Visible);
|
||||
AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType<BeatmapPicker>().FirstOrDefault()?.Beatmap.Value.OnlineID == requested_beatmap_id);
|
||||
AddUntilStep("Beatmap overlay showing content", () => Game.ChildrenOfType<BeatmapPicker>().FirstOrDefault()?.Beatmap.Value?.OnlineID == requested_beatmap_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -21,6 +23,7 @@ using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Comments;
|
||||
using osu.Game.Overlays.Comments.Buttons;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
@ -259,7 +262,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("Nothing happened", () => this.ChildrenOfType<ReportCommentPopover>().Any());
|
||||
AddStep("Set report data", () =>
|
||||
{
|
||||
var field = this.ChildrenOfType<OsuTextBox>().Single();
|
||||
var field = this.ChildrenOfType<ReportCommentPopover>().Single().ChildrenOfType<OsuTextBox>().Single();
|
||||
field.Current.Value = report_text;
|
||||
var reason = this.ChildrenOfType<OsuEnumDropdown<CommentReportReason>>().Single();
|
||||
reason.Current.Value = CommentReportReason.Other;
|
||||
@ -278,6 +281,93 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddAssert("Request is correct", () => request != null && request.CommentID == 2 && request.Comment == report_text && request.Reason == CommentReportReason.Other);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReply()
|
||||
{
|
||||
addTestComments();
|
||||
DrawableComment? targetComment = null;
|
||||
AddUntilStep("Comment exists", () =>
|
||||
{
|
||||
var comments = this.ChildrenOfType<DrawableComment>();
|
||||
targetComment = comments.SingleOrDefault(x => x.Comment.Id == 2);
|
||||
return targetComment != null;
|
||||
});
|
||||
AddStep("Setup request handling", () =>
|
||||
{
|
||||
requestLock.Reset();
|
||||
|
||||
dummyAPI.HandleRequest = r =>
|
||||
{
|
||||
if (!(r is CommentPostRequest req))
|
||||
return false;
|
||||
|
||||
if (req.ParentCommentId != 2)
|
||||
throw new ArgumentException("Wrong parent ID in request!");
|
||||
|
||||
if (req.CommentableId != 123 || req.Commentable != CommentableType.Beatmapset)
|
||||
throw new ArgumentException("Wrong commentable data in request!");
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
requestLock.Wait(10000);
|
||||
req.TriggerSuccess(new CommentBundle
|
||||
{
|
||||
Comments = new List<Comment>
|
||||
{
|
||||
new Comment
|
||||
{
|
||||
Id = 98,
|
||||
Message = req.Message,
|
||||
LegacyName = "FirstUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 98,
|
||||
ParentId = req.ParentCommentId,
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
});
|
||||
AddStep("Click reply button", () =>
|
||||
{
|
||||
var btn = targetComment.ChildrenOfType<LinkFlowContainer>().Skip(1).First();
|
||||
var texts = btn.ChildrenOfType<SpriteText>();
|
||||
InputManager.MoveMouseTo(texts.Skip(1).First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("There is 0 replies", () =>
|
||||
{
|
||||
var replLabel = targetComment.ChildrenOfType<ShowRepliesButton>().First().ChildrenOfType<SpriteText>().First();
|
||||
return replLabel.Text.ToString().Contains('0') && targetComment!.Comment.RepliesCount == 0;
|
||||
});
|
||||
AddStep("Focus field", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(targetComment.ChildrenOfType<TextBox>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddStep("Enter text", () =>
|
||||
{
|
||||
targetComment.ChildrenOfType<TextBox>().First().Current.Value = "random reply";
|
||||
});
|
||||
AddStep("Submit", () =>
|
||||
{
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddStep("Complete request", () => requestLock.Set());
|
||||
AddUntilStep("There is 1 reply", () =>
|
||||
{
|
||||
var replLabel = targetComment.ChildrenOfType<ShowRepliesButton>().First().ChildrenOfType<SpriteText>().First();
|
||||
return replLabel.Text.ToString().Contains('1') && targetComment!.Comment.RepliesCount == 1;
|
||||
});
|
||||
AddUntilStep("Submitted comment shown", () =>
|
||||
{
|
||||
var r = targetComment.ChildrenOfType<DrawableComment>().Skip(1).FirstOrDefault();
|
||||
return r != null && r.Comment.Message == "random reply";
|
||||
});
|
||||
}
|
||||
|
||||
private void addTestComments()
|
||||
{
|
||||
AddStep("set up response", () =>
|
||||
|
@ -1,8 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -11,7 +9,10 @@ using osu.Game.Overlays;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@ -27,15 +28,20 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
private CommentsContainer commentsContainer;
|
||||
private CommentsContainer commentsContainer = null!;
|
||||
|
||||
private TextBox editorTextBox = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = new BasicScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = commentsContainer = new CommentsContainer()
|
||||
});
|
||||
};
|
||||
editorTextBox = commentsContainer.ChildrenOfType<TextBox>().First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestIdleState()
|
||||
@ -126,6 +132,44 @@ namespace osu.Game.Tests.Visual.Online
|
||||
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPost()
|
||||
{
|
||||
setUpCommentsResponse(new CommentBundle { Comments = new List<Comment>() });
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
AddAssert("no comments placeholder shown", () => commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
|
||||
|
||||
setUpPostResponse();
|
||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||
AddStep("submit", () => commentsContainer.ChildrenOfType<RoundedButton>().First().TriggerClick());
|
||||
|
||||
AddUntilStep("comment sent", () =>
|
||||
{
|
||||
string writtenText = editorTextBox.Current.Value;
|
||||
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
|
||||
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
|
||||
});
|
||||
AddAssert("no comments placeholder removed", () => !commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPostWithExistingComments()
|
||||
{
|
||||
setUpCommentsResponse(getExampleComments());
|
||||
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
|
||||
|
||||
setUpPostResponse();
|
||||
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
|
||||
AddStep("submit", () => commentsContainer.ChildrenOfType<CommentEditor>().Single().ChildrenOfType<RoundedButton>().First().TriggerClick());
|
||||
|
||||
AddUntilStep("comment sent", () =>
|
||||
{
|
||||
string writtenText = editorTextBox.Current.Value;
|
||||
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
|
||||
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpCommentsResponse(CommentBundle commentBundle)
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
@ -139,7 +183,33 @@ namespace osu.Game.Tests.Visual.Online
|
||||
};
|
||||
});
|
||||
|
||||
private CommentBundle getExampleComments(bool withPinned = false)
|
||||
private void setUpPostResponse()
|
||||
=> AddStep("set up response", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
{
|
||||
if (!(request is CommentPostRequest req))
|
||||
return false;
|
||||
|
||||
req.TriggerSuccess(new CommentBundle
|
||||
{
|
||||
Comments = new List<Comment>
|
||||
{
|
||||
new Comment
|
||||
{
|
||||
Id = 98,
|
||||
Message = req.Message,
|
||||
LegacyName = "FirstUser",
|
||||
CreatedAt = DateTimeOffset.Now,
|
||||
VotesCount = 98,
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
};
|
||||
});
|
||||
|
||||
private static CommentBundle getExampleComments(bool withPinned = false)
|
||||
{
|
||||
var bundle = new CommentBundle
|
||||
{
|
||||
|
54
osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs
Normal file
54
osu.Game.Tests/Visual/Online/TestSceneLevelBadge.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// 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.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays.Profile.Header.Components;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneLevelBadge : OsuTestScene
|
||||
{
|
||||
public TestSceneLevelBadge()
|
||||
{
|
||||
var levels = new List<UserStatistics.LevelInfo>();
|
||||
|
||||
for (int i = 0; i < 11; i++)
|
||||
{
|
||||
levels.Add(new UserStatistics.LevelInfo
|
||||
{
|
||||
Current = i * 10
|
||||
});
|
||||
}
|
||||
|
||||
levels.Add(new UserStatistics.LevelInfo { Current = 101 });
|
||||
levels.Add(new UserStatistics.LevelInfo { Current = 105 });
|
||||
levels.Add(new UserStatistics.LevelInfo { Current = 110 });
|
||||
levels.Add(new UserStatistics.LevelInfo { Current = 115 });
|
||||
levels.Add(new UserStatistics.LevelInfo { Current = 120 });
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer<LevelBadge>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Full,
|
||||
Spacing = new Vector2(5),
|
||||
ChildrenEnumerable = levels.Select(level => new LevelBadge
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(60),
|
||||
LevelInfo = { Value = level }
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Profile;
|
||||
@ -19,6 +20,9 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager configManager { get; set; } = null!;
|
||||
|
||||
private ProfileHeader header = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
@ -33,6 +37,22 @@ namespace osu.Game.Tests.Visual.Online
|
||||
AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestProfileCoverExpanded()
|
||||
{
|
||||
AddStep("Set cover to expanded", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, true));
|
||||
AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo));
|
||||
AddUntilStep("Cover is expanded", () => header.ChildrenOfType<UserCoverBackground>().Single().Height, () => Is.GreaterThan(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestProfileCoverCollapsed()
|
||||
{
|
||||
AddStep("Set cover to collapsed", () => configManager.SetValue(OsuSetting.ProfileCoverExpanded, false));
|
||||
AddStep("Show example user", () => header.User.Value = new UserProfileData(TestSceneUserProfileOverlay.TEST_USER, new OsuRuleset().RulesetInfo));
|
||||
AddUntilStep("Cover is collapsed", () => header.ChildrenOfType<UserCoverBackground>().Single().Height, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlineState()
|
||||
{
|
||||
|
@ -82,13 +82,14 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Username = @"Somebody",
|
||||
Id = 1,
|
||||
CountryCode = CountryCode.Unknown,
|
||||
CountryCode = CountryCode.JP,
|
||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
|
||||
JoinDate = DateTimeOffset.Now.AddDays(-1),
|
||||
LastVisit = DateTimeOffset.Now,
|
||||
Groups = new[]
|
||||
{
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "mania" } },
|
||||
new APIUserGroup { Colour = "#A347EB", ShortName = "BN", Name = "Beatmap Nominators", Playmodes = new[] { "osu", "taiko" } }
|
||||
},
|
||||
ProfileOrder = new[]
|
||||
@ -142,7 +143,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
Available = 10,
|
||||
Total = 50
|
||||
}
|
||||
},
|
||||
SupportLevel = 2,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ needs_cleanup: true
|
||||
AddStep("Add absolute image", () =>
|
||||
{
|
||||
markdownContainer.CurrentPath = "https://dev.ppy.sh";
|
||||
markdownContainer.Text = "![intro](/wiki/Interface/img/intro-screen.jpg)";
|
||||
markdownContainer.Text = "![intro](/wiki/images/Client/Interface/img/intro-screen.jpg)";
|
||||
});
|
||||
}
|
||||
|
||||
@ -133,7 +133,7 @@ needs_cleanup: true
|
||||
AddStep("Add relative image", () =>
|
||||
{
|
||||
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
|
||||
markdownContainer.Text = "![intro](img/intro-screen.jpg)";
|
||||
markdownContainer.Text = "![intro](../images/Client/Interface/img/intro-screen.jpg)";
|
||||
});
|
||||
}
|
||||
|
||||
@ -145,7 +145,7 @@ needs_cleanup: true
|
||||
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/Interface/";
|
||||
markdownContainer.Text = @"Line before image
|
||||
|
||||
![play menu](img/play-menu.jpg ""Main Menu in osu!"")
|
||||
![play menu](../images/Client/Interface/img/play-menu.jpg ""Main Menu in osu!"")
|
||||
|
||||
Line after image";
|
||||
});
|
||||
@ -170,12 +170,12 @@ Line after image";
|
||||
markdownContainer.Text = @"
|
||||
| Image | Name | Effect |
|
||||
| :-: | :-: | :-- |
|
||||
| ![](/wiki/Skinning/Interface/img/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. |
|
||||
| ![](/wiki/Skinning/Interface/img/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. |
|
||||
| ![](/wiki/images/shared/judgement/osu!/hit300.png ""300"") | 300 | A possible score when tapping a hit circle precisely on time, completing a Slider and keeping the cursor over every tick, or completing a Spinner with the Spinner Metre full. A score of 300 appears in an blue score by default. Scoring nothing except 300s in a beatmap will award the player with the SS or SSH grade. |
|
||||
| ![](/wiki/images/shared/judgement/osu!/hit300g.png ""Geki"") | (激) Geki | A term from Ouendan, called Elite Beat! in EBA. Appears when playing the last element in a combo in which the player has scored only 300s. Getting a Geki will give a sizable boost to the Life Bar. By default, it is blue. |
|
||||
| ![](/wiki/images/shared/judgement/osu!/hit100.png ""100"") | 100 | A possible score one can get when tapping a Hit Object slightly late or early, completing a Slider and missing a number of ticks, or completing a Spinner with the Spinner Meter almost full. A score of 100 appears in a green score by default. When very skilled players test a beatmap and they get a lot of 100s, this may mean that the beatmap does not have correct timing. |
|
||||
| ![](/wiki/images/shared/judgement/osu!/hit300k.png ""300 Katu"") ![](/wiki/Skinning/Interface/img/hit100k.png ""100 Katu"") | (喝) Katu or Katsu | A term from Ouendan, called Beat! in EBA. Appears when playing the last element in a combo in which the player has scored at least one 100, but no 50s or misses. Getting a Katu will give a small boost to the Life Bar. By default, it is coloured green or blue depending on whether the Katu itself is a 100 or a 300. |
|
||||
| ![](/wiki/images/shared/judgement/osu!/hit50.png ""50"") | 50 | A possible score one can get when tapping a hit circle rather early or late but not early or late enough to cause a miss, completing a Slider and missing a lot of ticks, or completing a Spinner with the Spinner Metre close to full. A score of 50 appears in a orange score by default. Scoring a 50 in a combo will prevent the appearance of a Katu or a Geki at the combo's end. |
|
||||
| ![](/wiki/images/shared/judgement/osu!/hit0.png ""Miss"") | Miss | A possible score one can get when not tapping a hit circle or too early (based on OD and AR, it may *shake* instead), not tapping or holding the Slider at least once, or completing a Spinner with low Spinner Metre fill. Scoring a Miss will reset the current combo to 0 and will prevent the appearance of a Katu or a Geki at the combo's end. |
|
||||
";
|
||||
});
|
||||
}
|
||||
@ -186,7 +186,7 @@ Line after image";
|
||||
AddStep("Add image", () =>
|
||||
{
|
||||
markdownContainer.CurrentPath = "https://dev.ppy.sh/wiki/osu!_Program_Files/";
|
||||
markdownContainer.Text = "![](img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")";
|
||||
markdownContainer.Text = "![](../images/Client/Program_files/img/file_structure.jpg \"The file structure of osu!'s installation folder, on Windows and macOS\")";
|
||||
});
|
||||
|
||||
AddUntilStep("Wait image to load", () => markdownContainer.ChildrenOfType<DelayedLoadWrapper>().First().DelayedLoadCompleted);
|
||||
@ -270,6 +270,30 @@ Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCodeSyntax()
|
||||
{
|
||||
AddStep("set content", () =>
|
||||
{
|
||||
markdownContainer.Text = @"
|
||||
This is a paragraph containing `inline code` synatax.
|
||||
Oh wow I do love the `WikiMarkdownContainer`, it is very cool!
|
||||
|
||||
This is a line before the fenced code block:
|
||||
```csharp
|
||||
public class WikiMarkdownContainer : MarkdownContainer
|
||||
{
|
||||
public WikiMarkdownContainer()
|
||||
{
|
||||
this.foo = bar;
|
||||
}
|
||||
}
|
||||
```
|
||||
This is a line after the fenced code block!
|
||||
";
|
||||
});
|
||||
}
|
||||
|
||||
private partial class TestMarkdownContainer : WikiMarkdownContainer
|
||||
{
|
||||
public LinkInline Link;
|
||||
|
@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
AddUntilStep("Leaderboard shows two aggregate scores", () => match.ChildrenOfType<MatchLeaderboardScore>().Count(s => s.ScoreText.Text != "0") == 2);
|
||||
|
||||
AddStep("start match", () => match.ChildrenOfType<PlaylistsReadyButton>().First().TriggerClick());
|
||||
ClickButtonWhenEnabled<PlaylistsReadyButton>();
|
||||
AddUntilStep("player loader loaded", () => Stack.CurrentScreen is PlayerLoader);
|
||||
}
|
||||
|
||||
|
@ -580,10 +580,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
/// Ensures stability is maintained on different sort modes for items with equal properties.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSortingStability()
|
||||
public void TestSortingStabilityDateAdded()
|
||||
{
|
||||
var sets = new List<BeatmapSetInfo>();
|
||||
int idOffset = 0;
|
||||
|
||||
AddStep("Populuate beatmap sets", () =>
|
||||
{
|
||||
@ -593,38 +592,34 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||
|
||||
set.DateAdded = DateTimeOffset.FromUnixTimeSeconds(i);
|
||||
|
||||
// only need to set the first as they are a shared reference.
|
||||
var beatmap = set.Beatmaps.First();
|
||||
|
||||
beatmap.Metadata.Artist = $"artist {i / 2}";
|
||||
beatmap.Metadata.Title = $"title {9 - i}";
|
||||
beatmap.Metadata.Artist = "a";
|
||||
beatmap.Metadata.Title = "b";
|
||||
|
||||
sets.Add(set);
|
||||
}
|
||||
|
||||
idOffset = sets.First().OnlineID;
|
||||
});
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
|
||||
|
||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||
AddAssert("Items are in reverse order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + sets.Count - index - 1).All(b => b));
|
||||
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
AddAssert("Items reset to original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b));
|
||||
AddAssert("Items remain in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures stability is maintained on different sort modes while a new item is added to the carousel.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSortingStabilityWithNewItems()
|
||||
public void TestSortingStabilityWithRemovedAndReaddedItem()
|
||||
{
|
||||
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||
int idOffset = 0;
|
||||
|
||||
AddStep("Populuate beatmap sets", () =>
|
||||
{
|
||||
@ -640,16 +635,68 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
beatmap.Metadata.Artist = "same artist";
|
||||
beatmap.Metadata.Title = "same title";
|
||||
|
||||
// testing the case where DateAdded happens to equal (quite rare).
|
||||
set.DateAdded = DateTimeOffset.UnixEpoch;
|
||||
|
||||
sets.Add(set);
|
||||
}
|
||||
|
||||
idOffset = sets.First().OnlineID;
|
||||
});
|
||||
|
||||
Guid[] originalOrder = null!;
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
assertOriginalOrderMaintained();
|
||||
|
||||
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||
|
||||
AddStep("Remove item", () => carousel.RemoveBeatmapSet(sets[1]));
|
||||
AddStep("Re-add item", () => carousel.UpdateBeatmapSet(sets[1]));
|
||||
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
|
||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures stability is maintained on different sort modes while a new item is added to the carousel.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestSortingStabilityWithNewItems()
|
||||
{
|
||||
List<BeatmapSetInfo> sets = new List<BeatmapSetInfo>();
|
||||
|
||||
AddStep("Populuate beatmap sets", () =>
|
||||
{
|
||||
sets.Clear();
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
var set = TestResources.CreateTestBeatmapSetInfo(3);
|
||||
|
||||
// only need to set the first as they are a shared reference.
|
||||
var beatmap = set.Beatmaps.First();
|
||||
|
||||
beatmap.Metadata.Artist = "same artist";
|
||||
beatmap.Metadata.Title = "same title";
|
||||
|
||||
// testing the case where DateAdded happens to equal (quite rare).
|
||||
set.DateAdded = DateTimeOffset.UnixEpoch;
|
||||
|
||||
sets.Add(set);
|
||||
}
|
||||
});
|
||||
|
||||
Guid[] originalOrder = null!;
|
||||
|
||||
loadBeatmaps(sets);
|
||||
|
||||
AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false));
|
||||
|
||||
AddAssert("Items in descending added order", () => carousel.BeatmapSets.Select(s => s.DateAdded), () => Is.Ordered.Descending);
|
||||
AddStep("Save order", () => originalOrder = carousel.BeatmapSets.Select(s => s.ID).ToArray());
|
||||
|
||||
AddStep("Add new item", () =>
|
||||
{
|
||||
@ -661,19 +708,18 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
beatmap.Metadata.Artist = "same artist";
|
||||
beatmap.Metadata.Title = "same title";
|
||||
|
||||
set.DateAdded = DateTimeOffset.FromUnixTimeSeconds(1);
|
||||
|
||||
carousel.UpdateBeatmapSet(set);
|
||||
|
||||
// add set to expected ordering
|
||||
originalOrder = originalOrder.Prepend(set.ID).ToArray();
|
||||
});
|
||||
|
||||
assertOriginalOrderMaintained();
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
|
||||
AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false));
|
||||
assertOriginalOrderMaintained();
|
||||
|
||||
void assertOriginalOrderMaintained()
|
||||
{
|
||||
AddAssert("Items remain in original order",
|
||||
() => carousel.BeatmapSets.Select(s => s.OnlineID), () => Is.EqualTo(carousel.BeatmapSets.Select((set, index) => idOffset + index)));
|
||||
}
|
||||
AddAssert("Order didn't change", () => carousel.BeatmapSets.Select(s => s.ID), () => Is.EqualTo(originalOrder));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -1064,6 +1064,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
AddAssert("options enabled", () => songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
|
||||
AddStep("delete all beatmaps", () => manager.Delete());
|
||||
AddUntilStep("wait for no beatmap", () => Beatmap.IsDefault);
|
||||
AddAssert("options disabled", () => !songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@ -52,7 +53,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("beatmap density with granularity of 200", () => beatmapDensity());
|
||||
AddStep("beatmap density with granularity of 300", () => beatmapDensity(300));
|
||||
AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().ToArray());
|
||||
AddStep("change colour", () =>
|
||||
AddStep("change tier colours", () =>
|
||||
{
|
||||
graph.TierColours = new[]
|
||||
{
|
||||
@ -62,7 +63,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Colour4.Blue
|
||||
};
|
||||
});
|
||||
AddStep("reset colour", () =>
|
||||
AddStep("reset tier colours", () =>
|
||||
{
|
||||
graph.TierColours = new[]
|
||||
{
|
||||
@ -74,6 +75,12 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Colour4.Green
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("set graph colour to blue", () => graph.Colour = Colour4.Blue);
|
||||
AddStep("set graph colour to transparent", () => graph.Colour = Colour4.Transparent);
|
||||
AddStep("set graph colour to vertical gradient", () => graph.Colour = ColourInfo.GradientVertical(Colour4.White, Colour4.Black));
|
||||
AddStep("set graph colour to horizontal gradient", () => graph.Colour = ColourInfo.GradientHorizontal(Colour4.White, Colour4.Black));
|
||||
AddStep("reset graph colour", () => graph.Colour = Colour4.White);
|
||||
}
|
||||
|
||||
private void sinFunction(int size = 100)
|
||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Tests
|
||||
{
|
||||
Colour = OsuColour.Gray(0.5f),
|
||||
Depth = 10
|
||||
}, AddInternal);
|
||||
}, Add);
|
||||
|
||||
// Have to construct this here, rather than in the constructor, because
|
||||
// we depend on some dependencies to be loaded within OsuGameBase.load().
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Tournament
|
||||
|
||||
private async Task checkForChanges()
|
||||
{
|
||||
string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder()).ConfigureAwait(false);
|
||||
string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder()).ConfigureAwait(true);
|
||||
|
||||
// If a save hasn't been triggered by the user yet, populate the initial value
|
||||
lastSerialisedLadder ??= serialisedLadder;
|
||||
|
@ -113,7 +113,7 @@ namespace osu.Game.Tournament.Screens.Setup
|
||||
new LabelledDropdown<RulesetInfo>
|
||||
{
|
||||
Label = "Ruleset",
|
||||
Description = "Decides what stats are displayed and which ranks are retrieved for players.",
|
||||
Description = "Decides what stats are displayed and which ranks are retrieved for players. This requires a restart to reload data for an existing bracket.",
|
||||
Items = rulesets.AvailableRulesets,
|
||||
Current = LadderInfo.Ruleset,
|
||||
},
|
||||
|
@ -16,6 +16,7 @@ using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Tournament.IO;
|
||||
@ -44,12 +45,20 @@ namespace osu.Game.Tournament
|
||||
return dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
}
|
||||
|
||||
public override EndpointConfiguration CreateEndpoints()
|
||||
{
|
||||
if (UseDevelopmentServer)
|
||||
return base.CreateEndpoints();
|
||||
|
||||
return new ProductionEndpointConfiguration();
|
||||
}
|
||||
|
||||
private TournamentSpriteText initialisationText;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(Storage baseStorage)
|
||||
{
|
||||
AddInternal(initialisationText = new TournamentSpriteText
|
||||
Add(initialisationText = new TournamentSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -156,9 +165,21 @@ namespace osu.Game.Tournament
|
||||
addedInfo |= addSeedingBeatmaps();
|
||||
|
||||
if (addedInfo)
|
||||
SaveChanges();
|
||||
saveChanges();
|
||||
|
||||
ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value);
|
||||
|
||||
ladder.Ruleset.BindValueChanged(r =>
|
||||
{
|
||||
// Refetch player rank data on next startup as the ruleset has changed.
|
||||
foreach (var team in ladder.Teams)
|
||||
{
|
||||
foreach (var player in team.Players)
|
||||
player.Rank = null;
|
||||
}
|
||||
|
||||
SaveChanges();
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -306,6 +327,11 @@ namespace osu.Game.Tournament
|
||||
return;
|
||||
}
|
||||
|
||||
saveChanges();
|
||||
}
|
||||
|
||||
private void saveChanges()
|
||||
{
|
||||
foreach (var r in ladder.Rounds)
|
||||
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user