1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 10:12:54 +08:00

Merge branch 'master' into beatmap-overlay-ruleset-selector

This commit is contained in:
Salman Ahmed 2019-10-19 04:06:43 +03:00 committed by GitHub
commit eed6f708a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
218 changed files with 5073 additions and 1427 deletions

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

@ -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)
@ -27,7 +27,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.7)
fastlane (2.131.0)
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)
@ -102,7 +102,7 @@ GEM
memoist (0.16.0)
mime-types (3.3)
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0904)
mime-types-data (3.2019.1009)
mini_magick (4.9.5)
mini_portile2 (2.4.0)
multi_json (1.13.1)
@ -121,9 +121,9 @@ GEM
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.2.4)
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)

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/).
@ -78,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:

63
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
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

@ -61,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.930.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1011.0" />
</ItemGroup>
</Project>

View File

@ -15,7 +15,7 @@ using osuTK.Graphics;
namespace osu.Desktop.Overlays
{
public class VersionManager : OverlayContainer
public class VersionManager : VisibilityContainer
{
[BackgroundDependencyLoader]
private void load(OsuColour colours, TextureStore textures, OsuGameBase game)

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>

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

@ -9,7 +9,7 @@
</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

@ -93,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

@ -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

@ -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

@ -9,7 +9,7 @@
</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

@ -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 HitObject => (DrawableManiaHitObject)base.HitObject;
protected IClock EditorClock { get; private set; }

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.HitObject.HitObject.Column;
base.HandleDrag(blueprint, dragEvent);
adjustOrigins(maniaBlueprint);
performDragMovement(moveEvent);
performColumnMovement(lastColumn, moveEvent);
base.HandleMovement(moveEvent);
}
/// <summary>
@ -62,26 +63,29 @@ namespace osu.Game.Rulesets.Mania.Edit
b.HitObject.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.HitObject.Parent.DrawHeight;
foreach (var b in SelectedBlueprints)
{
var hitObject = b.HitObject;
var objectParent = (HitObjectContainer)hitObject.Parent;
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame
// without the position having been updated by the parenting ScrollingHitObjectContainer
hitObject.Y += dragEvent.Delta.Y;
// 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

@ -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

@ -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

@ -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,51 @@ 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);
}
private class TestBlueprint : HitCircleSelectionBlueprint
{
public new HitCirclePiece CirclePiece => base.CirclePiece;
public TestBlueprint(DrawableHitCircle hitCircle)
: base(hitCircle)
{
}
}
}
}

View File

@ -0,0 +1,210 @@
// 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 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.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
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);
[Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
[Cached]
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
private TestOsuDistanceSnapGrid grid;
public TestSceneOsuDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
createGrid();
}
[SetUp]
public void Setup() => Schedule(() =>
{
Clear();
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
editorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
beatDivisor.Value = 1;
});
[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);
createGrid();
}
[TestCase(100, 100)]
[TestCase(200, 100)]
public void TestBeatLength(float beatLength, float expectedSpacing)
{
AddStep($"set beat length = {beatLength}", () =>
{
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength });
});
createGrid();
AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing));
}
[TestCase(0.5f, 50)]
[TestCase(1, 100)]
[TestCase(1.5f, 150)]
public void TestSpeedMultiplier(float multiplier, float expectedSpacing)
{
AddStep($"set speed multiplier = {multiplier}", () =>
{
editorBeatmap.ControlPointInfo.DifficultyPoints.Clear();
editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier });
});
createGrid();
AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing));
}
[TestCase(0.5f, 50)]
[TestCase(1, 100)]
[TestCase(1.5f, 150)]
public void TestSliderMultiplier(float multiplier, float expectedSpacing)
{
AddStep($"set speed multiplier = {multiplier}", () => editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier);
createGrid();
AddAssert($"spacing = {expectedSpacing}", () => Precision.AlmostEquals(expectedSpacing, grid.DistanceSpacing));
}
[Test]
public void TestCursorInCentre()
{
createGrid();
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position)));
assertSnappedDistance((float)beat_length);
}
[Test]
public void TestCursorBeforeMovementPoint()
{
createGrid();
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()
{
createGrid();
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.GetSnapPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position));
float distance = Vector2.Distance(snappedPosition, grid_position);
return Precision.AlmostEquals(expectedDistance, distance);
});
private void createGrid()
{
AddStep("create grid", () =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnapPosition(grid.ToLocalSpace(v)) }
};
});
}
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)
{
}
}
}
}

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,71 @@ 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();
}
private void moveHitObject()
{
AddStep("move hitobject", () =>
{
slider.Position = new Vector2(300, 225);
});
}
private void checkPositions()
{
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.Position);
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

@ -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

@ -9,7 +9,7 @@
</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.Position;
}
}
}

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

@ -7,12 +7,21 @@ using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint<HitCircle>
{
protected readonly HitCirclePiece CirclePiece;
public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle)
: base(hitCircle)
{
InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);
InternalChild = CirclePiece = new HitCirclePiece();
}
protected override void Update()
{
base.Update();
CirclePiece.UpdateFrom(HitObject);
}
}
}

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,11 +7,12 @@ 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 new T HitObject => (T)base.HitObject.HitObject;
public OsuSelectionBlueprint(DrawableHitObject hitObject)
protected OsuSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
{
}

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

@ -14,7 +14,7 @@ using osuTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointPiece : CompositeDrawable
public class PathControlPointPiece : BlueprintPiece<Slider>
{
private readonly Slider slider;
private readonly int index;

View File

@ -1,35 +1,29 @@
// 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.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointVisualiser : SliderPiece
public class PathControlPointVisualiser : CompositeDrawable
{
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));
while (slider.Path.ControlPoints.Length < pieces.Count)

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

