1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-13 22:35:35 +08:00

Merge remote-tracking branch 'upstream/master' into fix-ios-import

This commit is contained in:
Dean Herbert 2019-10-31 17:47:17 +09:00
commit 7207209404
429 changed files with 11898 additions and 3548 deletions

View File

@ -0,0 +1,8 @@
---
name: Mobile Report
about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!)
---
**PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them.
If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome.
Otherwise, please check back in the future when the focus of development shifts towards mobile!

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<browser url="http://localhost:5000" />
<method v="2">
<option name="Build" enabled="true" />

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="--tournament" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<method v="2">
<option name="Build" enabled="true" />
</method>

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<method v="2">
<option name="Build" enabled="true" />
</method>

View File

@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
@ -12,7 +12,7 @@
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.2" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
<method v="2">
<option name="Build" enabled="true" />
</method>

32
.vscode/launch.json vendored
View File

@ -6,13 +6,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll"
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -23,13 +23,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll"
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -40,13 +40,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll"
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -56,13 +56,13 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll"
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tests (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -73,14 +73,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -91,14 +91,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build osu! (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -109,14 +109,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll",
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Debug)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"
@ -127,14 +127,14 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll",
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
"--tournament"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build tournament tests (Release)",
"linux": {
"env": {
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
}
},
"console": "internalConsole"

2
.vscode/tasks.json vendored
View File

@ -95,7 +95,7 @@
"problemMatcher": "$msCompile"
},
{
"label": "Restore (netcoreapp2.2)",
"label": "Restore (netcoreapp3.0)",
"type": "shell",
"command": "dotnet",
"args": [

View File

@ -1,11 +1,11 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.0)
addressable (2.6.0)
public_suffix (>= 2.0.2, < 4.0)
CFPropertyList (3.0.1)
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
atomos (0.1.3)
babosa (1.0.2)
babosa (1.0.3)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
@ -18,7 +18,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.5)
emoji_regex (1.0.1)
excon (0.66.0)
excon (0.67.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@ -26,8 +26,8 @@ GEM
http-cookie (~> 1.0.0)
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.5)
fastlane (2.129.0)
fastimage (2.1.7)
fastlane (2.133.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@ -37,9 +37,9 @@ GEM
dotenv (>= 2.1.1, < 3.0.0)
emoji_regex (>= 0.1, < 2.0)
excon (>= 0.45.0, < 1.0.0)
faraday (~> 0.9)
faraday (< 0.16.0)
faraday-cookie_jar (~> 0.0.6)
faraday_middleware (~> 0.9)
faraday_middleware (< 0.16.0)
fastimage (>= 2.1.0, < 3.0.0)
gh_inspector (>= 1.1.2, < 2.0.0)
google-api-client (>= 0.21.2, < 0.24.0)
@ -52,7 +52,7 @@ GEM
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
public_suffix (~> 2.0.0)
rubyzip (>= 1.2.2, < 2.0.0)
rubyzip (>= 1.3.0, < 2.0.0)
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
@ -77,9 +77,9 @@ GEM
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
signet (~> 0.9)
google-cloud-core (1.3.0)
google-cloud-core (1.3.1)
google-cloud-env (~> 1.0)
google-cloud-env (1.2.0)
google-cloud-env (1.2.1)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
@ -100,9 +100,9 @@ GEM
json (2.2.0)
jwt (2.1.0)
memoist (0.16.0)
mime-types (3.2.2)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0331)
mime-types-data (3.2019.1009)
mini_magick (4.9.5)
mini_portile2 (2.4.0)
multi_json (1.13.1)
@ -121,14 +121,14 @@ GEM
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.2.3)
rubyzip (1.3.0)
security (0.1.3)
signet (0.11.0)
signet (0.12.0)
addressable (~> 2.3)
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
simctl (1.6.5)
simctl (1.6.6)
CFPropertyList
naturally
slack-notifier (2.3.2)

View File

@ -18,7 +18,7 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
@ -31,12 +31,10 @@ If you are not interested in developing the game, you can still consume our [bin
**Latest build:**
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) |
| ------------- | ------------- |
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- |
- **Linux** users are recommended to self-compile until we have official deployment in place.
- **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full).
- **Android** users can self-compile, and expect a public beta soon.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
@ -80,7 +78,7 @@ On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build dir
For example, you can run osu! with the following command:
```shell
LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop
LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp3.0" dotnet run --project osu.Desktop
```
### Testing with resource/framework modifications

View File

@ -1,6 +1,6 @@
clone_depth: 1
version: '{branch}-{build}'
image: Previous Visual Studio 2017
image: Visual Studio 2019 Preview
test: off
build_script:
- cmd: PowerShell -Version 2.0 .\build.ps1

View File

@ -1,6 +1,6 @@
clone_depth: 1
version: '{build}'
image: Previous Visual Studio 2017
image: Visual Studio 2019 Preview
test: off
skip_non_tags: true
build_script:

65
build.ps1 Normal file → Executable file
View File

@ -1,39 +1,5 @@
##########################################################################
# This is a customized Cake bootstrapper script for PowerShell.
##########################################################################
<#
.SYNOPSIS
This is a Powershell script to bootstrap a Cake build.
.DESCRIPTION
This Powershell script restores NuGet tools (including Cake)
and execute your Cake build script with the parameters you provide.
.PARAMETER Script
The build script to execute.
.PARAMETER Target
The build script target to run.
.PARAMETER Configuration
The build configuration to use.
.PARAMETER Verbosity
Specifies the amount of information to be displayed.
.PARAMETER ShowDescription
Shows description about tasks.
.PARAMETER DryRun
Performs a dry run.
.PARAMETER ScriptArgs
Remaining arguments are added here.
.LINK
https://cakebuild.net
#>
[CmdletBinding()]
Param(
[string]$Script = "build.cake",
[string]$Target,
[string]$Configuration,
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
@ -45,27 +11,8 @@ Param(
[string[]]$ScriptArgs
)
Write-Host "Preparing to run build script..."
# Determine the script root for resolving other paths.
if(!$PSScriptRoot) {
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
}
# Resolve the paths for resources used for debugging.
$BUILD_DIR = Join-Path $PSScriptRoot "build"
$TOOLS_DIR = Join-Path $BUILD_DIR "tools"
$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj"
# Install the required tools locally.
Write-Host "Restoring cake tools..."
Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
# Find the Cake executable
$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName
# Build Cake arguments
$cakeArguments = @("$Script");
$cakeArguments = "";
if ($Target) { $cakeArguments += "-target=$Target" }
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
@ -74,9 +21,7 @@ if ($DryRun) { $cakeArguments += "-dryrun" }
if ($Experimental) { $cakeArguments += "-experimental" }
$cakeArguments += $ScriptArgs
# Start Cake
Write-Host "Running build script..."
Push-Location -Path $BUILD_DIR
Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
Pop-Location
exit $LASTEXITCODE
dotnet tool install Cake.Tool --global --version 0.35.0
dotnet cake ./build/build.cake --bootstrap
dotnet cake ./build/build.cake $cakeArguments
exit $LASTEXITCODE

View File

@ -1,18 +1,5 @@
#!/usr/bin/env bash
##########################################################################
# This is a customized Cake bootstrapper script for Shell.
##########################################################################
echo "Preparing to run build script..."
cd build
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
TOOLS_DIR=$SCRIPT_DIR/tools
CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
SCRIPT="build.cake"
CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj"
echo "Installing Cake.Tool..."
dotnet tool install Cake.Tool --global --version 0.35.0
# Parse arguments.
CAKE_ARGUMENTS=()
@ -25,14 +12,6 @@ for i in "$@"; do
shift
done
# Install the required tools locally.
echo "Restoring cake tools..."
dotnet restore $CAKE_CSPROJ --packages $TOOLS_DIR > /dev/null 2>&1
# Search for the CakeBuild binary.
CAKE_BINARY=$(find $CAKE_BINARY_PATH -name "Cake.dll")
# Start Cake
echo "Running build script..."
dotnet "$CAKE_BINARY" $SCRIPT "${CAKE_ARGUMENTS[@]}"
dotnet cake ./build/build.cake --bootstrap
dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}"

View File

@ -1,5 +1,5 @@
#addin "nuget:?package=CodeFileSanity&version=0.0.21"
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1"
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();

View File