@ -11,6 +11,7 @@ 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,6 +22,10 @@ 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;
@ -38,37 +43,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
InternalChildren = new Drawable[]
{
new SliderBodyPiece(HitObject),
new SliderCirclePiece(HitObject, SliderPosition.Start),
new SliderCirclePiece(HitObject, SliderPosition.End),
bodyPiece = new SliderBodyPiece(),
headCirclePiece = new HitCirclePiece(),
tailCirclePiece = new HitCirclePiece(),
new PathControlPointVisualiser(HitObject),
};
setState(PlacementState.Initial);
}
protected override void LoadComplete()
{
base.LoadComplete();
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
}
protected override bool OnMouseMove(MouseMoveEvent e)
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;
cursor = ToLocalSpace(screenSpacePosition) - HitObject.Position;
break;
}
return false;
}
protected override bool OnClick(ClickEvent e)
@ -109,8 +104,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
private void beginCurve()
{
BeginPlacement();
HitObject.StartTime = EditorClock.CurrentTime;
setState(PlacementState.Body);
}
@ -130,6 +123,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
bodyPiece.UpdateFrom(HitObject);
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
}
private void setState(PlacementState newState)

View File

@ -9,9 +9,11 @@ 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;
public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider)
@ -20,13 +22,22 @@ 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),
BodyPiece = new SliderBodyPiece(),
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
new PathControlPointVisualiser(sliderObject),
};
}
public override Vector2 SelectionPoint => headBlueprint.SelectionPoint;
protected override void Update()
{
base.Update();
BodyPiece.UpdateFrom(HitObject);
}
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
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,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.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
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)
{
}
protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(time);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(time);
double scoringDistance = OsuHitObject.BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
return (float)(scoringDistance / timingPoint.BeatLength);
}
}
}

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

@ -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)

View File

@ -14,8 +14,16 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{
/// <summary>
/// The radius of hit objects (ie. the radius of a <see cref="HitCircle"/>).
/// </summary>
public const float OBJECT_RADIUS = 64;
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second (ie. the speed slider balls move through their track).
/// </summary>
internal const float BASE_SCORING_DISTANCE = 100;
public double TimePreempt = 600;
public double TimeFadeIn = 400;

View File

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

View File

@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class Slider : OsuHitObject, IHasCurve
{
/// <summary>
/// Scoring distance with a speed-adjusted beat length of 1 second.
/// </summary>
private const float base_scoring_distance = 100;
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime;
@ -54,13 +49,13 @@ namespace osu.Game.Rulesets.Osu.Objects
{
base.Position = value;
endPositionCache.Invalidate();
if (HeadCircle != null)
HeadCircle.Position = value;
if (TailCircle != null)
TailCircle.Position = EndPosition;
endPositionCache.Invalidate();
}
}
@ -123,7 +118,7 @@ namespace osu.Game.Rulesets.Osu.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
double scoringDistance = BASE_SCORING_DISTANCE * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier;
@ -205,6 +200,6 @@ namespace osu.Game.Rulesets.Osu.Objects
public override Judgement CreateJudgement() => new OsuJudgement();
protected override HitWindows CreateHitWindows() => null;
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
}
}

View File

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

View File

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

View File

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

View File

@ -40,6 +40,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private void load(OsuRulesetConfigManager config)
{
config?.BindWith(OsuRulesetSetting.ShowCursorTrail, showTrail);
}
protected override void LoadComplete()
{
base.LoadComplete();
showTrail.BindValueChanged(v => cursorTrail.FadeTo(v.NewValue ? 1 : 0, 200), true);
}

View File

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

View File

@ -9,7 +9,7 @@
</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.Taiko\osu.Game.Rulesets.Taiko.csproj" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,154 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using Microsoft.EntityFrameworkCore.Internal;
using NUnit.Framework;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
namespace osu.Game.Tests.Beatmaps
{
[TestFixture]
public class EditorBeatmapTest
{
/// <summary>
/// Tests that the addition event is correctly invoked after a hitobject is added.
/// </summary>
[Test]
public void TestHitObjectAddEvent()
{
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
HitObject addedObject = null;
editorBeatmap.HitObjectAdded += h => addedObject = h;
var hitCircle = new HitCircle();
editorBeatmap.Add(hitCircle);
Assert.That(addedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the removal event is correctly invoked after a hitobject is removed.
/// </summary>
[Test]
public void HitObjectRemoveEvent()
{
var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject removedObject = null;
editorBeatmap.HitObjectRemoved += h => removedObject = h;
editorBeatmap.Remove(hitCircle);
Assert.That(removedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the changed event is correctly invoked after the start time of a hitobject is changed.
/// This tests for hitobjects which were already present before the editor beatmap was constructed.
/// </summary>
[Test]
public void TestInitialHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
hitCircle.StartTime = 1000;
Assert.That(changedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the changed event is correctly invoked after the start time of a hitobject is changed.
/// This tests for hitobjects which were added to an existing editor beatmap.
/// </summary>
[Test]
public void TestAddedHitObjectStartTimeChangeEvent()
{
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
var hitCircle = new HitCircle();
editorBeatmap.Add(hitCircle);
Assert.That(changedObject, Is.Null);
hitCircle.StartTime = 1000;
Assert.That(changedObject, Is.EqualTo(hitCircle));
}
/// <summary>
/// Tests that the channged event is not invoked after a hitobject is removed from the beatmap/
/// </summary>
[Test]
public void TestRemovedHitObjectStartTimeChangeEvent()
{
var hitCircle = new HitCircle();
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap { HitObjects = { hitCircle } });
HitObject changedObject = null;
editorBeatmap.StartTimeChanged += h => changedObject = h;
editorBeatmap.Remove(hitCircle);
Assert.That(changedObject, Is.Null);
hitCircle.StartTime = 1000;
Assert.That(changedObject, Is.Null);
}
/// <summary>
/// Tests that an added hitobject is correctly inserted to preserve the sorting order of the beatmap.
/// </summary>
[Test]
public void TestAddHitObjectInMiddle()
{
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap
{
HitObjects =
{
new HitCircle(),
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 2000 },
}
});
var hitCircle = new HitCircle { StartTime = 1000 };
editorBeatmap.Add(hitCircle);
Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1));
Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(3));
}
/// <summary>
/// Tests that the beatmap remains correctly sorted after the start time of a hitobject is changed.
/// </summary>
[Test]
public void TestResortWhenStartTimeChanged()
{
var hitCircle = new HitCircle { StartTime = 1000 };
var editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap
{
HitObjects =
{
new HitCircle(),
new HitCircle { StartTime = 1000 },
new HitCircle { StartTime = 1000 },
hitCircle,
new HitCircle { StartTime = 2000 },
}
});
hitCircle.StartTime = 0;
Assert.That(editorBeatmap.HitObjects.Count(h => h == hitCircle), Is.EqualTo(1));
Assert.That(editorBeatmap.HitObjects.IndexOf(hitCircle), Is.EqualTo(1));
}
}
}

View File

@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
using osu.Game.IO;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
@ -30,13 +31,9 @@ namespace osu.Game.Tests.Beatmaps.Formats
public void TestDecodeBeatmapVersion()
{
using (var resStream = TestResources.OpenResource("beatmap-version.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var decoder = Decoder.GetDecoder<Beatmap>(stream);
stream.BaseStream.Position = 0;
stream.DiscardBufferedData();
var working = new TestWorkingBeatmap(decoder.Decode(stream));
Assert.AreEqual(6, working.BeatmapInfo.BeatmapVersion);
@ -51,7 +48,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var beatmapInfo = beatmap.BeatmapInfo;
@ -75,7 +72,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmapInfo = decoder.Decode(stream).BeatmapInfo;
@ -101,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var beatmapInfo = beatmap.BeatmapInfo;
@ -126,7 +123,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty;
@ -145,7 +142,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var metadata = beatmap.Metadata;
@ -164,7 +161,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
var controlPoints = beatmap.ControlPointInfo;
@ -239,7 +236,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("overlapping-control-points.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var controlPoints = decoder.Decode(stream).ControlPointInfo;
@ -271,7 +268,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
@ -297,7 +294,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
@ -320,7 +317,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder();
using (var resStream = TestResources.OpenResource("hitobject-combo-offset.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var beatmap = decoder.Decode(stream);
@ -343,7 +340,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -371,7 +368,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("controlpoint-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -393,7 +390,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("hitobject-custom-samplebank.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -411,7 +408,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("hitobject-file-samples.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -431,7 +428,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("slider-samples.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -475,7 +472,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var resStream = TestResources.OpenResource("hitobject-no-addition-bank.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var hitObjects = decoder.Decode(stream).HitObjects;
@ -489,10 +486,132 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
using (var badResStream = TestResources.OpenResource("invalid-events.osu"))
using (var badStream = new StreamReader(badResStream))
using (var badStream = new LineBufferedReader(badResStream))
{
Assert.DoesNotThrow(() => decoder.Decode(badStream));
}
}
[Test]
public void TestFallbackDecoderForCorruptedHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("Beatmap with corrupted header", beatmap.Metadata.Title);
Assert.AreEqual("Evil Hacker", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestFallbackDecoderForMissingHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("missing-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("Beatmap with no header", beatmap.Metadata.Title);
Assert.AreEqual("Incredibly Evil Hacker", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeFileWithEmptyLinesAtStart()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("Empty lines at start", beatmap.Metadata.Title);
Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeFileWithEmptyLinesAndNoHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("The dog ate the file header", beatmap.Metadata.Title);
Assert.AreEqual("Why does this keep happening", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeFileWithContentImmediatelyAfterHeader()
{
Decoder<Beatmap> decoder = null;
Beatmap beatmap = null;
using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
Assert.DoesNotThrow(() => beatmap = decoder.Decode(stream));
Assert.IsNotNull(beatmap);
Assert.AreEqual("No empty line delimiting header from contents", beatmap.Metadata.Title);
Assert.AreEqual("Edge Case Hunter", beatmap.Metadata.AuthorString);
}
}
[Test]
public void TestDecodeEmptyFile()
{
using (var resStream = new MemoryStream())
using (var stream = new LineBufferedReader(resStream))
{
Assert.Throws<IOException>(() => Decoder.GetDecoder<Beatmap>(stream));
}
}
[Test]
public void TestAllowFallbackDecoderOverwrite()
{
Decoder<Beatmap> decoder = null;
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyBeatmapDecoder>(decoder);
}
Assert.DoesNotThrow(LegacyDifficultyCalculatorBeatmapDecoder.Register);
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
using (var stream = new LineBufferedReader(resStream))
{
Assert.DoesNotThrow(() => decoder = Decoder.GetDecoder<Beatmap>(stream));
Assert.IsInstanceOf<LegacyDifficultyCalculatorBeatmapDecoder>(decoder);
}
}
}
}

View File

@ -2,9 +2,9 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats
@ -18,7 +18,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LineLoggingDecoder(14);
using (var resStream = TestResources.OpenResource("comments.osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
decoder.Decode(stream);

View File

@ -1,12 +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 System.IO;
using System.Linq;
using NUnit.Framework;
using osuTK;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Storyboards;
using osu.Game.Tests.Resources;
@ -21,7 +21,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("Himeringo - Yotsuya-san ni Yoroshiku (RLC) [Winber1's Extreme].osu"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
var decoder = new LegacyStoryboardDecoder();
using (var resStream = TestResources.OpenResource("variable-with-suffix.osb"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var storyboard = decoder.Decode(stream);

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
@ -148,13 +149,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
private Beatmap decode(string filename, out Beatmap jsonDecoded)
{
using (var stream = TestResources.OpenResource(filename))
using (var sr = new StreamReader(stream))
using (var sr = new LineBufferedReader(stream))
{
var legacyDecoded = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(sr);
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms))
using (var sr2 = new StreamReader(ms))
using (var sr2 = new LineBufferedReader(ms))
{
sw.Write(legacyDecoded.Serialize());
sw.Flush();

View File

@ -29,7 +29,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWhenClosed()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenClosed"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenClosed)))
{
try
{
@ -46,7 +46,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDelete()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDelete"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDelete)))
{
try
{
@ -67,7 +67,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImport)))
{
try
{
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportCorruptThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImport"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportCorruptThenImport)))
{
try
{
@ -136,7 +136,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestRollbackOnFailure()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestRollbackOnFailure)))
{
try
{
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Beatmaps.IO
var breakTemp = TestResources.GetTestBeatmapForImport();
MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 });
MemoryStream brokenOsu = new MemoryStream();
MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp));
File.Delete(breakTemp);
@ -213,7 +213,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenImportDifferentHash()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenImportDifferentHash"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash)))
{
try
{
@ -244,7 +244,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImport()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportThenDeleteThenImport"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenDeleteThenImport)))
{
try
{
@ -272,7 +272,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportThenDeleteThenImportWithOnlineIDMismatch(bool set)
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"TestImportThenDeleteThenImport-{set}"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost($"{nameof(TestImportThenDeleteThenImportWithOnlineIDMismatch)}-{set}"))
{
try
{
@ -306,7 +306,7 @@ namespace osu.Game.Tests.Beatmaps.IO
public async Task TestImportWithDuplicateBeatmapIDs()
{
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWithDuplicateBeatmapID"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithDuplicateBeatmapIDs)))
{
try
{
@ -392,7 +392,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportWhenFileOpen()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportWhenFileOpen"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWhenFileOpen)))
{
try
{
@ -414,7 +414,7 @@ namespace osu.Game.Tests.Beatmaps.IO
[Test]
public async Task TestImportNestedStructure()
{
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestImportNestedStructure"))
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportNestedStructure)))
{
try
{
@ -456,9 +456,63 @@ namespace osu.Game.Tests.Beatmaps.IO
}
}
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null)
[Test]
public async Task TestImportWithIgnoredDirectoryInArchive()
{
var temp = path ?? TestResources.GetTestBeatmapForImport();
using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportWithIgnoredDirectoryInArchive)))
{
try
{
var osu = loadOsu(host);
var temp = TestResources.GetTestBeatmapForImport();
string extractedFolder = $"{temp}_extracted";
string dataFolder = Path.Combine(extractedFolder, "actual_data");
string resourceForkFolder = Path.Combine(extractedFolder, "__MACOSX");
string resourceForkFilePath = Path.Combine(resourceForkFolder, ".extracted");
Directory.CreateDirectory(dataFolder);
Directory.CreateDirectory(resourceForkFolder);
using (var resourceForkFile = File.CreateText(resourceForkFilePath))
{
resourceForkFile.WriteLine("adding content so that it's not empty");
}
try
{
using (var zip = ZipArchive.Open(temp))
zip.WriteToDirectory(dataFolder);
using (var zip = ZipArchive.Create())
{
zip.AddAllFromDirectory(extractedFolder);
zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate));
}
var imported = await osu.Dependencies.Get<BeatmapManager>().Import(temp);
ensureLoaded(osu);
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored");
Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder");
}
finally
{
Directory.Delete(extractedFolder, true);
}
}
finally
{
host.Exit();
}
}
}
public static async Task<BeatmapSetInfo> LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false)
{
var temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack);
var manager = osu.Dependencies.Get<BeatmapManager>();

View File

@ -0,0 +1,125 @@
// 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.IO;
using System.Text;
using NUnit.Framework;
using osu.Game.IO;
namespace osu.Game.Tests.Beatmaps.IO
{
[TestFixture]
public class LineBufferedReaderTest
{
[Test]
public void TestReadLineByLine()
{
const string contents = "line 1\rline 2\nline 3";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("line 1", bufferedReader.ReadLine());
Assert.AreEqual("line 2", bufferedReader.ReadLine());
Assert.AreEqual("line 3", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
}
}
[Test]
public void TestPeekLineOnce()
{
const string contents = "line 1\r\npeek this\nline 3";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("line 1", bufferedReader.ReadLine());
Assert.AreEqual("peek this", bufferedReader.PeekLine());
Assert.AreEqual("peek this", bufferedReader.ReadLine());
Assert.AreEqual("line 3", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
}
}
[Test]
public void TestPeekLineMultipleTimes()
{
const string contents = "peek this once\nline 2\rpeek this a lot";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("peek this once", bufferedReader.PeekLine());
Assert.AreEqual("peek this once", bufferedReader.ReadLine());
Assert.AreEqual("line 2", bufferedReader.ReadLine());
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
Assert.AreEqual("peek this a lot", bufferedReader.PeekLine());
Assert.AreEqual("peek this a lot", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
}
}
[Test]
public void TestPeekLineAtEndOfStream()
{
const string contents = "first line\r\nsecond line";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("first line", bufferedReader.ReadLine());
Assert.AreEqual("second line", bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.PeekLine());
Assert.IsNull(bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.PeekLine());
}
}
[Test]
public void TestPeekReadLineOnEmptyStream()
{
using (var stream = new MemoryStream())
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.IsNull(bufferedReader.PeekLine());
Assert.IsNull(bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.ReadLine());
Assert.IsNull(bufferedReader.PeekLine());
}
}
[Test]
public void TestReadToEndNoPeeks()
{
const string contents = "first line\r\nsecond line";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual(contents, bufferedReader.ReadToEnd());
}
}
[Test]
public void TestReadToEndAfterReadsAndPeeks()
{
const string contents = "this line is gone\rthis one shouldn't be\r\nthese ones\ndefinitely not";
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
using (var bufferedReader = new LineBufferedReader(stream))
{
Assert.AreEqual("this line is gone", bufferedReader.ReadLine());
Assert.AreEqual("this one shouldn't be", bufferedReader.PeekLine());
var endingLines = bufferedReader.ReadToEnd().Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
Assert.AreEqual(3, endingLines.Length);
Assert.AreEqual("this one shouldn't be", endingLines[0]);
Assert.AreEqual("these ones", endingLines[1]);
Assert.AreEqual("definitely not", endingLines[2]);
}
}
}
}

View File

@ -7,6 +7,7 @@ using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Tests.Resources;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.IO.Archives;
namespace osu.Game.Tests.Beatmaps.IO
@ -50,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Beatmap beatmap;
using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
using (var stream = new LineBufferedReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu")))
beatmap = Decoder.GetDecoder<Beatmap>(stream).Decode(stream);
var meta = beatmap.Metadata;

View File

@ -0,0 +1,143 @@
// 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.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Audio;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK.Graphics;
namespace osu.Game.Tests.Gameplay
{
[HeadlessTest]
public class TestSceneHitObjectAccentColour : OsuTestScene
{
private Container skinContainer;
[SetUp]
public void Setup() => Schedule(() => Child = skinContainer = new SkinProvidingContainer(new TestSkin()));
[Test]
public void TestChangeComboIndexBeforeLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("set combo and add hitobject", () =>
{
hitObject = new TestDrawableHitObject();
hitObject.HitObject.ComboIndex = 1;
skinContainer.Add(hitObject);
});
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
[Test]
public void TestChangeComboIndexDuringLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("add hitobject and set combo", () =>
{
skinContainer.Add(hitObject = new TestDrawableHitObject());
hitObject.HitObject.ComboIndex = 1;
});
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
[Test]
public void TestChangeComboIndexAfterLoad()
{
TestDrawableHitObject hitObject = null;
AddStep("add hitobject", () => skinContainer.Add(hitObject = new TestDrawableHitObject()));
AddAssert("combo colour is red", () => hitObject.AccentColour.Value == Color4.Red);
AddStep("change combo", () => hitObject.HitObject.ComboIndex = 1);
AddAssert("combo colour is green", () => hitObject.AccentColour.Value == Color4.Green);
}
private class TestDrawableHitObject : DrawableHitObject<TestHitObjectWithCombo>
{
public TestDrawableHitObject()
: base(new TestHitObjectWithCombo())
{
}
}
private class TestHitObjectWithCombo : HitObject, IHasComboInformation
{
public bool NewCombo { get; } = false;
public int ComboOffset { get; } = 0;
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
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;
}
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
public bool LastInCombo
{
get => LastInComboBindable.Value;
set => LastInComboBindable.Value = value;
}
}
private class TestSkin : ISkin
{
public readonly List<Color4> ComboColours = new List<Color4>
{
Color4.Red,
Color4.Green
};
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
public Texture GetTexture(string componentName) => throw new NotImplementedException();
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
{
switch (lookup)
{
case GlobalSkinConfiguration global:
switch (global)
{
case GlobalSkinConfiguration.ComboColours:
return SkinUtils.As<TValue>(new Bindable<List<Color4>>(ComboColours));
}
break;
}
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,48 @@
// 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;
namespace osu.Game.Tests.NonVisual
{
[TestFixture]
public class BeatmapSetInfoEqualityTest
{
[Test]
public void TestOnlineWithOnline()
{
var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestDatabasedWithDatabased()
{
var ourInfo = new BeatmapSetInfo { ID = 123 };
var otherInfo = new BeatmapSetInfo { ID = 123 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestDatabasedWithOnline()
{
var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 };
var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 };
Assert.AreEqual(ourInfo, otherInfo);
}
[Test]
public void TestCheckNullID()
{
var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved };
var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved };
Assert.AreNotEqual(ourInfo, otherInfo);
}
}
}

View File

@ -11,13 +11,13 @@ namespace osu.Game.Tests.Resources
{
public static Stream OpenResource(string name) => new DllResourceStore("osu.Game.Tests.dll").GetStream($"Resources/{name}");
public static Stream GetTestBeatmapStream() => new DllResourceStore("osu.Game.Resources.dll").GetStream("Beatmaps/241526 Soleily - Renatus.osz");
public static Stream GetTestBeatmapStream(bool virtualTrack = false) => new DllResourceStore("osu.Game.Resources.dll").GetStream($"Beatmaps/241526 Soleily - Renatus{(virtualTrack ? "_virtual" : "")}.osz");
public static string GetTestBeatmapForImport()
public static string GetTestBeatmapForImport(bool virtualTrack = false)
{
var temp = Path.GetTempFileName() + ".osz";
using (var stream = GetTestBeatmapStream())
using (var stream = GetTestBeatmapStream(virtualTrack))
using (var newFile = File.Create(temp))
stream.CopyTo(newFile);

View File

@ -0,0 +1,5 @@
ow computerosu file format v14
[Metadata]
Title: Beatmap with corrupted header
Creator: Evil Hacker

View File

@ -0,0 +1,5 @@

[Metadata]
Title: The dog ate the file header
Creator: Why does this keep happening

View File

@ -0,0 +1,8 @@

osu file format v14
[Metadata]
Title: Empty lines at start
Creator: Edge Case Hunter

View File

@ -0,0 +1,4 @@
[Metadata]
Title: Beatmap with no header
Creator: Incredibly Evil Hacker

View File

@ -0,0 +1,4 @@
osu file format v14
[Metadata]
Title: No empty line delimiting header from contents
Creator: Edge Case Hunter

View File

@ -2,8 +2,8 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.IO;
using NUnit.Framework;
using osu.Game.IO;
using osu.Game.Skinning;
using osu.Game.Tests.Resources;
using osuTK.Graphics;
@ -20,7 +20,7 @@ namespace osu.Game.Tests.Skins
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource(hasColours ? "skin.ini" : "skin-empty.ini"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var comboColors = decoder.Decode(stream).ComboColours;
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Skins
var decoder = new LegacySkinDecoder();
using (var resStream = TestResources.OpenResource("skin.ini"))
using (var stream = new StreamReader(resStream))
using (var stream = new LineBufferedReader(resStream))
{
var config = decoder.Decode(stream);

View File

@ -285,6 +285,12 @@ namespace osu.Game.Tests.Visual.Background
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
rulesets?.Dispose();
}
private class DummySongSelect : PlaySongSelect
{
protected override BackgroundScreen CreateBackground()

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
@ -11,10 +9,8 @@ using osu.Game.Screens.Edit.Compose;
namespace osu.Game.Tests.Visual.Editor
{
[TestFixture]
public class TestSceneEditorCompose : EditorClockTestScene
public class TestSceneComposeScreen : EditorClockTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ComposeScreen) };
[BackgroundDependencyLoader]
private void load()
{

View File

@ -0,0 +1,213 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual.Editor
{
public class TestSceneDistanceSnapGrid : EditorClockTestScene
{
private const double beat_length = 100;
private static readonly Vector2 grid_position = new Vector2(512, 384);
[Cached(typeof(IEditorBeatmap))]
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
private TestDistanceSnapGrid grid;
public TestSceneDistanceSnapGrid()
{
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
createGrid();
}
[SetUp]
public void Setup() => Schedule(() =>
{
Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length });
BeatDivisor.Value = 1;
});
[TestCase(1)]
[TestCase(2)]
[TestCase(3)]
[TestCase(4)]
[TestCase(6)]
[TestCase(8)]
[TestCase(12)]
[TestCase(16)]
public void TestInitialBeatDivisor(int divisor)
{
AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor);
createGrid();
float expectedDistance = (float)beat_length / divisor;
AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance));
}
[Test]
public void TestChangeBeatDivisor()
{
createGrid();
AddStep("set beat divisor = 2", () => BeatDivisor.Value = 2);
const float expected_distance = (float)beat_length / 2;
AddAssert($"spacing is {expected_distance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expected_distance));
}
[TestCase(100)]
[TestCase(200)]
public void TestBeatLength(double beatLength)
{
AddStep($"set beat length = {beatLength}", () =>
{
editorBeatmap.ControlPointInfo.TimingPoints.Clear();
editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength });
});
createGrid();
AddAssert($"spacing is {beatLength}", () => Precision.AlmostEquals(grid.DistanceSpacing, beatLength));
}
[TestCase(1)]
[TestCase(2)]
public void TestGridVelocity(float velocity)
{
createGrid(g => g.Velocity = velocity);
float expectedDistance = (float)beat_length * velocity;
AddAssert($"spacing is {expectedDistance}", () => Precision.AlmostEquals(grid.DistanceSpacing, expectedDistance));
}
[Test]
public void TestGetSnappedTime()
{
createGrid();
Vector2 snapPosition = Vector2.Zero;
AddStep("get first tick position", () => snapPosition = grid_position + new Vector2((float)beat_length, 0));
AddAssert("snap time is 1 beat away", () => Precision.AlmostEquals(beat_length, grid.GetSnapTime(snapPosition), 0.01));
createGrid(g => g.Velocity = 2, "with velocity = 2");
AddAssert("snap time is now 0.5 beats away", () => Precision.AlmostEquals(beat_length / 2, grid.GetSnapTime(snapPosition), 0.01));
}
private void createGrid(Action<TestDistanceSnapGrid> func = null, string description = null)
{
AddStep($"create grid {description ?? string.Empty}", () =>
{
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.SlateGray
},
grid = new TestDistanceSnapGrid(new HitObject(), grid_position)
};
func?.Invoke(grid);
});
}
private class TestDistanceSnapGrid : DistanceSnapGrid
{
public new float Velocity = 1;
public new float DistanceSpacing => base.DistanceSpacing;
public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition)
: base(hitObject, centrePosition)
{
}
protected override void CreateContent(Vector2 centrePosition)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5),
Position = centrePosition
});
int beatIndex = 0;
for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(5, 10),
Position = new Vector2(s, centrePosition.Y),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
beatIndex = 0;
for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++)
{
AddInternal(new Circle
{
Origin = Anchor.Centre,
Size = new Vector2(10, 5),
Position = new Vector2(centrePosition.X, s),
Colour = GetColourForBeatIndex(beatIndex)
});
}
}
protected override float GetVelocity(double time, ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
=> Velocity;
public override Vector2 GetSnapPosition(Vector2 screenSpacePosition)
=> Vector2.Zero;
}
}
}

View File

@ -127,14 +127,47 @@ namespace osu.Game.Tests.Visual.Gameplay
exitAndConfirm();
}
[Test]
public void TestExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("exit", () => Player.Exit());
confirmExited();
}
[Test]
public void TestQuickRetryFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick retry", () => Player.GameplayClockContainer.OfType<HotkeyRetryOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
public void TestQuickExitFromFailedGameplay()
{
AddUntilStep("wait for fail", () => Player.HasFailed);
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]
public void TestExitFromGameplay()
{
AddStep("exit", () => Player.Exit());
confirmPaused();
confirmExited();
}
exitAndConfirm();
[Test]
public void TestQuickExitFromGameplay()
{
AddStep("quick exit", () => Player.GameplayClockContainer.OfType<HotkeyExitOverlay>().First().Action?.Invoke());
confirmExited();
}
[Test]

View File

@ -7,10 +7,16 @@ using System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
@ -18,25 +24,49 @@ using osu.Game.Scoring;
using osu.Game.Screens;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.PlayerSettings;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
{
public class TestScenePlayerLoader : ManualInputManagerTestScene
{
private TestPlayerLoader loader;
private OsuScreenStack stack;
private TestPlayerLoaderContainer container;
private TestPlayer player;
[SetUp]
public void Setup() => Schedule(() =>
[Resolved]
private AudioManager audioManager { get; set; }
[Resolved]
private SessionStatics sessionStatics { get; set; }
/// <summary>
/// Sets the input manager child to a new test player loader container instance.
/// </summary>
/// <param name="interactive">If the test player should behave like the production one.</param>
/// <param name="beforeLoadAction">An action to run before player load but after bindable leases are returned.</param>
/// <param name="afterLoadAction">An action to run after container load.</param>
public void ResetPlayer(bool interactive, Action beforeLoadAction = null, Action afterLoadAction = null)
{
InputManager.Child = stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both };
audioManager.Volume.SetDefault();
InputManager.Clear();
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
});
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() =>
{
afterLoadAction?.Invoke();
return player = new TestPlayer(interactive, interactive);
}));
}
[Test]
public void TestBlockLoadViaMouseMovement()
{
AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => new TestPlayer(false, false))));
AddStep("load dummy beatmap", () => ResetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddRepeatStep("move mouse", () => InputManager.MoveMouseTo(loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft + (loader.VisualSettings.ScreenSpaceDrawQuad.BottomRight - loader.VisualSettings.ScreenSpaceDrawQuad.TopLeft) * RNG.NextSingle()), 20);
AddAssert("loader still active", () => loader.IsCurrentScreen());
@ -46,16 +76,17 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestLoadContinuation()
{
Player player = null;
SlowLoadPlayer slowPlayer = null;
AddStep("load dummy beatmap", () => stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer(false, false))));
AddStep("load dummy beatmap", () => ResetPlayer(false));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
AddUntilStep("wait for player to be current", () => player.IsCurrentScreen());
AddStep("load slow dummy beatmap", () =>
{
stack.Push(loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() => slowPlayer = new SlowLoadPlayer(false, false)));
Scheduler.AddDelayed(() => slowPlayer.AllowLoad.Set(), 5000);
});
@ -65,16 +96,11 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestModReinstantiation()
{
TestPlayer player = null;
TestMod gameMod = null;
TestMod playerMod1 = null;
TestMod playerMod2 = null;
AddStep("load player", () =>
{
Mods.Value = new[] { gameMod = new TestMod() };
stack.Push(loader = new TestPlayerLoader(() => player = new TestPlayer()));
});
AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); });
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));
@ -97,6 +123,75 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("player mods applied", () => playerMod2.Applied);
}
[Test]
public void TestMutedNotificationMasterVolume() => addVolumeSteps("master volume", () => audioManager.Volume.Value = 0, null, () => audioManager.Volume.IsDefault);
[Test]
public void TestMutedNotificationTrackVolume() => addVolumeSteps("music volume", () => audioManager.VolumeTrack.Value = 0, null, () => audioManager.VolumeTrack.IsDefault);
[Test]
public void TestMutedNotificationMuteButton() => addVolumeSteps("mute button", null, () => container.VolumeOverlay.IsMuted.Value = true, () => !container.VolumeOverlay.IsMuted.Value);
/// <remarks>
/// Created for avoiding copy pasting code for the same steps.
/// </remarks>
/// <param name="volumeName">What part of the volume system is checked</param>
/// <param name="beforeLoad">The action to be invoked to set the volume before loading</param>
/// <param name="afterLoad">The action to be invoked to set the volume after loading</param>
/// <param name="assert">The function to be invoked and checked</param>
private void addVolumeSteps(string volumeName, Action beforeLoad, Action afterLoad, Func<bool> assert)
{
AddStep("reset notification lock", () => sessionStatics.GetBindable<bool>(Static.MutedAudioNotificationShownOnce).Value = false);
AddStep("load player", () => ResetPlayer(false, beforeLoad, afterLoad));
AddUntilStep("wait for player", () => player.IsLoaded);
AddAssert("check for notification", () => container.NotificationOverlay.UnreadCount.Value == 1);
AddStep("click notification", () =>
{
var scrollContainer = (OsuScrollContainer)container.NotificationOverlay.Children.Last();
var flowContainer = scrollContainer.Children.OfType<FillFlowContainer<NotificationSection>>().First();
var notification = flowContainer.First();
InputManager.MoveMouseTo(notification);
InputManager.Click(MouseButton.Left);
});
AddAssert("check " + volumeName, assert);
}
private class TestPlayerLoaderContainer : Container
{
[Cached]
public readonly NotificationOverlay NotificationOverlay;
[Cached]
public readonly VolumeOverlay VolumeOverlay;
public TestPlayerLoaderContainer(IScreen screen)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new OsuScreenStack(screen)
{
RelativeSizeAxes = Axes.Both,
},
NotificationOverlay = new NotificationOverlay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
VolumeOverlay = new VolumeOverlay
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
}
};
}
}
private class TestPlayerLoader : PlayerLoader
{
public new VisualSettings VisualSettings => base.VisualSettings;

View File

@ -5,12 +5,15 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
@ -18,7 +21,9 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps.IO;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@ -31,11 +36,11 @@ namespace osu.Game.Tests.Visual.Menus
private const float click_padding = 25;
private GameHost host;
private TestOsuGame osuGame;
private TestOsuGame game;
private Vector2 backButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, osuGame.LayoutRectangle.Bottom - click_padding));
private Vector2 backButtonPosition => game.ToScreenSpace(new Vector2(click_padding, game.LayoutRectangle.Bottom - click_padding));
private Vector2 optionsButtonPosition => osuGame.ToScreenSpace(new Vector2(click_padding, click_padding));
private Vector2 optionsButtonPosition => game.ToScreenSpace(new Vector2(click_padding, click_padding));
[BackgroundDependencyLoader]
private void load(GameHost host)
@ -54,23 +59,23 @@ namespace osu.Game.Tests.Visual.Menus
{
AddStep("Create new game instance", () =>
{
if (osuGame != null)
if (game != null)
{
Remove(osuGame);
osuGame.Dispose();
Remove(game);
game.Dispose();
}
osuGame = new TestOsuGame(LocalStorage, API);
osuGame.SetHost(host);
game = new TestOsuGame(LocalStorage, API);
game.SetHost(host);
// todo: this can be removed once we can run audio trakcs without a device present
// see https://github.com/ppy/osu/issues/1302
osuGame.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
game.LocalConfig.Set(OsuSetting.IntroSequence, IntroSequence.Circles);
Add(osuGame);
Add(game);
});
AddUntilStep("Wait for load", () => osuGame.IsLoaded);
AddUntilStep("Wait for intro", () => osuGame.ScreenStack.CurrentScreen is IntroScreen);
AddUntilStep("Wait for load", () => game.IsLoaded);
AddUntilStep("Wait for intro", () => game.ScreenStack.CurrentScreen is IntroScreen);
confirmAtMainMenu();
}
@ -82,11 +87,43 @@ namespace osu.Game.Tests.Visual.Menus
pushAndConfirm(() => songSelect = new TestSongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
AddStep("Press escape", () => pressAndRelease(Key.Escape));
pushEscape();
AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
exitViaEscapeAndConfirm();
}
[TestCase(true)]
[TestCase(false)]
public void TestSongContinuesAfterExitPlayer(bool withUserPause)
{
Player player = null;
WorkingBeatmap beatmap() => game.Beatmap.Value;
Track track() => beatmap().Track;
pushAndConfirm(() => new TestSongSelect());
AddStep("import beatmap", () => ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Wait());
AddUntilStep("wait for selected", () => !game.Beatmap.IsDefault);
if (withUserPause)
AddStep("pause", () => game.Dependencies.Get<MusicController>().Stop());
AddStep("press enter", () => pressAndRelease(Key.Enter));
AddUntilStep("wait for player", () => (player = game.ScreenStack.CurrentScreen as Player) != null);
AddUntilStep("wait for fail", () => player.HasFailed);
AddUntilStep("wait for track stop", () => !track().IsRunning);
AddAssert("Ensure time before preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
pushEscape();
AddUntilStep("wait for track playing", () => track().IsRunning);
AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime);
}
[Test]
public void TestExitSongSelectWithClick()
{
@ -98,7 +135,7 @@ namespace osu.Game.Tests.Visual.Menus
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == osuGame.BackButton));
AddUntilStep("Back button is hovered", () => InputManager.HoveredDrawables.Any(d => d.Parent == game.BackButton));
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
@ -122,25 +159,28 @@ namespace osu.Game.Tests.Visual.Menus
[Test]
public void TestOpenOptionsAndExitWithEscape()
{
AddUntilStep("Wait for options to load", () => osuGame.Settings.IsLoaded);
AddUntilStep("Wait for options to load", () => game.Settings.IsLoaded);
AddStep("Enter menu", () => pressAndRelease(Key.Enter));
AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition));
AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left));
AddAssert("Options overlay was opened", () => osuGame.Settings.State.Value == Visibility.Visible);
AddAssert("Options overlay was opened", () => game.Settings.State.Value == Visibility.Visible);
AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape));
AddAssert("Options overlay was closed", () => osuGame.Settings.State.Value == Visibility.Hidden);
AddAssert("Options overlay was closed", () => game.Settings.State.Value == Visibility.Hidden);
}
private void pushAndConfirm(Func<Screen> newScreen)
{
Screen screen = null;
AddStep("Push new screen", () => osuGame.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => osuGame.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
AddStep("Push new screen", () => game.ScreenStack.Push(screen = newScreen()));
AddUntilStep("Wait for new screen", () => game.ScreenStack.CurrentScreen == screen && screen.IsLoaded);
}
private void pushEscape() =>
AddStep("Press escape", () => pressAndRelease(Key.Escape));
private void exitViaEscapeAndConfirm()
{
AddStep("Press escape", () => pressAndRelease(Key.Escape));
pushEscape();
confirmAtMainMenu();
}
@ -151,7 +191,7 @@ namespace osu.Game.Tests.Visual.Menus
confirmAtMainMenu();
}
private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => osuGame.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
private void confirmAtMainMenu() => AddUntilStep("Wait for main menu", () => game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded);
private void pressAndRelease(Key key)
{
@ -169,6 +209,8 @@ namespace osu.Game.Tests.Visual.Menus
public new OsuConfigManager LocalConfig => base.LocalConfig;
public new Bindable<WorkingBeatmap> Beatmap => base.Beatmap;
protected override Loader CreateLoader() => new TestLoader();
public TestOsuGame(Storage storage, IAPIProvider api)

View File

@ -0,0 +1,58 @@
// 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.Game.Online.API.Requests;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Game.Overlays.Comments;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneCommentsContainer : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CommentsContainer),
typeof(CommentsHeader),
typeof(DrawableComment),
typeof(HeaderButton),
typeof(SortTabControl),
typeof(ShowChildrenButton),
typeof(DeletedChildrenPlaceholder)
};
protected override bool UseOnlineAPI => true;
public TestSceneCommentsContainer()
{
BasicScrollContainer scrollFlow;
Add(scrollFlow = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
});
AddStep("Big Black comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 41823));
});
AddStep("Airman comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 24313));
});
AddStep("lazer build comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Build, 4772));
});
}
}
}

View File

@ -0,0 +1,39 @@
// 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.Bindables;
using osu.Game.Overlays.Comments;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneCommentsHeader : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CommentsHeader),
typeof(HeaderButton),
typeof(SortTabControl),
};
private readonly Bindable<CommentsSortCriteria> sort = new Bindable<CommentsSortCriteria>();
private readonly BindableBool showDeleted = new BindableBool();
public TestSceneCommentsHeader()
{
Add(new CommentsHeader
{
Sort = { BindTarget = sort },
ShowDeleted = { BindTarget = showDeleted }
});
AddStep("Trigger ShowDeleted", () => showDeleted.Value = !showDeleted.Value);
AddStep("Select old", () => sort.Value = CommentsSortCriteria.Old);
AddStep("Select new", () => sort.Value = CommentsSortCriteria.New);
AddStep("Select top", () => sort.Value = CommentsSortCriteria.Top);
}
}
}

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