@ -1,11 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<PackAsTool>true</PackAsTool>
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Cake" Version="0.34.1" />
<PackageReference Include="Cake.CoreCLR" Version="0.34.1" />
</ItemGroup>
</Project>

View File

@ -1,5 +1,83 @@
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.sln'
)
souyuz(
build_configuration: 'Release',
solution_path: 'osu.Android.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.Android.sln',
version: options[:version],
build: android_build,
)
end
end
platform :ios do
desc 'Deploy to testflight'
lane :beta do |options|

View File

@ -15,6 +15,30 @@ Install _fastlane_ using
or alternatively using `brew cask install fastlane`
# Available Actions
## Android
### android beta
```
fastlane android beta
```
Deploy to play store
### android build_github
```
fastlane android build_github
```
Deploy to github release
### android build
```
fastlane android build
```
Compile the project
### android update_version
```
fastlane android update_version
```
----
## iOS
### ios beta
```

View File

@ -51,7 +51,6 @@
<None Include="$(MSBuildThisFileDirectory)\osu.licenseheader">
<Link>osu.licenseheader</Link>
</None>
<AndroidNativeLibrary Include="$(OutputPath)\**\*.so" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
@ -62,7 +61,7 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.911.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1029.0" />
</ItemGroup>
</Project>

View File

@ -16,6 +16,11 @@ namespace osu.Android
protected override void OnCreate(Bundle savedInstanceState)
{
// The default current directory on android is '/'.
// On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
// In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
base.OnCreate(savedInstanceState);
Window.AddFlags(WindowManagerFlags.Fullscreen);

View File

@ -4,11 +4,37 @@
using System;
using Android.App;
using osu.Game;
using osu.Game.Updater;
namespace osu.Android
{
public class OsuGameAndroid : OsuGame
{
public override Version AssemblyVersion => new Version(Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0).VersionName);
public override Version AssemblyVersion
{
get
{
var packageInfo = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0);
try
{
string versionName = packageInfo.VersionCode.ToString();
// undo play store version garbling
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
}
catch
{
}
return new Version(packageInfo.VersionName);
}
}
protected override void LoadComplete()
{
base.LoadComplete();
Add(new SimpleUpdateManager());
}
}
}
}

View File

@ -17,6 +17,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform.Windows;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Updater;
namespace osu.Desktop
{

View File

@ -8,29 +8,18 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osuTK;
using osuTK.Graphics;
namespace osu.Desktop.Overlays
{
public class VersionManager : OverlayContainer
public class VersionManager : VisibilityContainer
{
private OsuConfigManager config;
private OsuGameBase game;
private NotificationOverlay notificationOverlay;
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config)
private void load(OsuColour colours, TextureStore textures, OsuGameBase game)
{
notificationOverlay = notification;
this.config = config;
this.game = game;
AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
@ -85,48 +74,6 @@ namespace osu.Desktop.Overlays
};
}
protected override void LoadComplete()
{
base.LoadComplete();
var version = game.Version;
var lastVersion = config.Get<string>(OsuSetting.Version);
if (game.IsDeployedBuild && version != lastVersion)
{
config.Set(OsuSetting.Version, version);
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion))
notificationOverlay.Post(new UpdateCompleteNotification(version));
}
}
private class UpdateCompleteNotification : SimpleNotification
{
private readonly string version;
public UpdateCompleteNotification(string version)
{
this.version = version;
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay)
{
Icon = FontAwesome.Solid.CheckSquare;
IconBackgound.Colour = colours.BlueDark;
Activated = delegate
{
notificationOverlay.Hide();
changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
return true;
};
}
}
protected override void PopIn()
{
this.FadeIn(1400, Easing.OutQuint);

View File

@ -20,7 +20,7 @@ using LogLevel = Splat.LogLevel;
namespace osu.Desktop.Updater
{
public class SquirrelUpdateManager : Component
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
{
private UpdateManager updateManager;
private NotificationOverlay notificationOverlay;

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project">
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.0</TargetFramework>
<OutputType>WinExe</OutputType>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@ -23,10 +23,10 @@
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="System.IO.Packaging" Version="4.5.0" />
<PackageReference Include="System.IO.Packaging" Version="4.6.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />

View File

@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",

View File

@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void load()
{
var controlPointInfo = new ControlPointInfo();
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
controlPointInfo.Add(0, new TimingControlPoint());
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
{

View File

@ -2,14 +2,14 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />

View File

@ -1,6 +1,7 @@
// 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 osu.Framework.Bindables;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.Beatmaps;
@ -37,9 +38,21 @@ namespace osu.Game.Rulesets.Catch.Objects
public int ComboOffset { get; set; }
public int IndexInCurrentCombo { get; set; }
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
public int ComboIndex { get; set; }
public int IndexInCurrentCombo
{
get => IndexInCurrentComboBindable.Value;
set => IndexInCurrentComboBindable.Value = value;
}
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
public int ComboIndex
{
get => ComboIndexBindable.Value;
set => ComboIndexBindable.Value = value;
}
/// <summary>
/// Difference between the distance to the next object
@ -48,10 +61,16 @@ namespace osu.Game.Rulesets.Catch.Objects
/// </summary>
public float DistanceToHyperDash { get; set; }
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
/// <summary>
/// The next fruit starts a new combo. Used for explodey.
/// </summary>
public virtual bool LastInCombo { get; set; }
public virtual bool LastInCombo
{
get => LastInComboBindable.Value;
set => LastInComboBindable.Value = value;
}
public float Scale { get; set; } = 1;
@ -74,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
public enum FruitVisualRepresentation

View File

@ -2,35 +2,50 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
{
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
private readonly Container bananaContainer;
public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
: base(s)
{
this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft;
X = 0;
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
foreach (var b in s.NestedHitObjects.Cast<Banana>())
AddNested(createDrawableRepresentation?.Invoke(b));
}
protected override void AddNested(DrawableHitObject h)
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
bananaContainer.Add(h);
base.AddNested(h);
base.AddNestedHitObject(hitObject);
bananaContainer.Add(hitObject);
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
bananaContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case Banana banana:
return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
}
return base.CreateNestedHitObject(hitObject);
}
}
}

View File

@ -2,38 +2,50 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
{
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
private readonly Container dropletContainer;
public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
: base(s)
{
this.createDrawableRepresentation = createDrawableRepresentation;
RelativeSizeAxes = Axes.Both;
Origin = Anchor.BottomLeft;
X = 0;
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
foreach (var o in s.NestedHitObjects.Cast<CatchHitObject>())
AddNested(createDrawableRepresentation?.Invoke(o));
}
protected override void AddNested(DrawableHitObject h)
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
var catchObject = (DrawableCatchHitObject)h;
base.AddNestedHitObject(hitObject);
dropletContainer.Add(hitObject);
}
catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
dropletContainer.Clear();
}
dropletContainer.Add(h);
base.AddNested(h);
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case CatchHitObject catchObject:
return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
}
return base.CreateNestedHitObject(hitObject);
}
}
}

View File

@ -37,9 +37,6 @@ namespace osu.Game.Rulesets.Catch.Replays
float lastPosition = 0.5f;
double lastTime = 0;
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
addFrame(-100000, lastPosition);
void moveToNext(CatchHitObject h)
{
float positionChange = Math.Abs(lastPosition - h.X);
@ -127,6 +124,10 @@ namespace osu.Game.Rulesets.Catch.Replays
private void addFrame(double time, float? position = null, bool dashing = false)
{
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
if (Replay.Frames.Count == 0)
Replay.Frames.Add(new CatchReplayFrame(time - 1, position, false, null));
var last = currentFrame;
currentFrame = new CatchReplayFrame(time, position, dashing, last);
Replay.Frames.Add(currentFrame);

View File

@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",

View File

@ -27,8 +27,13 @@ namespace osu.Game.Rulesets.Mania.Tests
[Cached(typeof(IReadOnlyList<Mod>))]
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
[Cached(typeof(IScrollingInfo))]
private IScrollingInfo scrollingInfo;
protected ManiaPlacementBlueprintTestScene()
{
scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo;
Add(column = new Column(0)
{
Anchor = Anchor.Centre,
@ -36,15 +41,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll
});
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo);
return dependencies;
AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip());
}
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };

View File

@ -16,6 +16,11 @@ namespace osu.Game.Rulesets.Mania.Tests
[HeadlessTest]
public class TestSceneAutoGeneration : OsuTestScene
{
/// <summary>
/// The number of frames which are generated at the start of a replay regardless of hitobject content.
/// </summary>
private const int frame_offset = 1;
[Test]
public void TestSingleNote()
{
@ -28,11 +33,11 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
}
[Test]
@ -49,11 +54,11 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Special1), "Special1 has not been released");
}
[Test]
@ -69,11 +74,11 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
}
[Test]
@ -91,11 +96,13 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
Assert.IsTrue(generated.Frames.Count == frame_offset + 2, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
}
[Test]
@ -112,15 +119,15 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 1].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[frame_offset + 2].Time, "Incorrect second note hit time");
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
}
[Test]
@ -139,16 +146,16 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released");
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
Assert.IsTrue(generated.Frames.Count == frame_offset + 4, "Replay must have 4 generated frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[frame_offset + 1].Time, "Incorrect second note hit time");
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 3].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has been released");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 3], ManiaAction.Key2), "Key2 has not been released");
}
[Test]
@ -166,14 +173,14 @@ namespace osu.Game.Rulesets.Mania.Tests
var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released");
Assert.IsTrue(generated.Frames.Count == frame_offset + 3, "Replay must have 3 generated frames");
Assert.AreEqual(1000, generated.Frames[frame_offset].Time, "Incorrect first note hit time");
Assert.AreEqual(3000, generated.Frames[frame_offset + 1].Time, "Incorrect second note press time + first note release time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[frame_offset + 2].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[frame_offset], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[frame_offset + 1], ManiaAction.Key2), "Key2 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[frame_offset + 2], ManiaAction.Key2), "Key2 has not been released");
}
private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));

View File

@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -39,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = { Value = OsuColour.Gray(0.3f) }
}
};
AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject));
}
protected override void Update()
@ -51,7 +52,5 @@ namespace osu.Game.Rulesets.Mania.Tests
nested.Y = (float)(-finalPosition * content.DrawHeight);
}
}
protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject);
}
}

View File

@ -5,7 +5,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
@ -17,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
{
private readonly DrawableNote drawableObject;
protected override Container<Drawable> Content => content ?? base.Content;
private readonly Container content;
@ -27,6 +24,8 @@ namespace osu.Game.Rulesets.Mania.Tests
var note = new Note { Column = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
DrawableNote drawableObject;
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
{
Anchor = Anchor.Centre,
@ -34,8 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests
Size = new Vector2(50, 20),
Child = drawableObject = new DrawableNote(note)
};
}
protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject);
AddBlueprint(new NoteSelectionBlueprint(drawableObject));
}
}
}

View File

@ -15,7 +15,6 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
@ -67,6 +66,8 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre));
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.TopCentre));
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.BottomCentre));
AddStep("flip direction", () =>
{
@ -76,10 +77,14 @@ namespace osu.Game.Rulesets.Mania.Tests
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre));
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre));
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.BottomCentre));
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre));
}
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
private void createNote()
{
foreach (var stage in stages)

View File

@ -2,14 +2,14 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />

View File

@ -3,7 +3,6 @@
using System;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects;
using osuTK;
@ -49,13 +48,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
private double originalStartTime;
protected override bool OnMouseMove(MouseMoveEvent e)
public override void UpdatePosition(Vector2 screenSpacePosition)
{
base.OnMouseMove(e);
base.UpdatePosition(screenSpacePosition);
if (PlacementBegun)
{
var endTime = TimeAt(e.ScreenSpaceMousePosition);
var endTime = TimeAt(screenSpacePosition);
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
HitObject.Duration = Math.Abs(endTime - originalStartTime);
@ -65,10 +64,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
headPiece.Width = tailPiece.Width = SnappedWidth;
headPiece.X = tailPiece.X = SnappedMousePosition.X;
originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
}
return true;
}
}
}

View File

@ -16,45 +16,51 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
{
public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly BodyPiece body;
[Resolved]
private OsuColour colours { get; set; }
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
: base(hold)
{
InternalChildren = new Drawable[]
{
new HoldNoteNoteSelectionBlueprint(hold.Head),
new HoldNoteNoteSelectionBlueprint(hold.Tail),
body = new BodyPiece
{
AccentColour = Color4.Transparent
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, IScrollingInfo scrollingInfo)
private void load(IScrollingInfo scrollingInfo)
{
body.BorderColour = colours.Yellow;
direction.BindTo(scrollingInfo.Direction);
}
protected override void LoadComplete()
{
base.LoadComplete();
InternalChildren = new Drawable[]
{
new HoldNoteNoteSelectionBlueprint(DrawableObject.Head),
new HoldNoteNoteSelectionBlueprint(DrawableObject.Tail),
new BodyPiece
{
AccentColour = Color4.Transparent,
BorderColour = colours.Yellow
},
};
}
protected override void Update()
{
base.Update();
Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight);
Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight);
// This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
// When scrolling upwards our origin is already at the top of the head note (which is the intended location),
// but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
if (direction.Value == ScrollingDirection.Down)
Y -= HitObject.Tail.DrawHeight;
Y -= DrawableObject.Tail.DrawHeight;
}
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
@ -71,10 +77,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
Anchor = HitObject.Anchor;
Origin = HitObject.Origin;
Anchor = DrawableObject.Anchor;
Origin = DrawableObject.Origin;
Position = HitObject.DrawPosition;
Position = DrawableObject.DrawPosition;
}
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.

View File

@ -49,10 +49,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
if (Column == null)
return base.OnMouseDown(e);
HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
HitObject.Column = Column.Index;
BeginPlacement();
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition));
return true;
}
@ -62,19 +60,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return base.OnMouseUp(e);
}
protected override bool OnMouseMove(MouseMoveEvent e)
public override void UpdatePosition(Vector2 screenSpacePosition)
{
if (!PlacementBegun)
Column = ColumnAt(e.ScreenSpaceMousePosition);
Column = ColumnAt(screenSpacePosition);
if (Column == null) return false;
if (Column == null) return;
SnappedWidth = Column.DrawWidth;
// Snap to the column
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y);
return true;
SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
}
protected double TimeAt(Vector2 screenSpacePosition)
@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
// so we need to flip the vertical coordinate in the hitobject container's space
var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y;
var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
@ -103,16 +100,58 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
scrollingInfo.TimeRange.Value,
Column.HitObjectContainer.DrawHeight);
return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y;
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
pos = Column.HitObjectContainer.DrawHeight - pos;
return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
}
protected Column ColumnAt(Vector2 screenSpacePosition)
=> composer.ColumnAt(applyPositionOffset(screenSpacePosition, false));
=> composer.ColumnAt(screenSpacePosition);
private Vector2 applyPositionOffset(Vector2 position, bool reverse)
/// <summary>
/// Converts a mouse position to a hitobject position.
/// </summary>
/// <remarks>
/// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
/// </remarks>
/// <param name="mousePosition">The mouse position.</param>
/// <returns>The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.</returns>
private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
{
position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2;
return position;
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
mousePosition.Y += NotePiece.NOTE_HEIGHT / 2;
break;
}
return mousePosition;
}
/// <summary>
/// Converts a hitobject position to a mouse position.
/// </summary>
/// <param name="hitObjectPosition">The hitobject position.</param>
/// <returns>The resulting mouse position, anchored at the centre of the hitobject.</returns>
private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
{
switch (scrollingInfo.Direction.Value)
{
case ScrollingDirection.Up:
hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2;
break;
case ScrollingDirection.Down:
hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2;
break;
}
return hitObjectPosition;
}
}
}

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
public Vector2 ScreenSpaceDragPosition { get; private set; }
public Vector2 DragPosition { get; private set; }
protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject;
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
protected IClock EditorClock { get; private set; }
@ -28,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved]
private IManiaHitObjectComposer composer { get; set; }
public ManiaSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
public ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
RelativeSizeAxes = Axes.None;
}
@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero));
Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero));
}
protected override bool OnMouseDown(MouseDownEvent e)
{
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
return base.OnMouseDown(e);
}
@ -60,20 +60,20 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
var result = base.OnDrag(e);
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
return result;
}
public override void Show()
{
HitObject.AlwaysAlive = true;
DrawableObject.AlwaysAlive = true;
base.Show();
}
public override void Hide()
{
HitObject.AlwaysAlive = false;
DrawableObject.AlwaysAlive = false;
base.Hide();
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
base.Update();
Size = HitObject.DrawSize;
Size = DrawableObject.DrawSize;
}
}
}

View File

@ -3,9 +3,7 @@
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Input.Events;
using osu.Framework.Timing;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.UI;
@ -31,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Edit
editorClock = clock;
}
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
public override void HandleMovement(MoveSelectionEvent moveEvent)
{
adjustOrigins((ManiaSelectionBlueprint)blueprint);
performDragMovement(dragEvent);
performColumnMovement(dragEvent);
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
base.HandleDrag(blueprint, dragEvent);
adjustOrigins(maniaBlueprint);
performDragMovement(moveEvent);
performColumnMovement(lastColumn, moveEvent);
base.HandleMovement(moveEvent);
}
/// <summary>
@ -47,41 +48,44 @@ namespace osu.Game.Rulesets.Mania.Edit
/// <param name="reference">The <see cref="ManiaSelectionBlueprint"/> that received the drag event.</param>
private void adjustOrigins(ManiaSelectionBlueprint reference)
{
var referenceParent = (HitObjectContainer)reference.HitObject.Parent;
var referenceParent = (HitObjectContainer)reference.DrawableObject.Parent;
float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y;
float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.DrawableObject.OriginPosition.Y;
float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin;
// Flip the vertical coordinate space when scrolling downwards
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = targetPosition - referenceParent.DrawHeight;
float movementDelta = targetPosition - reference.HitObject.Position.Y;
float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
foreach (var b in SelectedBlueprints.OfType<ManiaSelectionBlueprint>())
b.HitObject.Y += movementDelta;
b.DrawableObject.Y += movementDelta;
}
private void performDragMovement(DragEvent dragEvent)
private void performDragMovement(MoveSelectionEvent moveEvent)
{
float delta = moveEvent.InstantDelta.Y;
// When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen.
// This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height.
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight;
foreach (var b in SelectedBlueprints)
{
var hitObject = b.HitObject;
var hitObject = b.DrawableObject;
var objectParent = (HitObjectContainer)hitObject.Parent;
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame
// without the position having been updated by the parenting ScrollingHitObjectContainer
hitObject.Y += dragEvent.Delta.Y;
// StartTime could be used to adjust the position if only one movement event was received per frame.
// However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events
hitObject.Y += delta;
float targetPosition;
float targetPosition = hitObject.Position.Y;
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
// so we need to flip the vertical coordinate in the hitobject container's space
// The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
targetPosition = -hitObject.Position.Y;
else
targetPosition = hitObject.Position.Y;
targetPosition = -targetPosition;
objectParent.Remove(hitObject);
@ -94,14 +98,13 @@ namespace osu.Game.Rulesets.Mania.Edit
}
}
private void performColumnMovement(DragEvent dragEvent)
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
{
var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition);
var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition);
if (lastColumn == null || currentColumn == null)
var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
if (currentColumn == null)
return;
int columnDelta = currentColumn.Index - lastColumn.Index;
int columnDelta = currentColumn.Index - lastColumn;
if (columnDelta == 0)
return;

View File

@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks
{
public abstract class ManiaSelectionBlueprint : SelectionBlueprint
{
protected ManiaSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
RelativeSizeAxes = Axes.None;
}

View File

@ -0,0 +1,12 @@
// 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 osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Objects
{
public class BarLine : ManiaHitObject, IBarLine
{
public bool Major { get; set; }
}
}

View File

@ -4,7 +4,6 @@
using osuTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osuTK.Graphics;
@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// Visualises a <see cref="BarLine"/>. Although this derives DrawableManiaHitObject,
/// this does not handle input/sound like a normal hit object.
/// </summary>
public class DrawableBarLine : DrawableHitObject<BarLine>
public class DrawableBarLine : DrawableManiaHitObject<BarLine>
{
/// <summary>
/// Height of major bar line triangles.

View File

@ -2,13 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Diagnostics;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI.Scrolling;
@ -22,8 +21,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
public override bool DisplayResult => false;
public readonly DrawableNote Head;
public readonly DrawableNote Tail;
public DrawableNote Head => headContainer.Child;
public DrawableNote Tail => tailContainer.Child;
private readonly Container<DrawableHeadNote> headContainer;
private readonly Container<DrawableTailNote> tailContainer;
private readonly Container<DrawableHoldNoteTick> tickContainer;
private readonly BodyPiece bodyPiece;
@ -40,50 +43,81 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableHoldNote(HoldNote hitObject)
: base(hitObject)
{
Container<DrawableHoldNoteTick> tickContainer;
RelativeSizeAxes = Axes.X;
AddRangeInternal(new Drawable[]
{
bodyPiece = new BodyPiece
{
RelativeSizeAxes = Axes.X,
},
tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
ChildrenEnumerable = HitObject.NestedHitObjects.OfType<HoldNoteTick>().Select(tick => new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
})
},
Head = new DrawableHeadNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
Tail = new DrawableTailNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
headContainer = new Container<DrawableHeadNote> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableTailNote> { RelativeSizeAxes = Axes.Both },
});
foreach (var tick in tickContainer)
AddNested(tick);
AddNested(Head);
AddNested(Tail);
AccentColour.BindValueChanged(colour =>
{
bodyPiece.AccentColour = colour.NewValue;
Head.AccentColour.Value = colour.NewValue;
Tail.AccentColour.Value = colour.NewValue;
tickContainer.ForEach(t => t.AccentColour.Value = colour.NewValue);
}, true);
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
switch (hitObject)
{
case DrawableHeadNote head:
headContainer.Child = head;
break;
case DrawableTailNote tail:
tailContainer.Child = tail;
break;
case DrawableHoldNoteTick tick:
tickContainer.Add(tick);
break;
}
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
headContainer.Clear();
tailContainer.Clear();
tickContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case TailNote _:
return new DrawableTailNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = { BindTarget = AccentColour }
};
case Note _:
return new DrawableHeadNote(this)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = { BindTarget = AccentColour }
};
case HoldNoteTick tick:
return new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime,
AccentColour = { BindTarget = AccentColour }
};
}
return base.CreateNestedHitObject(hitObject);
}
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
{
base.OnDirectionChanged(e);

View File

@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
{
public const float CORNER_RADIUS = NotePiece.NOTE_HEIGHT / 2;
private readonly NotePiece headPiece;
public DrawableNote(Note hitObject)

View File

@ -101,6 +101,6 @@ namespace osu.Game.Rulesets.Mania.Objects
public override Judgement CreateJudgement() => new HoldNoteJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

@ -47,9 +47,6 @@ namespace osu.Game.Rulesets.Mania.Replays
public override Replay Generate()
{
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
Replay.Frames.Add(new ManiaReplayFrame(-100000, 0));
var pointGroups = generateActionPoints().GroupBy(a => a.Time).OrderBy(g => g.First().Time);
var actions = new List<ManiaAction>();
@ -70,6 +67,10 @@ namespace osu.Game.Rulesets.Mania.Replays
}
}
// todo: can be removed once FramedReplayInputHandler correctly handles rewinding before first frame.
if (Replay.Frames.Count == 0)
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time - 1));
Replay.Frames.Add(new ManiaReplayFrame(group.First().Time, actions.ToArray()));
}

View File

@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
: base(ruleset, beatmap, mods)
{
BarLines = new BarLineGenerator(Beatmap).BarLines;
BarLines = new BarLineGenerator<BarLine>(Beatmap).BarLines;
}
[BackgroundDependencyLoader]

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;

View File

@ -12,7 +12,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;

View File

@ -7,7 +7,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug)",
@ -20,7 +20,7 @@
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release)",

View File

@ -7,6 +7,7 @@ using System.Linq;
using System.Text;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.IO;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public void TestStacking()
{
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(beatmap_data)))
using (var reader = new StreamReader(stream))
using (var reader = new LineBufferedReader(stream))
{
var beatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>());

View File

@ -0,0 +1,26 @@
// 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 osu.Framework.Bindables;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneHitCircleComboChange : TestSceneHitCircle
{
private readonly Bindable<int> comboIndex = new Bindable<int>();
protected override void LoadComplete()
{
base.LoadComplete();
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
}
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
{
circle.ComboIndexBindable.BindTo(comboIndex);
circle.IndexInCurrentComboBindable.BindTo(comboIndex);
return base.CreateDrawableHitCircle(circle, auto);
}
}
}

View File

@ -29,7 +29,8 @@ namespace osu.Game.Rulesets.Osu.Tests
};
for (int i = 0; i < 512; i++)
beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
if (i % 32 < 20)
beatmap.HitObjects.Add(new HitCircle { Position = new Vector2(256, 192), StartTime = i * 100 });
return beatmap;
}

View File

@ -1,10 +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.
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@ -14,16 +15,58 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
{
private readonly DrawableHitCircle drawableObject;
private HitCircle hitCircle;
private DrawableHitCircle drawableObject;
private TestBlueprint blueprint;
public TestSceneHitCircleSelectionBlueprint()
[SetUp]
public void Setup() => Schedule(() =>
{
var hitCircle = new HitCircle { Position = new Vector2(256, 192) };
Clear();
hitCircle = new HitCircle { Position = new Vector2(256, 192) };
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableHitCircle(hitCircle));
AddBlueprint(blueprint = new TestBlueprint(drawableObject));
});
[Test]
public void TestInitialState()
{
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject);
[Test]
public void TestMoveHitObject()
{
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
[Test]
public void TestMoveAfterApplyingDefaults()
{
AddStep("apply defaults", () => hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
}
[Test]
public void TestStackedHitObject()
{
AddStep("set stacking", () => hitCircle.StackHeight = 5);
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.StackedPosition);
}
private class TestBlueprint : HitCircleSelectionBlueprint
{
public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestBlueprint(DrawableHitCircle drawableCircle)
: base(drawableCircle)
{
}
}
}
}

View File

@ -0,0 +1,176 @@
// 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.Collections.Generic;
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.Events;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CircularDistanceSnapGrid)
};
[Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
[Cached(typeof(IDistanceSnapProvider))]
private readonly SnapProvider snapProvider = new SnapProvider();
private readonly TestOsuDistanceSnapGrid grid;
public TestSceneOsuDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
};
}
[SetUp]
public void Setup() => Schedule(() =>
{
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
editorBeatmap.ControlPointInfo.Clear();
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
});
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestBeatDivisor(int divisor)
{
AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor);
}
[Test]
public void TestCursorInCentre()
{
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position)));
assertSnappedDistance((float)beat_length);
}
[Test]
public void TestCursorBeforeMovementPoint()
{
AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f)));
assertSnappedDistance((float)beat_length);
}
[Test]
public void TestCursorAfterMovementPoint()
{
AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f)));
assertSnappedDistance((float)beat_length * 2);
}
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
{
Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position;
return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position));
});
private class SnappingCursorContainer : CompositeDrawable
{
public Func<Vector2, Vector2> GetSnapPosition;
private readonly Drawable cursor;
public SnappingCursorContainer()
{
RelativeSizeAxes = Axes.Both;
InternalChild = cursor = new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(50),
Colour = Color4.Red
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
base.OnMouseMove(e);
updatePosition(e.ScreenSpaceMousePosition);
return true;
}
private void updatePosition(Vector2 screenSpacePosition)
{
cursor.Position = GetSnapPosition.Invoke(screenSpacePosition);
}
}
private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid
{
public new float DistanceSpacing => base.DistanceSpacing;
public TestOsuDistanceSnapGrid(OsuHitObject hitObject)
: base(hitObject)
{
}
}
private class SnapProvider : IDistanceSnapProvider
{
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
public float DurationToDistance(double referenceTime, double duration) => 0;
public double DistanceToDuration(double referenceTime, float distance) => 0;
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
}
}
}

View File

@ -111,6 +111,21 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1)));
}
[Test]
public void TestChangeStackHeight()
{
DrawableSlider slider = null;
AddStep("create slider", () =>
{
slider = (DrawableSlider)createSlider(repeats: 1);
Add(slider);
});
AddStep("change stack height", () => slider.HitObject.StackHeight = 10);
AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
}
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
@ -293,15 +308,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
{
var cpi = new ControlPointInfo();
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
var drawable = new DrawableSlider(slider)
{
Anchor = Anchor.Centre,
Depth = depthIndex++
};
var drawable = CreateDrawableSlider(slider);
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
@ -311,6 +322,12 @@ namespace osu.Game.Rulesets.Osu.Tests
return drawable;
}
protected virtual DrawableSlider CreateDrawableSlider(Slider slider) => new DrawableSlider(slider)
{
Anchor = Anchor.Centre,
Depth = depthIndex++
};
private float judgementOffsetDirection = 1;
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)

View File

@ -0,0 +1,28 @@
// 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 osu.Framework.Bindables;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneSliderComboChange : TestSceneSlider
{
private readonly Bindable<int> comboIndex = new Bindable<int>();
protected override void LoadComplete()
{
base.LoadComplete();
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
}
protected override DrawableSlider CreateDrawableSlider(Slider slider)
{
slider.ComboIndexBindable.BindTo(comboIndex);
slider.IndexInCurrentComboBindable.BindTo(comboIndex);
return base.CreateDrawableSlider(slider);
}
}
}

View File

@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests
}, 25),
}
},
ControlPointInfo =
{
DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } }
},
BeatmapInfo =
{
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests
},
});
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
p.OnLoadComplete += _ =>

View File

@ -3,11 +3,13 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
@ -29,11 +31,16 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(PathControlPointPiece)
};
private readonly DrawableSlider drawableObject;
private Slider slider;
private DrawableSlider drawableObject;
private TestSliderBlueprint blueprint;
public TestSceneSliderSelectionBlueprint()
[SetUp]
public void Setup() => Schedule(() =>
{
var slider = new Slider
Clear();
slider = new Slider
{
Position = new Vector2(256, 192),
Path = new SliderPath(PathType.Bezier, new[]
@ -47,8 +54,78 @@ namespace osu.Game.Rulesets.Osu.Tests
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
Add(drawableObject = new DrawableSlider(slider));
AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject));
});
[Test]
public void TestInitialState()
{
checkPositions();
}
protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject);
[Test]
public void TestMoveHitObject()
{
moveHitObject();
checkPositions();
}
[Test]
public void TestMoveAfterApplyingDefaults()
{
AddStep("apply defaults", () => slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
moveHitObject();
checkPositions();
}
[Test]
public void TestStackedHitObject()
{
AddStep("set stacking", () => slider.StackHeight = 5);
checkPositions();
}
private void moveHitObject()
{
AddStep("move hitobject", () =>
{
slider.Position = new Vector2(300, 225);
});
}
private void checkPositions()
{
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
AddAssert("head positioned correctly",
() => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
AddAssert("tail positioned correctly",
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
}
private class TestSliderBlueprint : SliderSelectionBlueprint
{
public new SliderBodyPiece BodyPiece => base.BodyPiece;
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
public TestSliderBlueprint(DrawableSlider slider)
: base(slider)
{
}
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
}
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
{
public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
: base(slider, position)
{
}
}
}
}

View File

@ -0,0 +1,96 @@
// 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.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.MathUtils;
using osu.Framework.Testing;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using osuTK;
using System.Collections.Generic;
using System.Linq;
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestSceneSpinnerRotation : TestSceneOsuPlayer
{
[Resolved]
private AudioManager audioManager { get; set; }
private TrackVirtualManual track;
protected override bool Autoplay => true;
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
{
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
track = (TrackVirtualManual)working.Track;
return working;
}
private DrawableSpinner drawableSpinner;
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddUntilStep("wait for track to start running", () => track.IsRunning);
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First());
}
[Test]
public void TestSpinnerRewindingRotation()
{
addSeekStep(5000);
AddAssert("is rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
addSeekStep(0);
AddAssert("is rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
}
[Test]
public void TestSpinnerMiddleRewindingRotation()
{
double estimatedRotation = 0;
addSeekStep(5000);
AddStep("retrieve rotation", () => estimatedRotation = drawableSpinner.Disc.RotationAbsolute);
addSeekStep(2500);
addSeekStep(5000);
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
}
private void addSeekStep(double time)
{
AddStep($"seek to {time}", () => track.Seek(time));
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100));
}
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
{
HitObjects = new List<HitObject>
{
new Spinner
{
Position = new Vector2(256, 192),
EndTime = 5000,
},
// placeholder object to avoid hitting the results screen
new HitObject
{
StartTime = 99999,
}
}
};
}
}

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
@ -25,8 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
typeof(SpinnerPiece)
};
private readonly DrawableSpinner drawableSpinner;
public TestSceneSpinnerSelectionBlueprint()
{
var spinner = new Spinner
@ -35,16 +32,19 @@ namespace osu.Game.Rulesets.Osu.Tests
StartTime = -1000,
EndTime = 2000
};
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
DrawableSpinner drawableSpinner;
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.5f),
Child = drawableSpinner = new DrawableSpinner(spinner)
});
}
protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) };
AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) });
}
}
}

View File

@ -2,14 +2,14 @@
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />

View File

@ -0,0 +1,25 @@
// 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 osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a selection or placement blueprint which visualises an <see cref="OsuHitObject"/>.
/// </summary>
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="BlueprintPiece{T}"/> visualises.</typeparam>
public abstract class BlueprintPiece<T> : CompositeDrawable
where T : OsuHitObject
{
/// <summary>
/// Updates this <see cref="BlueprintPiece{T}"/> using the properties of a <see cref="OsuHitObject"/>.
/// </summary>
/// <param name="hitObject">The <see cref="OsuHitObject"/> to reference properties from.</param>
public virtual void UpdateFrom(T hitObject)
{
Position = hitObject.StackedPosition;
}
}
}

View File

@ -10,18 +10,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
{
public class HitCirclePiece : HitObjectPiece
public class HitCirclePiece : BlueprintPiece<HitCircle>
{
private readonly HitCircle hitCircle;
public HitCirclePiece(HitCircle hitCircle)
: base(hitCircle)
public HitCirclePiece()
{
this.hitCircle = hitCircle;
Origin = Anchor.Centre;
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
Scale = new Vector2(hitCircle.Scale);
CornerRadius = Size.X / 2;
InternalChild = new RingPiece();
@ -31,12 +26,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
private void load(OsuColour colours)
{
Colour = colours.Yellow;
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
}
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
public override void UpdateFrom(HitCircle hitObject)
{
base.UpdateFrom(hitObject);
Scale = new Vector2(hitObject.Scale);
}
}
}

View File

@ -13,31 +13,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public new HitCircle HitObject => (HitCircle)base.HitObject;
private readonly HitCirclePiece circlePiece;
public HitCirclePlacementBlueprint()
: base(new HitCircle())
{
InternalChild = new HitCirclePiece(HitObject);
InternalChild = circlePiece = new HitCirclePiece();
}
protected override void LoadComplete()
protected override void Update()
{
base.LoadComplete();
base.Update();
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
circlePiece.UpdateFrom(HitObject);
}
protected override bool OnClick(ClickEvent e)
{
HitObject.StartTime = EditorClock.CurrentTime;
EndPlacement();
return true;
}
protected override bool OnMouseMove(MouseMoveEvent e)
public override void UpdatePosition(Vector2 screenSpacePosition)
{
HitObject.Position = e.MousePosition;
return true;
HitObject.Position = ToLocalSpace(screenSpacePosition);
}
}
}

View File

@ -1,18 +1,35 @@
// 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 osu.Framework.Graphics.Primitives;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint<HitCircle>
{
public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle)
: base(hitCircle)
protected new DrawableHitCircle DrawableObject => (DrawableHitCircle)base.DrawableObject;
protected readonly HitCirclePiece CirclePiece;
public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle)
: base(drawableCircle)
{
InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);
InternalChild = CirclePiece = new HitCirclePiece();
}
protected override void Update()
{
base.Update();
CirclePiece.UpdateFrom(HitObject);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
public override Quad SelectionQuad => DrawableObject.HitArea.ScreenSpaceDrawQuad;
}
}

View File

@ -1,36 +0,0 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a blueprint which responds to changes in the state of a <see cref="OsuHitObject"/>.
/// </summary>
public abstract class HitObjectPiece : CompositeDrawable
{
protected readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
protected readonly IBindable<int> StackHeightBindable = new Bindable<int>();
protected readonly IBindable<float> ScaleBindable = new Bindable<float>();
private readonly OsuHitObject hitObject;
protected HitObjectPiece(OsuHitObject hitObject)
{
this.hitObject = hitObject;
}
[BackgroundDependencyLoader]
private void load()
{
PositionBindable.BindTo(hitObject.PositionBindable);
StackHeightBindable.BindTo(hitObject.StackHeightBindable);
ScaleBindable.BindTo(hitObject.ScaleBindable);
}
}
}

View File

@ -7,12 +7,13 @@ using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
public class OsuSelectionBlueprint : SelectionBlueprint
public abstract class OsuSelectionBlueprint<T> : SelectionBlueprint
where T : OsuHitObject
{
protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject;
protected T HitObject => (T)DrawableObject.HitObject;
public OsuSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
: base(drawableObject)
{
}
}

View File

@ -1,32 +0,0 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
/// <summary>
/// A piece of a blueprint which responds to changes in the state of a <see cref="Slider"/>.
/// </summary>
public abstract class SliderPiece : HitObjectPiece
{
protected readonly IBindable<SliderPath> PathBindable = new Bindable<SliderPath>();
private readonly Slider slider;
protected SliderPiece(Slider slider)
: base(slider)
{
this.slider = slider;
}
[BackgroundDependencyLoader]
private void load()
{
PathBindable.BindTo(slider.PathBindable);
}
}
}

View File

@ -1,6 +1,7 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -8,14 +9,15 @@ using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointPiece : CompositeDrawable
public class PathControlPointPiece : BlueprintPiece<Slider>
{
public Action<Vector2[]> ControlPointsChanged;
private readonly Slider slider;
private readonly int index;
@ -96,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
if (isSegmentSeparatorWithPrevious)
newControlPoints[index - 1] = newControlPoints[index];
slider.Path = new SliderPath(slider.Path.Type, newControlPoints);
ControlPointsChanged?.Invoke(newControlPoints);
return true;
}

View File

@ -1,37 +1,35 @@
// 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 osu.Framework.Allocation;
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointVisualiser : SliderPiece
public class PathControlPointVisualiser : CompositeDrawable
{
public Action<Vector2[]> ControlPointsChanged;
private readonly Slider slider;
private readonly Container<PathControlPointPiece> pieces;
public PathControlPointVisualiser(Slider slider)
: base(slider)
{
this.slider = slider;
InternalChild = pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
}
[BackgroundDependencyLoader]
private void load()
protected override void Update()
{
PathBindable.BindValueChanged(_ => updatePathControlPoints(), true);
}
base.Update();
private void updatePathControlPoints()
{
while (slider.Path.ControlPoints.Length > pieces.Count)
pieces.Add(new PathControlPointPiece(slider, pieces.Count));
pieces.Add(new PathControlPointPiece(slider, pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) });
while (slider.Path.ControlPoints.Length < pieces.Count)
pieces.Remove(pieces[pieces.Count - 1]);
}

View File

@ -11,19 +11,15 @@ using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class SliderBodyPiece : SliderPiece
public class SliderBodyPiece : BlueprintPiece<Slider>
{
private readonly Slider slider;
private readonly ManualSliderBody body;
public SliderBodyPiece(Slider slider)
: base(slider)
public SliderBodyPiece()
{
this.slider = slider;
InternalChild = body = new ManualSliderBody
{
AccentColour = Color4.Transparent,
AccentColour = Color4.Transparent
};
}
@ -31,19 +27,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
private void load(OsuColour colours)
{
body.BorderColour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * OsuHitObject.OBJECT_RADIUS, true);
}
private void updatePosition() => Position = slider.StackedPosition;
protected override void Update()
public override void UpdateFrom(Slider hitObject)
{
base.Update();
base.UpdateFrom(hitObject);
body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
var vertices = new List<Vector2>();
slider.Path.GetPathToProgress(vertices, 0, 1);
hitObject.Path.GetPathToProgress(vertices, 0, 1);
body.SetVertices(vertices);

View File

@ -1,47 +0,0 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class SliderCirclePiece : HitCirclePiece
{
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
private readonly Slider slider;
private readonly SliderPosition position;
public SliderCirclePiece(Slider slider, SliderPosition position)
: base(slider.HeadCircle)
{
this.slider = slider;
this.position = position;
}
[BackgroundDependencyLoader]
private void load()
{
pathBindable.BindTo(slider.PathBindable);
pathBindable.BindValueChanged(_ => UpdatePosition(), true);
}
protected override void UpdatePosition()
{
switch (position)
{
case SliderPosition.Start:
Position = slider.StackedPosition + slider.Path.PositionAt(0);
break;
case SliderPosition.End:
Position = slider.StackedPosition + slider.Path.PositionAt(1);
break;
}
}
}
}

View File

@ -1,22 +1,34 @@
// 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 osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint<Slider>
{
public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
: base(hitObject)
protected readonly HitCirclePiece CirclePiece;
private readonly SliderPosition position;
public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position)
: base(slider)
{
InternalChild = new SliderCirclePiece(slider, position);
this.position = position;
InternalChild = CirclePiece = new HitCirclePiece();
Select();
}
protected override void Update()
{
base.Update();
CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle);
}
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false;
}

View File

@ -6,11 +6,13 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osuTK;
using osuTK.Input;
@ -21,11 +23,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
private SliderBodyPiece bodyPiece;
private HitCirclePiece headCirclePiece;
private HitCirclePiece tailCirclePiece;
private readonly List<Segment> segments = new List<Segment>();
private Vector2 cursor;
private InputManager inputManager;
private PlacementState state;
[Resolved(CanBeNull = true)]
private HitObjectComposer composer { get; set; }
public SliderPlacementBlueprint()
: base(new Objects.Slider())
{
@ -38,10 +48,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
InternalChildren = new Drawable[]
{
new SliderBodyPiece(HitObject),
new SliderCirclePiece(HitObject, SliderPosition.Start),
new SliderCirclePiece(HitObject, SliderPosition.End),
new PathControlPointVisualiser(HitObject),
bodyPiece = new SliderBodyPiece(),
headCirclePiece = new HitCirclePiece(),
tailCirclePiece = new HitCirclePiece(),
new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() },
};
setState(PlacementState.Initial);
@ -50,25 +60,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
protected override void LoadComplete()
{
base.LoadComplete();
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
inputManager = GetContainingInputManager();
}
protected override bool OnMouseMove(MouseMoveEvent e)
public override void UpdatePosition(Vector2 screenSpacePosition)
{
switch (state)
{
case PlacementState.Initial:
HitObject.Position = e.MousePosition;
return true;
HitObject.Position = ToLocalSpace(screenSpacePosition);
break;
case PlacementState.Body:
cursor = e.MousePosition - HitObject.Position;
return true;
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
// is used instead since snapping control points doesn't make much sense
cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
break;
}
return false;
}
protected override bool OnClick(ClickEvent e)
@ -109,8 +117,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve()
{
BeginPlacement();
HitObject.StartTime = EditorClock.CurrentTime;
setState(PlacementState.Body);
}
@ -128,8 +134,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void updateSlider()
{
var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance);
bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
}
private void setState(PlacementState newState)

View File

@ -1,7 +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.
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
@ -9,9 +13,14 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderSelectionBlueprint : OsuSelectionBlueprint
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
{
private readonly SliderCircleSelectionBlueprint headBlueprint;
protected readonly SliderBodyPiece BodyPiece;
protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
protected readonly SliderCircleSelectionBlueprint TailBlueprint;
[Resolved(CanBeNull = true)]
private HitObjectComposer composer { get; set; }
public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider)
@ -20,13 +29,32 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
InternalChildren = new Drawable[]
{
new SliderBodyPiece(sliderObject),
headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start),
new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End),
new PathControlPointVisualiser(sliderObject),
BodyPiece = new SliderBodyPiece(),
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints },
};
}
public override Vector2 SelectionPoint => headBlueprint.SelectionPoint;
protected override void Update()
{
base.Update();
BodyPiece.UpdateFrom(HitObject);
}
private void onNewControlPoints(Vector2[] controlPoints)
{
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
}
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
}
}

View File

@ -12,17 +12,13 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
{
public class SpinnerPiece : HitObjectPiece
public class SpinnerPiece : BlueprintPiece<Spinner>
{
private readonly Spinner spinner;
private readonly CircularContainer circle;
private readonly RingPiece ring;
public SpinnerPiece(Spinner spinner)
: base(spinner)
public SpinnerPiece()
{
this.spinner = spinner;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
@ -44,21 +40,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
Origin = Anchor.Centre
}
};
ring.Scale = new Vector2(spinner.Scale);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Yellow;
PositionBindable.BindValueChanged(_ => updatePosition(), true);
StackHeightBindable.BindValueChanged(_ => updatePosition());
ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
}
private void updatePosition() => Position = spinner.Position;
public override void UpdateFrom(Spinner hitObject)
{
base.UpdateFrom(hitObject);
ring.Scale = new Vector2(hitObject.Scale);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
}

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
@ -21,7 +22,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
public SpinnerPlacementBlueprint()
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
{
InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f };
InternalChild = piece = new SpinnerPiece { Alpha = 0.5f };
}
protected override void Update()
{
base.Update();
piece.UpdateFrom(HitObject);
}
protected override bool OnClick(ClickEvent e)
@ -33,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
}
else
{
HitObject.StartTime = EditorClock.CurrentTime;
isPlacingEnd = true;
piece.FadeTo(1f, 150, Easing.OutQuint);
@ -43,5 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
return true;
}
public override void UpdatePosition(Vector2 screenSpacePosition)
{
}
}
}

View File

@ -8,14 +8,21 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint<Spinner>
{
private readonly SpinnerPiece piece;
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
: base(spinner)
{
InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject);
InternalChild = piece = new SpinnerPiece();
}
protected override void Update()
{
base.Update();
piece.UpdateFrom(HitObject);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);

View File

@ -0,0 +1,17 @@
// 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 osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
{
public OsuDistanceSnapGrid(OsuHitObject hitObject)
: base(hitObject, hitObject.StackedEndPosition)
{
Masking = true;
}
}
}

View File

@ -2,10 +2,12 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
@ -52,5 +54,31 @@ namespace osu.Game.Rulesets.Osu.Edit
return base.CreateBlueprintFor(hitObject);
}
protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
{
var objects = selectedHitObjects.ToList();
if (objects.Count == 0)
{
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime);
if (lastObject == null)
return null;
return new OsuDistanceSnapGrid(lastObject);
}
else
{
double minTime = objects.Min(h => h.StartTime);
var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime);
if (lastObject == null)
return null;
return new OsuDistanceSnapGrid(lastObject);
}
}
}
}

View File

@ -2,8 +2,6 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Compose.Components;
@ -11,7 +9,7 @@ namespace osu.Game.Rulesets.Osu.Edit
{
public class OsuSelectionHandler : SelectionHandler
{
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
public override void HandleMovement(MoveSelectionEvent moveEvent)
{
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
{
@ -21,10 +19,10 @@ namespace osu.Game.Rulesets.Osu.Edit
continue;
}
h.Position += dragEvent.Delta;
h.Position += moveEvent.InstantDelta;
}
base.HandleDrag(blueprint, dragEvent);
base.HandleMovement(moveEvent);
}
}
}

View File

@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
public bool AllowFail => false;
public bool RestartOnFail => false;
private OsuInputManager inputManager;

View File

@ -17,7 +17,9 @@ namespace osu.Game.Rulesets.Osu.Mods
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModTraceable), typeof(OsuModSpinIn) };
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;

View File

@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1;
// todo: this mod should be able to be compatible with hidden with a bit of further implementation.
public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModeObjectScaleTween), typeof(OsuModHidden), typeof(OsuModTraceable) };
private const int rotate_offset = 360;
private const float rotate_starting_width = 2;

View File

@ -0,0 +1,73 @@
// 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.Framework.Bindables;
using System.Collections.Generic;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Sprites;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
internal class OsuModTraceable : Mod, IReadFromConfig, IApplicableToDrawableHitObjects
{
public override string Name => "Traceable";
public override string Acronym => "TC";
public override IconUsage Icon => FontAwesome.Brands.SnapchatGhost;
public override ModType Type => ModType.Fun;
public override string Description => "Put your faith in the approach circles...";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModHidden), typeof(OsuModSpinIn), typeof(OsuModeObjectScaleTween) };
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
public void ReadFromConfig(OsuConfigManager config)
{
increaseFirstObjectVisibility = config.GetBindable<bool>(OsuSetting.IncreaseFirstObjectVisibility);
}
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
foreach (var drawable in drawables.Skip(increaseFirstObjectVisibility.Value ? 1 : 0))
drawable.ApplyCustomUpdateState += ApplyTraceableState;
}
protected void ApplyTraceableState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableOsuHitObject drawableOsu))
return;
var h = drawableOsu.HitObject;
switch (drawable)
{
case DrawableHitCircle circle:
// we only want to see the approach circle
using (circle.BeginAbsoluteSequence(h.StartTime - h.TimePreempt, true))
circle.CirclePiece.Hide();
break;
case DrawableSlider slider:
slider.AccentColour.BindValueChanged(_ =>
{
//will trigger on skin change.
slider.Body.AccentColour = slider.AccentColour.Value.Opacity(0);
slider.Body.BorderColour = slider.AccentColour.Value;
}, true);
break;
case DrawableSpinner spinner:
spinner.Disc.Hide();
spinner.Background.Hide();
break;
}
}
}
}

View File

@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods
private Bindable<bool> increaseFirstObjectVisibility = new Bindable<bool>();
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn) };
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpinIn), typeof(OsuModTraceable) };
public void ReadFromConfig(OsuConfigManager config)
{

View File

@ -18,20 +18,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableHitCircle : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{
public ApproachCircle ApproachCircle;
public ApproachCircle ApproachCircle { get; }
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>();
public OsuAction? HitAction => hitArea.HitAction;
public OsuAction? HitAction => HitArea.HitAction;
public readonly HitReceptor HitArea;
public readonly SkinnableDrawable CirclePiece;
private readonly Container scaleContainer;
private readonly HitArea hitArea;
private readonly SkinnableDrawable mainContent;
public DrawableHitCircle(HitCircle h)
: base(h)
{
@ -48,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Anchor = Anchor.Centre,
Children = new Drawable[]
{
hitArea = new HitArea
HitArea = new HitReceptor
{
Hit = () =>
{
@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
return true;
},
},
mainContent = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
@ -69,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
};
Size = hitArea.DrawSize;
Size = HitArea.DrawSize;
}
[BackgroundDependencyLoader]
@ -133,7 +131,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateInitialTransforms();
mainContent.FadeInFromZero(HitObject.TimeFadeIn);
CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
ApproachCircle.FadeIn(Math.Min(HitObject.TimeFadeIn * 2, HitObject.TimePreempt));
ApproachCircle.ScaleTo(1f, HitObject.TimePreempt);
@ -153,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Expire(true);
hitArea.HitAction = null;
HitArea.HitAction = null;
break;
case ArmedState.Miss:
@ -172,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => ApproachCircle;
private class HitArea : Drawable, IKeyBindingHandler<OsuAction>
public class HitReceptor : Drawable, IKeyBindingHandler<OsuAction>
{
// IsHovered is used
public override bool HandlePositionalInput => true;
@ -181,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public OsuAction? HitAction;
public HitArea()
public HitReceptor()
{
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);

View File

@ -1,22 +1,66 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Configuration;
using osuTK;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableOsuJudgement : DrawableJudgement
{
private SkinnableSprite lighting;
private Bindable<Color4> lightingColour;
public DrawableOsuJudgement(JudgementResult result, DrawableHitObject judgedObject)
: base(result, judgedObject)
{
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
if (config.Get<bool>(OsuSetting.HitLighting) && Result.Type != HitResult.Miss)
{
AddInternal(lighting = new SkinnableSprite("lighting")
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Blending = BlendingParameters.Additive,
Depth = float.MaxValue
});
if (JudgedObject != null)
{
lightingColour = JudgedObject.AccentColour.GetBoundCopy();
lightingColour.BindValueChanged(colour => lighting.Colour = colour.NewValue, true);
}
else
{
lighting.Colour = Color4.White;
}
}
}
protected override double FadeOutDelay => lighting == null ? base.FadeOutDelay : 1400;
protected override void ApplyHitAnimations()
{
if (lighting != null)
{
JudgementBody.Delay(FadeInDuration).FadeOut(400);
lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out);
lighting.FadeIn(200).Then().Delay(200).FadeOut(1000);
}
JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
base.ApplyHitAnimations();
}

View File

@ -5,7 +5,6 @@ using osuTK;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -21,16 +20,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
{
private readonly Slider slider;
private readonly List<Drawable> components = new List<Drawable>();
public readonly DrawableHitCircle HeadCircle;
public readonly DrawableSliderTail TailCircle;
public DrawableSliderHead HeadCircle => headContainer.Child;
public DrawableSliderTail TailCircle => tailContainer.Child;
public readonly SnakingSliderBody Body;
public readonly SliderBall Ball;
private readonly Container<DrawableSliderHead> headContainer;
private readonly Container<DrawableSliderTail> tailContainer;
private readonly Container<DrawableSliderTick> tickContainer;
private readonly Container<DrawableRepeatPoint> repeatContainer;
private readonly Slider slider;
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
private readonly IBindable<float> scaleBindable = new Bindable<float>();
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
@ -44,14 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Position = s.StackedPosition;
Container<DrawableSliderTick> ticks;
Container<DrawableRepeatPoint> repeatPoints;
InternalChildren = new Drawable[]
{
Body = new SnakingSliderBody(s),
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
Ball = new SliderBall(s, this)
{
GetInitialHitAction = () => HeadCircle.HitAction,
@ -60,45 +61,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true,
Alpha = 0
},
HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
{
OnShake = Shake
},
TailCircle = new DrawableSliderTail(s, s.TailCircle)
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
};
components.Add(Body);
components.Add(Ball);
AddNested(HeadCircle);
AddNested(TailCircle);
components.Add(TailCircle);
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{
var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position };
ticks.Add(drawableTick);
components.Add(drawableTick);
AddNested(drawableTick);
}
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position };
repeatPoints.Add(drawableRepeatPoint);
components.Add(drawableRepeatPoint);
AddNested(drawableRepeatPoint);
}
}
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
Body.FadeInFromZero(HitObject.TimeFadeIn);
}
[BackgroundDependencyLoader]
@ -108,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
scaleBindable.BindValueChanged(scale =>
{
updatePathRadius();
@ -115,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
});
positionBindable.BindTo(HitObject.PositionBindable);
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
scaleBindable.BindTo(HitObject.ScaleBindable);
pathBindable.BindTo(slider.PathBindable);
@ -129,6 +96,67 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}, true);
}
protected override void AddNestedHitObject(DrawableHitObject hitObject)
{
base.AddNestedHitObject(hitObject);
switch (hitObject)
{
case DrawableSliderHead head:
headContainer.Child = head;
break;
case DrawableSliderTail tail:
tailContainer.Child = tail;
break;
case DrawableSliderTick tick:
tickContainer.Add(tick);
break;
case DrawableRepeatPoint repeat:
repeatContainer.Add(repeat);
break;
}
}
protected override void ClearNestedHitObjects()
{
base.ClearNestedHitObjects();
headContainer.Clear();
tailContainer.Clear();
repeatContainer.Clear();
tickContainer.Clear();
}
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
{
switch (hitObject)
{
case SliderTailCircle tail:
return new DrawableSliderTail(slider, tail);
case HitCircle head:
return new DrawableSliderHead(slider, head) { OnShake = Shake };
case SliderTick tick:
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
case RepeatPoint repeat:
return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position };
}
return base.CreateNestedHitObject(hitObject);
}
protected override void UpdateInitialTransforms()
{
base.UpdateInitialTransforms();
Body.FadeInFromZero(HitObject.TimeFadeIn);
}
public readonly Bindable<bool> Tracking = new Bindable<bool>();
protected override void Update()
@ -139,9 +167,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
Ball.UpdateProgress(completionProgress);
Body.UpdateProgress(completionProgress);
foreach (DrawableHitObject hitObject in NestedHitObjects)
{
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
}
Size = Body.Size;
OriginPosition = Body.PathOffset;
@ -163,9 +196,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private float sliderPathRadius;
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
protected override void ApplySkin(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
base.ApplySkin(skin, allowFallback);
Body.BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? SliderBody.DEFAULT_BORDER_SIZE;
sliderPathRadius = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS;
@ -173,6 +206,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
}
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
@ -184,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
ApplyResult(r =>
{
var judgementsCount = NestedHitObjects.Count();
var judgementsCount = NestedHitObjects.Count;
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
var hitFraction = (double)judgementsHit / judgementsCount;
@ -225,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
}

View File

@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
positionBindable.BindValueChanged(_ => updatePosition());
pathBindable.BindValueChanged(_ => updatePosition(), true);
// TODO: This has no drawable content. Support for skins should be added.
}
protected override void CheckForResult(bool userTriggered, double timeOffset)

Some files were not shown because too many files have changed in this diff Show More