1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-14 21:53:40 +08:00

Compare commits

...

809 Commits

366 changed files with 8032 additions and 5025 deletions
+1 -4
View File
@@ -1,6 +1,3 @@
[submodule "osu-framework"]
path = osu-framework
url = https://github.com/ppy/osu-framework
[submodule "osu-resources"]
path = osu-resources
url = https://github.com/ppy/osu-resources
url = https://github.com/ppy/osu-resources
@@ -1,21 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (catch)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net471/osu.Game.Rulesets.Catch.Tests.exe" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp2.1/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" />
<envs>
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
<env name="ASPNETCORE_URLS" value="http://localhost:5000" />
</envs>
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<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=".NETFramework,Version=v4.7.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<browser url="http://localhost:5000" />
<method />
</configuration>
@@ -1,21 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (mania)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net471/osu.Game.Rulesets.Mania.Tests.exe" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp2.1/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" />
<envs>
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
<env name="ASPNETCORE_URLS" value="http://localhost:5000" />
</envs>
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<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=".NETFramework,Version=v4.7.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<browser url="http://localhost:5000" />
<method />
</configuration>
@@ -1,21 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (osu!)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net471/osu.Game.Rulesets.Osu.Tests.exe" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp2.1/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" />
<envs>
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
<env name="ASPNETCORE_URLS" value="http://localhost:5000" />
</envs>
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<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=".NETFramework,Version=v4.7.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<browser url="http://localhost:5000" />
<method />
</configuration>
@@ -1,21 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="RulesetTests (taiko)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net471/osu.Game.Rulesets.Taiko.Tests.exe" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp2.1/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" />
<envs>
<env name="ASPNETCORE_ENVIRONMENT" value="Development" />
<env name="ASPNETCORE_URLS" value="http://localhost:5000" />
</envs>
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<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=".NETFramework,Version=v4.7.1" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<browser url="http://localhost:5000" />
<method />
</configuration>
@@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="VisualTests (net471)" type="DotNetProject" factoryName=".NET Project" singleton="true">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
<option name="PASS_PARENT_ENVS" value="1" />
<envs />
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Game.Tests/osu.Game.Tests.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<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=".NETFramework,Version=v4.7.1" />
<method />
</configuration>
</component>
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="VisualTests (netcoreapp2.0)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll" />
<configuration default="false" name="VisualTests (netcoreapp2.1)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.1/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.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<method />
</configuration>
</component>
@@ -1,18 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (net471)" type="DotNetProject" factoryName=".NET Project" singleton="true">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net471/osu!.exe" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
<option name="PASS_PARENT_ENVS" value="1" />
<envs />
<option name="USE_MONO" value="0" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/osu.Desktop/osu.Desktop.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<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=".NETFramework,Version=v4.7.1" />
<method />
</configuration>
</component>
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="osu! (netcoreapp2.0)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.0/osu!.dll" />
<configuration default="false" name="osu! (netcoreapp2.1)" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.1/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.0" />
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v2.1" />
<method />
</configuration>
</component>
+12 -68
View File
@@ -2,110 +2,54 @@
"version": "0.2.0",
"configurations": [
{
"name": "VisualTests (Debug, net471)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, msbuild)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
},
{
"name": "VisualTests (Release, net471)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, msbuild)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
},
{
"name": "osu! (Debug, net471)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/osu.Desktop/bin/Debug/net471/osu!.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, msbuild)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
},
{
"name": "osu! (Release, net471)",
"windows": {
"type": "clr"
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/osu.Desktop/bin/Release/net471/osu!.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, msbuild)",
"runtimeExecutable": null,
"env": {},
"console": "internalConsole"
},
{
"name": "VisualTests (Debug, netcoreapp2.0)",
"name": "VisualTests (Debug)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll"
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, dotnet)",
"preLaunchTask": "Build tests (Debug)",
"env": {},
"console": "internalConsole"
},
{
"name": "VisualTests (Release, netcoreapp2.0)",
"name": "VisualTests (Release)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.0/osu.Game.Tests.dll"
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.1/osu.Game.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, dotnet)",
"preLaunchTask": "Build tests (Release)",
"env": {},
"console": "internalConsole"
},
{
"name": "osu! (Debug, netcoreapp2.0)",
"name": "osu! (Debug)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.0/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.1/osu!.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, dotnet)",
"preLaunchTask": "Build osu! (Debug)",
"env": {},
"console": "internalConsole"
},
{
"name": "osu! (Release, netcoreapp2.0)",
"name": "osu! (Release)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.0/osu!.dll",
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.1/osu!.dll",
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, dotnet)",
"preLaunchTask": "Build osu! (Release)",
"env": {},
"console": "internalConsole"
}
+33 -36
View File
@@ -4,41 +4,14 @@
"version": "2.0.0",
"tasks": [
{
"label": "Build (Debug, msbuild)",
"type": "shell",
"command": "msbuild",
"args": [
"/p:TargetFramework=net471",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Build (Release, msbuild)",
"type": "shell",
"command": "msbuild",
"args": [
"/p:Configuration=Release",
"/p:TargetFramework=net471",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Build (Debug, dotnet)",
"label": "Build osu! (Debug)",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"--no-restore",
"osu.Desktop",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
@@ -47,14 +20,14 @@
"problemMatcher": "$msCompile"
},
{
"label": "Build (Release, dotnet)",
"label": "Build osu! (Release)",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"--no-restore",
"osu.Desktop",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
@@ -64,16 +37,40 @@
"problemMatcher": "$msCompile"
},
{
"label": "Restore (net471)",
"label": "Build tests (Debug)",
"type": "shell",
"command": "nuget",
"command": "dotnet",
"args": [
"restore"
"build",
"--no-restore",
"osu.Game.Tests",
"/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
],
"problemMatcher": []
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Restore (netcoreapp2.0)",
"label": "Build tests (Release)",
"type": "shell",
"command": "dotnet",
"args": [
"build",
"--no-restore",
"osu.Game.Tests",
"/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
],
"group": "build",
"problemMatcher": "$msCompile"
},
{
"label": "Restore (netcoreapp2.1)",
"type": "shell",
"command": "dotnet",
"args": [
-36
View File
@@ -1,36 +0,0 @@
# Linux
### 1. Requirements:
Mono >= 5.4.0 (>= 5.8.0 recommended)
Please check [here](http://www.mono-project.com/download/) for stable or [here](http://www.mono-project.com/download/alpha/) for an alpha release.
NuGet >= 4.4.0
msbuild
git
### 2. Cloning project
Clone the entire repository with submodules using
```
git clone https://github.com/ppy/osu --recursive
```
Then restore NuGet packages from the repository
```
nuget restore
```
### 3. Compiling
Simply run `msbuild` where `osu.sln` is located, this will create all binaries in `osu/osu.Desktop/bin/Debug`.
### 4. Optimizing
If you want additional performance you can change build type to Release with
```
msbuild -p:Configuration=Release
```
Additionally, mono provides an AOT utility which attempts to precompile binaries. You can utilize that by running
```
mono --aot ./osu\!.exe
```
### 5. Troubleshooting
You may run into trouble with NuGet versioning, as the one in packaging system is almost always out of date. Simply run
```
nuget
sudo nuget update -self
```
**Warning** NuGet creates few config files when it's run for the first time.
Do not run NuGet as root on the first run or you might run into very peculiar issues.
-5
View File
@@ -2,11 +2,6 @@ clone_depth: 1
version: '{branch}-{build}'
image: Visual Studio 2017
configuration: Debug
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
- inspectcode -> appveyor.yml
- packages -> **\packages.config
install:
- cmd: git submodule update --init --recursive --depth=5
- cmd: choco install resharper-clt -y
+16 -18
View File
@@ -1,29 +1,24 @@
branches:
only:
- release
skip_tags: true
skip_branch_with_pr: true
clone_depth: 1
version: '{branch}-{build}'
version: '{build}'
skip_non_tags: true
image: Visual Studio 2017
configuration: Debug
cache:
- packages -> **\packages.config
install:
- cmd: git submodule update --init --recursive --depth=5
- git clone https://github.com/ppy/osu-deploy
before_build:
- ps: if($env:appveyor_repo_tag -eq 'True') { Update-AppveyorBuild -Version $env:appveyor_repo_tag_name }
- cmd: git submodule update --init --recursive --depth=5
- cmd: nuget restore -verbosity quiet
build:
project: osu.Desktop.Deploy/osu.Desktop.Deploy.csproj
verbosity: minimal
after_build:
build_script:
- ps: iex ((New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/appveyor/secure-file/master/install.ps1'))
- appveyor DownloadFile https://puu.sh/A6g5K/4d08705438.enc # signing certificate
- cmd: appveyor-tools\secure-file -decrypt 4d08705438.enc -secret %decode_secret% -out %HOMEPATH%\deanherbert.pfx
- appveyor DownloadFile https://puu.sh/A6g75/fdc6f19b04.enc # deploy configuration
- cmd: appveyor-tools\secure-file -decrypt fdc6f19b04.enc -secret %decode_secret% -out osu.Desktop.Deploy\bin\Debug\net471\osu.Desktop.Deploy.exe.config
- cd osu.Desktop.Deploy\bin\Debug\net471\
- osu.Desktop.Deploy.exe %code_signing_password%
- cd osu-deploy
- nuget restore -verbosity quiet
- msbuild osu.Desktop.Deploy.csproj
- cmd: ..\appveyor-tools\secure-file -decrypt ..\fdc6f19b04.enc -secret %decode_secret% -out bin\Debug\net471\osu.Desktop.Deploy.exe.config
- cd bin\Debug\net471\
- osu.Desktop.Deploy.exe %code_signing_password% %APPVEYOR_REPO_TAG_NAME%
environment:
TargetFramework: net471
decode_secret:
@@ -31,4 +26,7 @@ environment:
code_signing_password:
secure: 34tLNqvjmmZEi97MLKfrnQ==
artifacts:
- path: 'Releases\*'
- path: 'Releases\*'
deploy:
- provider: Environment
name: github
Submodule osu-framework deleted from f807997301
-21
View File
@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-->
<configuration>
<appSettings>
<add key="StagingFolder" value="Staging" />
<add key="ReleasesFolder" value="Releases" />
<add key="GitHubAccessToken" value="" />
<add key="GitHubUsername" value="ppy" />
<add key="GitHubRepoName" value="osu" />
<add key="ProjectName" value="osu.Desktop" />
<add key="NuSpecName" value="osu.Desktop\osu.nuspec" />
<add key="SolutionName" value="osu" />
<add key="TargetName" value="osu.Desktop" />
<add key="PackageName" value="osulazer" />
<add key="IconName" value="lazer.ico" />
<add key="CodeSigningCertificate" value="" />
</appSettings>
</configuration>
-28
View File
@@ -1,28 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
namespace osu.Desktop.Deploy
{
public class GitHubRelease
{
[JsonProperty(@"id")]
public int Id;
[JsonProperty(@"tag_name")]
public string TagName => $"v{Name}";
[JsonProperty(@"name")]
public string Name;
[JsonProperty(@"draft")]
public bool Draft;
[JsonProperty(@"prerelease")]
public bool PreRelease;
[JsonProperty(@"upload_url")]
public string UploadUrl;
}
}
-471
View File
@@ -1,471 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
using FileWebRequest = osu.Framework.IO.Network.FileWebRequest;
using WebRequest = osu.Framework.IO.Network.WebRequest;
namespace osu.Desktop.Deploy
{
internal static class Program
{
private static string packages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
private static string nugetPath => Path.Combine(packages, @"nuget.commandline\4.5.1\tools\NuGet.exe");
private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.8.0\tools\Squirrel.exe");
private const string msbuild_path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.exe";
public static string StagingFolder = ConfigurationManager.AppSettings["StagingFolder"];
public static string ReleasesFolder = ConfigurationManager.AppSettings["ReleasesFolder"];
public static string GitHubAccessToken = ConfigurationManager.AppSettings["GitHubAccessToken"];
public static string GitHubUsername = ConfigurationManager.AppSettings["GitHubUsername"];
public static string GitHubRepoName = ConfigurationManager.AppSettings["GitHubRepoName"];
public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"];
public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"];
public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"];
public static string TargetNames = ConfigurationManager.AppSettings["TargetName"];
public static string PackageName = ConfigurationManager.AppSettings["PackageName"];
public static string IconName = ConfigurationManager.AppSettings["IconName"];
public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"];
public static string GitHubApiEndpoint => $"https://api.github.com/repos/{GitHubUsername}/{GitHubRepoName}/releases";
public static string GitHubReleasePage => $"https://github.com/{GitHubUsername}/{GitHubRepoName}/releases";
/// <summary>
/// How many previous build deltas we want to keep when publishing.
/// </summary>
private const int keep_delta_count = 4;
private static string codeSigningCmd => string.IsNullOrEmpty(codeSigningPassword) ? "" : $"-n \"/a /f {codeSigningCertPath} /p {codeSigningPassword} /t http://timestamp.comodoca.com/authenticode\"";
private static string homeDir => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
private static string codeSigningCertPath => Path.Combine(homeDir, CodeSigningCertificate);
private static string solutionPath => Environment.CurrentDirectory;
private static string stagingPath => Path.Combine(solutionPath, StagingFolder);
private static string iconPath => Path.Combine(solutionPath, ProjectName, IconName);
private static string nupkgFilename(string ver) => $"{PackageName}.{ver}.nupkg";
private static string nupkgDistroFilename(string ver) => $"{PackageName}-{ver}-full.nupkg";
private static readonly Stopwatch sw = new Stopwatch();
private static string codeSigningPassword;
private static bool interactive;
public static void Main(string[] args)
{
interactive = args.Length == 0;
displayHeader();
findSolutionPath();
if (!Directory.Exists(ReleasesFolder))
{
write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow);
Directory.CreateDirectory(ReleasesFolder);
}
checkGitHubReleases();
refreshDirectory(StagingFolder);
//increment build number until we have a unique one.
string verBase = DateTime.Now.ToString("yyyy.Mdd.");
int increment = 0;
while (Directory.GetFiles(ReleasesFolder, $"*{verBase}{increment}*").Any())
increment++;
string version = $"{verBase}{increment}";
Console.ForegroundColor = ConsoleColor.White;
Console.Write($"Ready to deploy {version}!");
pauseIfInteractive();
sw.Start();
if (!string.IsNullOrEmpty(CodeSigningCertificate))
{
Console.Write("Enter code signing password: ");
codeSigningPassword = args.Length > 0 ? args[0] : readLineMasked();
}
write("Updating AssemblyInfo...");
updateCsprojVersion(version);
updateAppveyorVersion(version);
write("Running build process...");
foreach (string targetName in TargetNames.Split(','))
runCommand(msbuild_path, $"/v:quiet /m /t:{targetName.Replace('.', '_')} /p:OutputPath={stagingPath};Targets=\"Clean;Build\";Configuration=Release {SolutionName}.sln");
write("Creating NuGet deployment package...");
runCommand(nugetPath, $"pack {NuSpecName} -Version {version} -Properties Configuration=Deploy -OutputDirectory {stagingPath} -BasePath {stagingPath}");
//prune once before checking for files so we can avoid erroring on files which aren't even needed for this build.
pruneReleases();
checkReleaseFiles();
write("Running squirrel build...");
runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --framework-version=net471 --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi");
//prune again to clean up before upload.
pruneReleases();
//rename setup to install.
File.Copy(Path.Combine(ReleasesFolder, "Setup.exe"), Path.Combine(ReleasesFolder, "install.exe"), true);
File.Delete(Path.Combine(ReleasesFolder, "Setup.exe"));
uploadBuild(version);
//reset assemblyinfo.
updateCsprojVersion("0.0.0");
write("Done!", ConsoleColor.White);
pauseIfInteractive();
}
private static void displayHeader()
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine();
Console.WriteLine(" Please note that OSU! and PPY are registered trademarks and as such covered by trademark law.");
Console.WriteLine(" Do not distribute builds of this project publicly that make use of these.");
Console.ResetColor();
Console.WriteLine();
}
/// <summary>
/// Ensure we have all the files in the release directory which are expected to be there.
/// This should have been accounted for in earlier steps, and just serves as a verification step.
/// </summary>
private static void checkReleaseFiles()
{
if (!canGitHub) return;
var releaseLines = getReleaseLines();
//ensure we have all files necessary
foreach (var l in releaseLines)
if (!File.Exists(Path.Combine(ReleasesFolder, l.Filename)))
error($"Local file missing {l.Filename}");
}
private static IEnumerable<ReleaseLine> getReleaseLines() => File.ReadAllLines(Path.Combine(ReleasesFolder, "RELEASES")).Select(l => new ReleaseLine(l));
private static void pruneReleases()
{
if (!canGitHub) return;
write("Pruning RELEASES...");
var releaseLines = getReleaseLines().ToList();
var fulls = releaseLines.Where(l => l.Filename.Contains("-full")).Reverse().Skip(1);
//remove any FULL releases (except most recent)
foreach (var l in fulls)
{
write($"- Removing old release {l.Filename}", ConsoleColor.Yellow);
File.Delete(Path.Combine(ReleasesFolder, l.Filename));
releaseLines.Remove(l);
}
//remove excess deltas
var deltas = releaseLines.Where(l => l.Filename.Contains("-delta")).ToArray();
if (deltas.Length > keep_delta_count)
{
foreach (var l in deltas.Take(deltas.Length - keep_delta_count))
{
write($"- Removing old delta {l.Filename}", ConsoleColor.Yellow);
File.Delete(Path.Combine(ReleasesFolder, l.Filename));
releaseLines.Remove(l);
}
}
var lines = new List<string>();
releaseLines.ForEach(l => lines.Add(l.ToString()));
File.WriteAllLines(Path.Combine(ReleasesFolder, "RELEASES"), lines);
}
private static void uploadBuild(string version)
{
if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate))
return;
write("Publishing to GitHub...");
write($"- Creating release {version}...", ConsoleColor.Yellow);
var req = new JsonWebRequest<GitHubRelease>($"{GitHubApiEndpoint}")
{
Method = HttpMethod.POST,
};
req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease
{
Name = version,
Draft = true,
PreRelease = true
}));
req.AuthenticatedBlockingPerform();
var assetUploadUrl = req.ResponseObject.UploadUrl.Replace("{?name,label}", "?name={0}");
foreach (var a in Directory.GetFiles(ReleasesFolder).Reverse()) //reverse to upload RELEASES first.
{
write($"- Adding asset {a}...", ConsoleColor.Yellow);
var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a))
{
Method = HttpMethod.POST,
Timeout = 240000,
ContentType = "application/octet-stream",
};
upload.AddRaw(File.ReadAllBytes(a));
upload.AuthenticatedBlockingPerform();
}
openGitHubReleasePage();
}
private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage);
private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken);
private static void checkGitHubReleases()
{
if (!canGitHub) return;
write("Checking GitHub releases...");
var req = new JsonWebRequest<List<GitHubRelease>>($"{GitHubApiEndpoint}");
req.AuthenticatedBlockingPerform();
var lastRelease = req.ResponseObject.FirstOrDefault();
if (lastRelease == null)
return;
if (lastRelease.Draft)
{
openGitHubReleasePage();
error("There's a pending draft release! You probably don't want to push a build with this present.");
}
//there's a previous release for this project.
var assetReq = new JsonWebRequest<List<GitHubObject>>($"{GitHubApiEndpoint}/{lastRelease.Id}/assets");
assetReq.AuthenticatedBlockingPerform();
var assets = assetReq.ResponseObject;
//make sure our RELEASES file is the same as the last build on the server.
var releaseAsset = assets.FirstOrDefault(a => a.Name == "RELEASES");
//if we don't have a RELEASES asset then the previous release likely wasn't a Squirrel one.
if (releaseAsset == null) return;
write($"Last GitHub release was {lastRelease.Name}.");
bool requireDownload = false;
if (!File.Exists(Path.Combine(ReleasesFolder, nupkgDistroFilename(lastRelease.Name))))
{
write("Last version's package not found locally.", ConsoleColor.Red);
requireDownload = true;
}
else
{
var lastReleases = new RawFileWebRequest($"{GitHubApiEndpoint}/assets/{releaseAsset.Id}");
lastReleases.AuthenticatedBlockingPerform();
if (File.ReadAllText(Path.Combine(ReleasesFolder, "RELEASES")) != lastReleases.ResponseString)
{
write("Server's RELEASES differed from ours.", ConsoleColor.Red);
requireDownload = true;
}
}
if (!requireDownload) return;
write("Refreshing local releases directory...");
refreshDirectory(ReleasesFolder);
foreach (var a in assets)
{
if (a.Name.EndsWith(".exe")) continue;
write($"- Downloading {a.Name}...", ConsoleColor.Yellow);
new FileWebRequest(Path.Combine(ReleasesFolder, a.Name), $"{GitHubApiEndpoint}/assets/{a.Id}").AuthenticatedBlockingPerform();
}
}
private static void refreshDirectory(string directory)
{
if (Directory.Exists(directory))
Directory.Delete(directory, true);
Directory.CreateDirectory(directory);
}
private static void updateCsprojVersion(string version)
{
var toUpdate = new[] { "<Version>", "<FileVersion>" };
string file = Path.Combine(ProjectName, $"{ProjectName}.csproj");
var l1 = File.ReadAllLines(file);
List<string> l2 = new List<string>();
foreach (var l in l1)
{
string line = l;
foreach (var tag in toUpdate)
{
int startIndex = l.IndexOf(tag, StringComparison.InvariantCulture);
if (startIndex == -1)
continue;
startIndex += tag.Length;
int endIndex = l.IndexOf("<", startIndex, StringComparison.InvariantCulture);
line = $"{l.Substring(0, startIndex)}{version}{l.Substring(endIndex)}";
}
l2.Add(line);
}
File.WriteAllLines(file, l2);
}
/// <summary>
/// Find the base path of the active solution (git checkout location)
/// </summary>
private static void findSolutionPath()
{
string path = Path.GetDirectoryName(Environment.CommandLine.Replace("\"", "").Trim());
if (string.IsNullOrEmpty(path))
path = Environment.CurrentDirectory;
while (!File.Exists(Path.Combine(path, $"{SolutionName}.sln")))
path = path.Remove(path.LastIndexOf(Path.DirectorySeparatorChar));
path += Path.DirectorySeparatorChar;
Environment.CurrentDirectory = path;
}
private static bool runCommand(string command, string args)
{
var psi = new ProcessStartInfo(command, args)
{
WorkingDirectory = solutionPath,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden
};
Process p = Process.Start(psi);
if (p == null) return false;
string output = p.StandardOutput.ReadToEnd();
output += p.StandardError.ReadToEnd();
if (p.ExitCode == 0) return true;
write(output);
error($"Command {command} {args} failed!");
return false;
}
private static string readLineMasked()
{
var fg = Console.ForegroundColor;
Console.ForegroundColor = Console.BackgroundColor;
var ret = Console.ReadLine();
Console.ForegroundColor = fg;
return ret;
}
private static void error(string message)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"FATAL ERROR: {message}");
pauseIfInteractive();
Environment.Exit(-1);
}
private static void pauseIfInteractive()
{
if (interactive)
Console.ReadLine();
else
Console.WriteLine();
}
private static bool updateAppveyorVersion(string version)
{
try
{
using (PowerShell ps = PowerShell.Create())
{
ps.AddScript($"Update-AppveyorBuild -Version \"{version}\"");
ps.Invoke();
}
return true;
}
catch
{
// we don't have appveyor and don't care
}
return false;
}
private static void write(string message, ConsoleColor col = ConsoleColor.Gray)
{
if (sw.ElapsedMilliseconds > 0)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.Write(sw.ElapsedMilliseconds.ToString().PadRight(8));
}
Console.ForegroundColor = col;
Console.WriteLine(message);
}
public static void AuthenticatedBlockingPerform(this WebRequest r)
{
r.AddHeader("Authorization", $"token {GitHubAccessToken}");
r.Perform();
}
}
internal class RawFileWebRequest : WebRequest
{
public RawFileWebRequest(string url) : base(url)
{
}
protected override string Accept => "application/octet-stream";
}
internal class ReleaseLine
{
public string Hash;
public string Filename;
public int Filesize;
public ReleaseLine(string line)
{
var split = line.Split(' ');
Hash = split[0];
Filename = split[1];
Filesize = int.Parse(split[2]);
}
public override string ToString() => $"{Hash} {Filename} {Filesize}";
}
}
@@ -1,19 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project">
<TargetFrameworks>net471</TargetFrameworks>
<OutputType>Exe</OutputType>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="NuGet.CommandLine" Version="4.5.1" />
<PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
<PackageReference Include="System.Management.Automation.dll" Version="10.0.10586" />
</ItemGroup>
</Project>
+2 -1
View File
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
using osu.Game;
using OpenTK.Input;
using Microsoft.Win32;
using osu.Framework.Platform.Windows;
namespace osu.Desktop
{
@@ -40,7 +41,7 @@ namespace osu.Desktop
/// <summary>
/// A method of accessing an osu-stable install in a controlled fashion.
/// </summary>
private class StableStorage : DesktopStorage
private class StableStorage : WindowsStorage
{
protected override string LocateBasePath()
{
+2 -2
View File
@@ -3,7 +3,6 @@
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -14,6 +13,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Utils;
using OpenTK;
using OpenTK.Graphics;
@@ -118,7 +118,7 @@ namespace osu.Desktop.Overlays
Icon = FontAwesome.fa_check_square;
Activated = delegate
{
Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}");
Process.Start($"https://osu.ppy.sh/home/changelog/{version}");
return true;
};
}
+5 -6
View File
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.Game.props" />
<PropertyGroup Label="Project">
<TargetFrameworks>net471;netcoreapp2.0</TargetFrameworks>
<TargetFrameworks>net471;netcoreapp2.1</TargetFrameworks>
<OutputType>WinExe</OutputType>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
@@ -20,20 +20,19 @@
<StartupObject>osu.Desktop.Program</StartupObject>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu-framework\osu.Framework\osu.Framework.csproj" />
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
<ProjectReference Include="..\osu-resources\osu.Game.Resources\osu.Game.Resources.csproj" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.0" />
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
</ItemGroup>
<ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" />
</ItemGroup>
</Project>
</Project>
+5 -5
View File
@@ -22,7 +22,7 @@
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Catch.Tests.exe",
"program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Catch.Tests.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, msbuild)",
"runtimeExecutable": null,
@@ -30,12 +30,12 @@
"console": "internalConsole"
},
{
"name": "VisualTests (Debug, netcoreapp2.0)",
"name": "VisualTests (Debug, netcoreapp2.1)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Catch.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, dotnet)",
@@ -43,12 +43,12 @@
"console": "internalConsole"
},
{
"name": "VisualTests (Release, netcoreapp2.0)",
"name": "VisualTests (Release, netcoreapp2.1)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Catch.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Catch.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, dotnet)",
+3 -3
View File
@@ -40,7 +40,7 @@
"build",
"--no-restore",
"osu.Game.Rulesets.Catch.Tests.csproj",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
@@ -56,7 +56,7 @@
"build",
"--no-restore",
"osu.Game.Rulesets.Catch.Tests.csproj",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
@@ -75,7 +75,7 @@
"problemMatcher": []
},
{
"label": "Restore (netcoreapp2.0)",
"label": "Restore (netcoreapp2.1)",
"type": "shell",
"command": "dotnet",
"args": [
@@ -5,8 +5,6 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
@@ -14,7 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests
{
public class CatchBeatmapConversionTest : BeatmapConversionTest<TestCatchRuleset, ConvertValue>
internal class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
@@ -47,10 +45,10 @@ namespace osu.Game.Rulesets.Catch.Tests
}
}
protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
protected override Ruleset CreateRuleset() => new CatchRuleset();
}
public struct ConvertValue : IEquatable<ConvertValue>
internal struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.
@@ -64,8 +62,4 @@ namespace osu.Game.Rulesets.Catch.Tests
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(Position, other.Position, conversion_lenience);
}
public class TestCatchRuleset : CatchRuleset
{
}
}
@@ -53,10 +53,10 @@ namespace osu.Game.Rulesets.Catch.Tests
return beatmap;
}
protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset)
protected override Player CreatePlayer(Ruleset ruleset)
{
beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
return base.CreatePlayer(beatmap, ruleset);
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
return base.CreatePlayer(ruleset);
}
}
}
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Tests
Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft
Origin = Anchor.TopLeft
},
};
}
@@ -25,6 +25,7 @@ namespace osu.Game.Rulesets.Catch.Tests
typeof(DrawableCatchHitObject),
typeof(DrawableFruit),
typeof(DrawableDroplet),
typeof(BananaShower),
typeof(Pulp),
};
@@ -53,12 +54,19 @@ namespace osu.Game.Rulesets.Catch.Tests
private DrawableFruit createDrawable(int index)
{
var fruit = new Fruit
{
StartTime = 1000000000000,
IndexInBeatmap = index,
Scale = 1.5f,
};
Fruit fruit = index == 5
? new BananaShower.Banana
{
StartTime = 1000000000000,
IndexInBeatmap = index,
Scale = 1.5f,
}
: new Fruit
{
StartTime = 1000000000000,
IndexInBeatmap = index,
Scale = 1.5f,
};
return new DrawableFruit(fruit)
{
@@ -1,16 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
: base(new CatchRuleset())
{
}
}
}
@@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFrameworks>netcoreapp2.0;net471</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
@@ -28,7 +28,20 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
var endTime = obj as IHasEndTime;
if (positionData == null)
{
if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false
};
}
yield break;
}
if (curveData != null)
{
@@ -48,19 +61,6 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
yield break;
}
if (endTime != null)
{
yield return new BananaShower
{
StartTime = obj.StartTime,
Samples = obj.Samples,
Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false
};
yield break;
}
yield return new Fruit
{
StartTime = obj.StartTime,
@@ -1,17 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Catch
{
public class CatchDifficultyCalculator : DifficultyCalculator
{
public CatchDifficultyCalculator(IBeatmap beatmap) : base(beatmap)
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
}
}
+7 -36
View File
@@ -14,6 +14,8 @@ using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Difficulty;
using osu.Game.Rulesets.Difficulty;
namespace osu.Game.Rulesets.Catch
{
@@ -80,56 +82,25 @@ namespace osu.Game.Rulesets.Catch
{
new CatchModEasy(),
new CatchModNoFail(),
new MultiMod
{
Mods = new Mod[]
{
new CatchModHalfTime(),
new CatchModDaycore(),
},
},
new MultiMod(new CatchModHalfTime(), new CatchModDaycore())
};
case ModType.DifficultyIncrease:
return new Mod[]
{
new CatchModHardRock(),
new MultiMod
{
Mods = new Mod[]
{
new CatchModSuddenDeath(),
new CatchModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new CatchModDoubleTime(),
new CatchModNightcore(),
},
},
new MultiMod(new CatchModSuddenDeath(), new CatchModPerfect()),
new MultiMod(new CatchModDoubleTime(), new CatchModNightcore()),
new CatchModHidden(),
new CatchModFlashlight(),
};
case ModType.Special:
return new Mod[]
{
new CatchModRelax(),
null,
null,
new MultiMod
{
Mods = new Mod[]
{
new CatchModAutoplay(),
new ModCinema(),
},
},
new MultiMod(new CatchModAutoplay(), new ModCinema()),
};
default:
return new Mod[] { };
}
@@ -141,7 +112,7 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new CatchDifficultyCalculator(this, beatmap);
public override int? LegacyID => 2;
@@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Difficulty
{
public class CatchDifficultyCalculator : DifficultyCalculator
{
public CatchDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate) => new DifficultyAttributes(mods, 0);
}
}
@@ -47,6 +47,8 @@ namespace osu.Game.Rulesets.Catch.Objects
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
}
protected override HitWindows CreateHitWindows() => null;
}
public enum FruitVisualRepresentation
@@ -42,6 +42,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public virtual bool CanBePlated => false;
public virtual bool StaysOnPlate => CanBePlated;
protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject)
{
@@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
private Pulp pulp;
public override bool StaysOnPlate => false;
public DrawableDroplet(Droplet h)
: base(h)
{
@@ -18,12 +18,19 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
private Circle border;
private const float drawable_radius = (float)CatchHitObject.OBJECT_RADIUS * radius_adjust;
/// <summary>
/// Because we're adding a border around the fruit, we need to scale down some.
/// </summary>
private const float radius_adjust = 1.1f;
public DrawableFruit(Fruit h)
: base(h)
{
Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS);
Size = new Vector2(drawable_radius);
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
@@ -44,14 +51,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
Hollow = !HitObject.HyperDash,
Type = EdgeEffectType.Glow,
Radius = 4,
Radius = 4 * radius_adjust,
Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f)
},
Size = new Vector2(Height * 1.5f),
Size = new Vector2(Height),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BorderColour = Color4.White,
BorderThickness = 4f,
BorderThickness = 3f * radius_adjust,
Children = new Framework.Graphics.Drawable[]
{
new Box
@@ -82,8 +89,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation)
{
const float large_pulp_3 = 13f;
const float distance_from_centre_3 = 0.23f;
const float large_pulp_3 = 8f * radius_adjust;
const float distance_from_centre_3 = 0.15f;
const float large_pulp_4 = large_pulp_3 * 0.925f;
const float distance_from_centre_4 = distance_from_centre_3 / 0.925f;
@@ -106,11 +113,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
Y = 0.05f,
Y = -0.34f,
},
new Pulp
{
@@ -146,11 +151,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
Y = 0.1f,
Y = -0.3f,
},
new Pulp
{
@@ -186,11 +189,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
Y = -0.1f,
Y = -0.33f,
},
new Pulp
{
@@ -220,10 +221,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
Y = -0.25f,
},
new Pulp
{
@@ -253,16 +253,15 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
new Pulp
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AccentColour = AccentColour,
Size = new Vector2(small_pulp),
Y = -0.15f
Y = -0.3f
},
new Pulp
{
AccentColour = AccentColour,
Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3),
Size = new Vector2(large_pulp_4 * 0.8f, large_pulp_4 * 2.5f),
Y = 0.05f,
},
}
};
@@ -29,14 +29,24 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
set
{
accentColour = value;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 8,
Colour = accentColour.Darken(0.2f).Opacity(0.75f)
};
if (IsLoaded) updateAccentColour();
}
}
private void updateAccentColour()
{
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = Size.X / 2,
Colour = accentColour.Darken(0.2f).Opacity(0.75f)
};
}
protected override void LoadComplete()
{
base.LoadComplete();
updateAccentColour();
}
}
}
@@ -124,6 +124,9 @@ namespace osu.Game.Rulesets.Catch.Objects
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
if (NestedHitObjects.LastOrDefault() is IHasComboInformation lastNested)
lastNested.LastInCombo = LastInCombo;
}
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
@@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.UI
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
protected override Vector2 PlayfieldArea => new Vector2(0.86f); // matches stable's vertical offset for catcher plate
protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
{
switch (h)
+52 -36
View File
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatcherArea : Container
{
public const float CATCHER_SIZE = 172;
public const float CATCHER_SIZE = 84;
protected readonly Catcher MovableCatcher;
@@ -48,6 +48,16 @@ namespace osu.Game.Rulesets.Catch.UI
public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement)
{
void runAfterLoaded(Action action)
{
// this is required to make this run after the last caught fruit runs UpdateState at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
action();
else
lastPlateableFruit.OnLoadComplete = _ => action();
}
if (judgement.IsHit && fruit.CanBePlated)
{
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
@@ -63,21 +73,17 @@ namespace osu.Game.Rulesets.Catch.UI
caughtFruit.LifetimeEnd = double.MaxValue;
MovableCatcher.Add(caughtFruit);
lastPlateableFruit = caughtFruit;
if (!fruit.StaysOnPlate)
runAfterLoaded(() => MovableCatcher.Explode(caughtFruit));
}
if (fruit.HitObject.LastInCombo)
{
if (judgement.IsHit)
{
// this is required to make this run after the last caught fruit runs UpdateState at least once.
// TODO: find a better alternative
if (lastPlateableFruit.IsLoaded)
MovableCatcher.Explode();
else
lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); };
}
runAfterLoaded(() => MovableCatcher.Explode());
else
MovableCatcher.Drop();
}
@@ -99,8 +105,6 @@ namespace osu.Game.Rulesets.Catch.UI
public class Catcher : Container, IKeyBindingHandler<CatchAction>
{
private Texture texture;
private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget;
@@ -121,10 +125,8 @@ namespace osu.Game.Rulesets.Catch.UI
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
private void load()
{
texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Children = new Drawable[]
{
caughtFruit = new Container<DrawableHitObject>
@@ -196,13 +198,7 @@ namespace osu.Game.Rulesets.Catch.UI
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
}
private Sprite createCatcherSprite() => new Sprite
{
Size = new Vector2(CATCHER_SIZE),
FillMode = FillMode.Fill,
Texture = texture,
OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
};
private Sprite createCatcherSprite() => new CatcherSprite();
/// <summary>
/// Add a caught fruit to the catcher's stack.
@@ -388,27 +384,47 @@ namespace osu.Game.Rulesets.Catch.UI
var fruit = caughtFruit.ToArray();
foreach (var f in fruit)
Explode(f);
}
public void Explode(DrawableHitObject fruit)
{
var originalX = fruit.X * Scale.X;
if (ExplodingFruitTarget != null)
{
var originalX = f.X * Scale.X;
fruit.Anchor = Anchor.TopLeft;
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
if (ExplodingFruitTarget != null)
{
f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(fruit);
caughtFruit.Remove(f);
ExplodingFruitTarget.Add(fruit);
}
ExplodingFruitTarget.Add(f);
}
fruit.MoveToY(fruit.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(fruit.Y + 50, 500, Easing.InSine);
f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then()
.MoveToY(f.Y + 50, 500, Easing.InSine);
fruit.MoveToX(fruit.X + originalX * 6, 1000);
fruit.FadeOut(750);
f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750);
fruit.Expire();
}
f.Expire();
private class CatcherSprite : Sprite
{
public CatcherSprite()
{
Size = new Vector2(CATCHER_SIZE);
// Sets the origin roughly to the centre of the catcher's plate to allow for correct scaling.
OriginPosition = new Vector2(-0.02f, 0.06f) * CATCHER_SIZE;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
}
}
}
+5 -5
View File
@@ -22,7 +22,7 @@
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Mania.Tests.exe",
"program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Mania.Tests.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, msbuild)",
"runtimeExecutable": null,
@@ -30,12 +30,12 @@
"console": "internalConsole"
},
{
"name": "VisualTests (Debug, netcoreapp2.0)",
"name": "VisualTests (Debug, netcoreapp2.1)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Mania.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, dotnet)",
@@ -43,12 +43,12 @@
"console": "internalConsole"
},
{
"name": "VisualTests (Release, netcoreapp2.0)",
"name": "VisualTests (Release, netcoreapp2.1)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Mania.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Mania.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, dotnet)",
+3 -3
View File
@@ -40,7 +40,7 @@
"build",
"--no-restore",
"osu.Game.Rulesets.Mania.Tests.csproj",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
@@ -56,7 +56,7 @@
"build",
"--no-restore",
"osu.Game.Rulesets.Mania.Tests.csproj",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
@@ -75,7 +75,7 @@
"problemMatcher": []
},
{
"label": "Restore (netcoreapp2.0)",
"label": "Restore (netcoreapp2.1)",
"type": "shell",
"command": "dotnet",
"args": [
@@ -5,8 +5,6 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
@@ -14,7 +12,7 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests
{
public class ManiaBeatmapConversionTest : BeatmapConversionTest<TestManiaRuleset, ConvertValue>
internal class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
@@ -35,10 +33,10 @@ namespace osu.Game.Rulesets.Mania.Tests
};
}
protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
protected override Ruleset CreateRuleset() => new ManiaRuleset();
}
public struct ConvertValue : IEquatable<ConvertValue>
internal struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.
@@ -54,8 +52,4 @@ namespace osu.Game.Rulesets.Mania.Tests
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
&& Column == other.Column;
}
public class TestManiaRuleset : ManiaRuleset
{
}
}
@@ -4,6 +4,8 @@
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Tests.Visual;
@@ -17,6 +19,14 @@ namespace osu.Game.Rulesets.Mania.Tests
{
public TestCaseManiaHitObjects()
{
Note note1 = new Note();
Note note2 = new Note();
HoldNote holdNote = new HoldNote { StartTime = 1000 };
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
@@ -43,14 +53,14 @@ namespace osu.Game.Rulesets.Mania.Tests
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
new DrawableNote(new Note(), ManiaAction.Key1)
new DrawableNote(note1, ManiaAction.Key1)
{
Y = 5000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red
},
new DrawableNote(new Note(), ManiaAction.Key1)
new DrawableNote(note2, ManiaAction.Key1)
{
Y = 6000,
LifetimeStart = double.MinValue,
@@ -77,13 +87,13 @@ namespace osu.Game.Rulesets.Mania.Tests
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1)
new DrawableHoldNote(holdNote, ManiaAction.Key1)
{
Y = 5000,
Height = 1000,
LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red
AccentColour = Color4.Red,
}
}
}
@@ -8,6 +8,8 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
@@ -83,27 +85,25 @@ namespace osu.Game.Rulesets.Mania.Tests
int col = rng.Next(0, 4);
var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1)
var note = new Note { Column = col };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
var drawableNote = new DrawableNote(note, ManiaAction.Key1)
{
AccentColour = playfield.Columns.ElementAt(col).AccentColour
};
playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
playfield.OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
playfield.Columns[col].OnJudgement(drawableNote, new ManiaJudgement { Result = HitResult.Perfect });
});
}
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets, SettingsStore settings)
{
maniaRuleset = rulesets.GetRuleset(3);
dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
Dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
}
private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
@@ -162,32 +162,24 @@ namespace osu.Game.Rulesets.Mania.Tests
for (double t = start_time; t <= start_time + duration; t += 100)
{
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 0
}, ManiaAction.Key1));
var note1 = new Note { StartTime = t, Column = 0 };
var note2 = new Note { StartTime = t, Column = 3 };
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 3
}, ManiaAction.Key4));
note1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
note2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableNote(note1, ManiaAction.Key1));
playfield.Add(new DrawableNote(note2, ManiaAction.Key4));
}
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 1
}, ManiaAction.Key2));
var holdNote1 = new HoldNote { StartTime = start_time, Duration = duration, Column = 1 };
var holdNote2 = new HoldNote { StartTime = start_time, Duration = duration, Column = 2 };
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 2
}, ManiaAction.Key3));
holdNote1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
holdNote2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
playfield.Add(new DrawableHoldNote(holdNote1, ManiaAction.Key2));
playfield.Add(new DrawableHoldNote(holdNote2, ManiaAction.Key3));
}
}
}
@@ -1,16 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
namespace osu.Game.Rulesets.Mania.Tests
{
[TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
: base(new ManiaRuleset())
{
}
}
}
@@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFrameworks>netcoreapp2.0;net471</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
@@ -59,7 +59,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
{
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty;
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
@@ -58,6 +58,13 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
public override Pattern Generate()
{
if (TotalColumns == 1)
{
var pattern = new Pattern();
addToPattern(pattern, 0, HitObject.StartTime, endTime);
return pattern;
}
if (spanCount > 1)
{
if (segmentDuration <= 90)
@@ -77,10 +77,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
}
else
convertType |= PatternType.LowProbability;
if ((convertType & PatternType.KeepSingle) == 0)
{
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && TotalColumns != 8)
convertType |= PatternType.Mirror;
else
convertType |= PatternType.Gathered;
}
}
public override Pattern Generate()
{
if (TotalColumns == 1)
{
var pattern = new Pattern();
addToPattern(pattern, 0);
return pattern;
}
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
@@ -346,7 +361,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
addToCentre = false;
if ((convertType & PatternType.ForceNotStack) > 0)
return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3);
return getRandomNoteCount(1 / 2f + p2 / 2, p2, (p2 + p3) / 2, p3);
switch (TotalColumns)
{
@@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
{
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant)
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant)
{
}
@@ -1,14 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania
namespace osu.Game.Rulesets.Mania.Difficulty
{
internal class ManiaDifficultyCalculator : DifficultyCalculator
{
@@ -26,48 +29,33 @@ namespace osu.Game.Rulesets.Mania
/// </summary>
private const double decay_weight = 0.9;
/// <summary>
/// HitObjects are stored as a member variable.
/// </summary>
private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
public ManiaDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
public ManiaDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
: base(beatmap, mods)
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
}
var difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
// Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear();
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
foreach (var hitObject in Beatmap.HitObjects)
difficultyHitObjects.Add(new ManiaHitObjectDifficulty((ManiaHitObject)hitObject, columnCount));
int columnCount = ((ManiaBeatmap)beatmap).TotalColumns;
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
// Note: Stable sort is done so that the ordering of hitobjects with equal start times doesn't change
difficultyHitObjects.AddRange(beatmap.HitObjects.Select(h => new ManiaHitObjectDifficulty((ManiaHitObject)h, columnCount)).OrderBy(h => h.BaseHitObject.StartTime));
if (!calculateStrainValues())
return 0;
if (!calculateStrainValues(difficultyHitObjects, timeRate))
return new DifficultyAttributes(mods, 0);
double starRating = calculateDifficulty() * star_scaling_factor;
double starRating = calculateDifficulty(difficultyHitObjects, timeRate) * star_scaling_factor;
categoryDifficulty?.Add("Strain", starRating);
return starRating;
return new DifficultyAttributes(mods, starRating);
}
private bool calculateStrainValues()
private bool calculateStrainValues(List<ManiaHitObjectDifficulty> objects, double timeRate)
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
using (List<ManiaHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator())
using (var hitObjectsEnumerator = objects.GetEnumerator())
{
if (!hitObjectsEnumerator.MoveNext())
return false;
@@ -78,7 +66,7 @@ namespace osu.Game.Rulesets.Mania
while (hitObjectsEnumerator.MoveNext())
{
var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate);
next?.CalculateStrains(current, timeRate);
current = next;
}
@@ -86,9 +74,9 @@ namespace osu.Game.Rulesets.Mania
}
}
private double calculateDifficulty()
private double calculateDifficulty(List<ManiaHitObjectDifficulty> objects, double timeRate)
{
double actualStrainStep = strain_step * TimeRate;
double actualStrainStep = strain_step * timeRate;
// Find the highest strain value within each strain step
List<double> highestStrains = new List<double>();
@@ -96,7 +84,7 @@ namespace osu.Game.Rulesets.Mania
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
ManiaHitObjectDifficulty previousHitObject = null;
foreach (var hitObject in difficultyHitObjects)
foreach (var hitObject in objects)
{
// While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
@@ -140,5 +128,23 @@ namespace osu.Game.Rulesets.Mania
return difficulty;
}
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new ManiaModDoubleTime(),
new ManiaModHalfTime(),
new ManiaModEasy(),
new ManiaModHardRock(),
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3(),
new ManiaModKey4(),
new ManiaModKey5(),
new ManiaModKey6(),
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
};
}
}
@@ -0,0 +1,127 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Difficulty
{
public class ManiaPerformanceCalculator : PerformanceCalculator
{
private Mod[] mods;
// Score after being scaled by non-difficulty-increasing mods
private double scaledScore;
private int countPerfect;
private int countGreat;
private int countGood;
private int countOk;
private int countMeh;
private int countMiss;
public ManiaPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
mods = Score.Mods;
scaledScore = Score.TotalScore;
countPerfect = Convert.ToInt32(Score.Statistics[HitResult.Perfect]);
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countOk = Convert.ToInt32(Score.Statistics[HitResult.Ok]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
if (mods.Any(m => !m.Ranked))
return 0;
IEnumerable<Mod> scoreIncreaseMods = Ruleset.GetModsFor(ModType.DifficultyIncrease);
double scoreMultiplier = 1.0;
foreach (var m in mods.Where(m => !scoreIncreaseMods.Contains(m)))
scoreMultiplier *= m.ScoreMultiplier;
// Scale score up, so it's comparable to other keymods
scaledScore *= 1.0 / scoreMultiplier;
// Arbitrary initial value for scaling pp in order to standardize distributions across game modes.
// The specific number has no intrinsic meaning and can be adjusted as needed.
double multiplier = 0.8;
if (mods.Any(m => m is ModNoFail))
multiplier *= 0.9;
if (mods.Any(m => m is ModEasy))
multiplier *= 0.5;
double strainValue = computeStrainValue();
double accValue = computeAccuracyValue(strainValue);
double totalValue =
Math.Pow(
Math.Pow(strainValue, 1.1) +
Math.Pow(accValue, 1.1), 1.0 / 1.1
) * multiplier;
if (categoryDifficulty != null)
{
categoryDifficulty["Strain"] = strainValue;
categoryDifficulty["Accuracy"] = accValue;
}
return totalValue;
}
private double computeStrainValue()
{
// Obtain strain difficulty
double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0;
// Longer maps are worth more
strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0);
if (scaledScore <= 500000)
strainValue = 0;
else if (scaledScore <= 600000)
strainValue *= (scaledScore - 500000) / 100000 * 0.3;
else if (scaledScore <= 700000)
strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25;
else if (scaledScore <= 800000)
strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20;
else if (scaledScore <= 900000)
strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15;
else
strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1;
return strainValue;
}
private double computeAccuracyValue(double strainValue)
{
// Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
if (hitWindowGreat <= 0)
return 0;
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Max(0.0, 0.2 - (hitWindowGreat - 34) * 0.006667)
* strainValue
* Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1);
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
// accuracyValue *= Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
return accuracyValue;
}
private double totalHits => countPerfect + countOk + countGreat + countGood + countMeh + countMiss;
}
}
@@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements
{
public class HoldNoteJudgement : ManiaJudgement
{
public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 0;
}
}
+24 -59
View File
@@ -15,7 +15,13 @@ using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Difficulty;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania
{
@@ -23,6 +29,7 @@ namespace osu.Game.Rulesets.Mania
{
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap) => new ManiaRulesetContainer(this, beatmap);
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
{
@@ -101,78 +108,34 @@ namespace osu.Game.Rulesets.Mania
{
new ManiaModEasy(),
new ManiaModNoFail(),
new MultiMod
{
Mods = new Mod[]
{
new ManiaModHalfTime(),
new ManiaModDaycore(),
},
},
new MultiMod(new ManiaModHalfTime(), new ManiaModDaycore()),
};
case ModType.DifficultyIncrease:
return new Mod[]
{
new ManiaModHardRock(),
new MultiMod
{
Mods = new Mod[]
{
new ManiaModSuddenDeath(),
new ManiaModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new ManiaModDoubleTime(),
new ManiaModNightcore(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new ManiaModFadeIn(),
new ManiaModHidden(),
}
},
new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()),
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
new ManiaModFlashlight(),
};
case ModType.Special:
return new Mod[]
{
new MultiMod
{
Mods = new Mod[]
{
new ManiaModKey4(),
new ManiaModKey5(),
new ManiaModKey6(),
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3(),
},
},
new MultiMod(new ManiaModKey4(),
new ManiaModKey5(),
new ManiaModKey6(),
new ManiaModKey7(),
new ManiaModKey8(),
new ManiaModKey9(),
new ManiaModKey1(),
new ManiaModKey2(),
new ManiaModKey3()),
new ManiaModRandom(),
new ManiaModDualStages(),
new ManiaModMirror(),
new MultiMod
{
Mods = new Mod[]
{
new ManiaModAutoplay(),
new ModCinema(),
},
},
new MultiMod(new ManiaModAutoplay(), new ModCinema()),
};
default:
return new Mod[] { };
}
@@ -184,12 +147,14 @@ namespace osu.Game.Rulesets.Mania
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new ManiaDifficultyCalculator(this, beatmap);
public override int? LegacyID => 3;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new ManiaConfigManager(settings, RulesetInfo);
public ManiaRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
@@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mods;
@@ -24,5 +26,18 @@ namespace osu.Game.Rulesets.Mania.Mods
mbc.TargetColumns = KeyCount;
}
public override Type[] IncompatibleMods => new[]
{
typeof(ManiaModKey1),
typeof(ManiaModKey2),
typeof(ManiaModKey3),
typeof(ManiaModKey4),
typeof(ManiaModKey5),
typeof(ManiaModKey6),
typeof(ManiaModKey7),
typeof(ManiaModKey8),
typeof(ManiaModKey9),
}.Except(new[] { GetType() }).ToArray();
}
}
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string Name => "Dual Stages";
public override string ShortenedName => "DS";
public override string Description => @"Double the stages, double the fun!";
public override double ScoreMultiplier => 0;
public override double ScoreMultiplier => 1;
private bool isForCurrentRuleset;
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Mods
public override string ShortenedName => "RD";
public override FontAwesome Icon => FontAwesome.fa_osu_dice;
public override string Description => @"Shuffle around the keys!";
public override double ScoreMultiplier => 0;
public override double ScoreMultiplier => 1;
public void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer)
{
@@ -5,6 +5,7 @@ using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
@@ -28,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
: base(barLine)
{
RelativeSizeAxes = Axes.X;
Height = 1;
Height = 2f;
AddInternal(new Box
{
@@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
Colour = new Color4(255, 204, 33, 255),
});
bool isMajor = barLine.BeatIndex % (int)barLine.ControlPoint.TimeSignature == 0;
@@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics;
@@ -18,12 +18,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction>
{
public override bool DisplayJudgement => false;
private readonly DrawableNote head;
private readonly DrawableNote tail;
private readonly GlowPiece glowPiece;
private readonly BodyPiece bodyPiece;
private readonly Container fullHeightContainer;
/// <summary>
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
@@ -35,25 +35,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
private bool hasBroken;
private readonly Container<DrawableHoldNoteTick> tickContainer;
public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, action)
{
Container<DrawableHoldNoteTick> tickContainer;
RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
// The hit object itself cannot be used for various elements because the tail overshoots it
// So a specialized container that is updated to contain the tail height is used
fullHeightContainer = new Container
{
RelativeSizeAxes = Axes.X,
Child = glowPiece = new GlowPiece()
},
bodyPiece = new BodyPiece
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
},
tickContainer = new Container<DrawableHoldNoteTick>
@@ -90,15 +82,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
base.AccentColour = value;
glowPiece.AccentColour = value;
bodyPiece.AccentColour = value;
head.AccentColour = value;
tail.AccentColour = value;
tickContainer.ForEach(t => t.AccentColour = value);
}
}
protected override void UpdateState(ArmedState state)
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (tail.AllJudged)
AddJudgement(new HoldNoteJudgement { Result = HitResult.Perfect });
}
protected override void Update()
@@ -106,12 +100,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
base.Update();
// Make the body piece not lie under the head note
bodyPiece.Y = head.Height;
bodyPiece.Height = DrawHeight - head.Height;
// Make the fullHeightContainer "contain" the height of the tail note, keeping in mind
// that the tail note overshoots the height of this hit object
fullHeightContainer.Height = DrawHeight + tail.Height;
bodyPiece.Y = head.Height / 2;
bodyPiece.Height = DrawHeight - head.Height / 2 + tail.Height / 2;
}
public bool OnPressed(ManiaAction action)
@@ -160,8 +150,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
: base(holdNote.HitObject.Head, action)
{
this.holdNote = holdNote;
GlowPiece.Alpha = 0;
}
public override bool OnPressed(ManiaAction action)
@@ -179,11 +167,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return true;
}
protected override void UpdateState(ArmedState state)
{
// The holdnote keeps scrolling through for now, so having the head disappear looks weird
}
}
/// <summary>
@@ -191,18 +174,26 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
private class DrawableTailNote : DrawableNote
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
: base(holdNote.HitObject.Tail, action)
{
this.holdNote = holdNote;
GlowPiece.Alpha = 0;
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
// Factor in the release lenience
timeOffset /= release_window_lenience;
if (!userTriggered)
{
if (!HitObject.HitWindows.CanBeHit(timeOffset))
@@ -228,11 +219,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
});
}
protected override void UpdateState(ArmedState state)
{
// The holdnote keeps scrolling through, so having the tail disappear looks weird
}
public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
public override bool OnReleased(ManiaAction action)
@@ -8,7 +8,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Scoring;
@@ -87,16 +86,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddJudgement(new HoldNoteTickJudgement { Result = HitResult.Perfect });
}
protected override void UpdateState(ArmedState state)
{
switch (State.Value)
{
case ArmedState.Hit:
AccentColour = Color4.Green;
break;
}
}
protected override void Update()
{
if (AllJudged)
@@ -27,5 +27,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != null)
Action = action.Value;
}
protected override void UpdateState(ArmedState state)
{
switch (state)
{
case ArmedState.Miss:
this.FadeOut(150, Easing.In).Expire();
break;
case ArmedState.Hit:
this.FadeOut(150, Easing.OutQuint).Expire();
break;
}
}
}
}
@@ -1,12 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Extensions.Color4Extensions;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
@@ -16,9 +17,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
{
protected readonly GlowPiece GlowPiece;
private readonly LaneGlowPiece laneGlowPiece;
private readonly NotePiece headPiece;
public DrawableNote(Note hitObject, ManiaAction action)
@@ -27,14 +25,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
CornerRadius = 5;
Masking = true;
InternalChildren = new Drawable[]
{
laneGlowPiece = new LaneGlowPiece
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
GlowPiece = new GlowPiece(),
headPiece = new NotePiece
{
Anchor = Anchor.TopCentre,
@@ -49,9 +44,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
set
{
base.AccentColour = value;
laneGlowPiece.AccentColour = AccentColour;
GlowPiece.AccentColour = AccentColour;
headPiece.AccentColour = AccentColour;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = AccentColour.Lighten(1f).Opacity(0.6f),
Radius = 10,
};
}
}
@@ -71,17 +71,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddJudgement(new ManiaJudgement { Result = result });
}
protected override void UpdateState(ArmedState state)
{
switch (state)
{
case ArmedState.Hit:
case ArmedState.Miss:
this.FadeOut(100).Expire();
break;
}
}
public virtual bool OnPressed(ManiaAction action)
{
if (action != Action)
@@ -123,8 +123,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
if (!IsLoaded)
return;
foreground.Colour = AccentColour.Opacity(0.4f);
background.Colour = AccentColour.Opacity(0.2f);
foreground.Colour = AccentColour.Opacity(0.9f);
background.Colour = AccentColour.Opacity(0.6f);
subtractionCache.Invalidate();
}
@@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
/// </summary>
internal class NotePiece : Container, IHasAccentColour
{
private const float head_height = 10;
public const float NOTE_HEIGHT = 10;
private const float head_colour_height = 6;
private readonly Box colouredBox;
@@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public NotePiece()
{
RelativeSizeAxes = Axes.X;
Height = head_height;
Height = NOTE_HEIGHT;
Children = new[]
{
+1 -20
View File
@@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.Objects
/// <summary>
/// The tail note of the hold.
/// </summary>
public readonly Note Tail = new TailNote();
public readonly Note Tail = new Note();
/// <summary>
/// The time between ticks of this hold.
@@ -94,24 +94,5 @@ namespace osu.Game.Rulesets.Mania.Objects
});
}
}
/// <summary>
/// The tail of the hold note.
/// </summary>
private class TailNote : Note
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// </summary>
private const double release_window_lenience = 1.5;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
HitWindows *= release_window_lenience;
}
}
}
}
@@ -1,8 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.Objects.Types;
using osu.Game.Rulesets.Objects;
@@ -12,12 +10,6 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public virtual int Column { get; set; }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
HitWindows.AllowsPerfect = true;
HitWindows.AllowsOk = true;
}
protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
}
}
@@ -0,0 +1,36 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects
{
public class ManiaHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{
{ HitResult.Perfect, (44.8, 38.8, 27.8) },
{ HitResult.Great, (128, 98, 68 ) },
{ HitResult.Good, (194, 164, 134) },
{ HitResult.Ok, (254, 224, 194) },
{ HitResult.Meh, (302, 272, 242) },
{ HitResult.Miss, (376, 346, 316) },
};
public override void SetDifficulty(double difficulty)
{
AllowsPerfect = true;
AllowsOk = true;
Perfect = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Perfect]);
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
Ok = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Ok]);
Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
}
}
}
+28 -11
View File
@@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Mania.UI
public ManiaAction Action;
private readonly Box background;
private readonly Box backgroundOverlay;
private readonly Container hitTargetBar;
private readonly Container keyIcon;
@@ -42,22 +43,32 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
private const float opacity_released = 0.1f;
private const float opacity_pressed = 0.25f;
public Column()
: base(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Y;
Width = column_width;
Masking = true;
CornerRadius = 5;
InternalChildren = new Drawable[]
{
background = new Box
{
Name = "Background",
RelativeSizeAxes = Axes.Both,
Alpha = opacity_released
Alpha = 0.3f
},
backgroundOverlay = new Box
{
Name = "Background Gradient Overlay",
RelativeSizeAxes = Axes.Both,
Height = 0.5f,
Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft,
Blending = BlendingMode.Additive,
Alpha = 0
},
new Container
{
@@ -182,6 +193,7 @@ namespace osu.Game.Rulesets.Mania.UI
accentColour = value;
background.Colour = accentColour;
backgroundOverlay.Colour = ColourInfo.GradientVertical(accentColour.Opacity(0.6f), accentColour.Opacity(0));
hitTargetBar.EdgeEffect = new EdgeEffectParameters
{
@@ -213,7 +225,7 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
if (!judgement.IsHit)
if (!judgement.IsHit || !judgedObject.DisplayJudgement)
return;
explosionContainer.Add(new HitExplosion(judgedObject));
@@ -223,8 +235,8 @@ namespace osu.Game.Rulesets.Mania.UI
{
if (action == Action)
{
background.FadeTo(opacity_pressed, 50, Easing.OutQuint);
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint);
backgroundOverlay.FadeTo(1, 50, Easing.OutQuint).Then().FadeTo(0.5f, 250, Easing.OutQuint);
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint).Then().ScaleTo(1.3f, 250, Easing.OutQuint);
}
return false;
@@ -234,8 +246,8 @@ namespace osu.Game.Rulesets.Mania.UI
{
if (action == Action)
{
background.FadeTo(opacity_released, 800, Easing.OutQuart);
keyIcon.ScaleTo(1f, 400, Easing.OutQuart);
backgroundOverlay.FadeTo(0, 250, Easing.OutQuint);
keyIcon.ScaleTo(1f, 125, Easing.OutQuint);
}
return false;
@@ -265,8 +277,13 @@ namespace osu.Game.Rulesets.Mania.UI
if (action != Action)
return false;
var hitObject = HitObjects.Objects.LastOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.FirstOrDefault();
hitObject?.PlaySamples();
var nextObject =
HitObjects.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
// fallback to non-alive objects to find next off-screen object
HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
HitObjects.Objects.LastOrDefault();
nextObject?.PlaySamples();
return true;
}
+23 -20
View File
@@ -1,19 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Mania.UI
{
internal class HitExplosion : CompositeDrawable
{
private readonly Box inner;
public override bool RemoveWhenNotAlive => true;
private readonly CircularContainer circle;
public HitExplosion(DrawableHitObject judgedObject)
{
@@ -22,33 +26,32 @@ namespace osu.Game.Rulesets.Mania.UI
Anchor = Anchor.TopCentre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(isTick ? 0.5f : 1);
FillMode = FillMode.Fit;
RelativeSizeAxes = Axes.X;
Y = NotePiece.NOTE_HEIGHT / 2;
Height = NotePiece.NOTE_HEIGHT;
Blending = BlendingMode.Additive;
// scale roughly in-line with visual appearance of notes
Scale = new Vector2(isTick ? 0.4f : 0.8f);
Color4 accent = isTick ? Color4.White : judgedObject.AccentColour;
InternalChild = new CircularContainer
InternalChild = circle = new CircularContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = 1,
BorderColour = accent,
// we want our size to be very small so the glow dominates it.
Size = new Vector2(0.1f),
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Colour = accent,
Radius = 10,
Hollow = true
Colour = Interpolation.ValueAt(0.1f, judgedObject.AccentColour, Color4.White, 0, 1),
Radius = 100,
},
Child = inner = new Box
Child = new Box
{
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Colour = accent,
Alpha = 1,
AlwaysPresent = true,
AlwaysPresent = true
}
};
}
@@ -57,8 +60,8 @@ namespace osu.Game.Rulesets.Mania.UI
{
base.LoadComplete();
this.ScaleTo(2f, 600, Easing.OutQuint).FadeOut(500);
inner.FadeOut(250);
circle.ResizeTo(circle.Size * new Vector2(4, 20), 1000, Easing.OutQuint);
this.FadeIn(16).Then().FadeOut(500, Easing.OutQuint);
Expire(true);
}
@@ -10,12 +10,9 @@ using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays;
@@ -103,7 +100,5 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant);
}
}
+8 -5
View File
@@ -9,7 +9,6 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
@@ -78,6 +77,7 @@ namespace osu.Game.Rulesets.Mania.UI
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Masking = true,
CornerRadius = 5,
Children = new Drawable[]
{
new Box
@@ -171,6 +171,9 @@ namespace osu.Game.Rulesets.Mania.UI
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{
if (!judgedObject.DisplayJudgement)
return;
judgements.Clear();
judgements.Add(new DrawableManiaJudgement(judgement, judgedObject)
{
@@ -180,15 +183,15 @@ namespace osu.Game.Rulesets.Mania.UI
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load()
{
normalColumnColours = new List<Color4>
{
colours.RedDark,
colours.GreenDark
new Color4(94, 0, 57, 255),
new Color4(6, 84, 0, 255)
};
specialColumnColour = colours.BlueDark;
specialColumnColour = new Color4(0, 48, 63, 255);
// Set the special column + colour + key
foreach (var column in Columns)
+5 -5
View File
@@ -22,7 +22,7 @@
},
"type": "mono",
"request": "launch",
"program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Osu.Tests.exe",
"program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Osu.Tests.exe",
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, msbuild)",
"runtimeExecutable": null,
@@ -30,12 +30,12 @@
"console": "internalConsole"
},
{
"name": "VisualTests (Debug, netcoreapp2.0)",
"name": "VisualTests (Debug, netcoreapp2.1)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Osu.Tests.dll"
"${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Debug, dotnet)",
@@ -43,12 +43,12 @@
"console": "internalConsole"
},
{
"name": "VisualTests (Release, netcoreapp2.0)",
"name": "VisualTests (Release, netcoreapp2.1)",
"type": "coreclr",
"request": "launch",
"program": "dotnet",
"args": [
"${workspaceRoot}/bin/Debug/netcoreapp2.0/osu.Game.Rulesets.Osu.Tests.dll"
"${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Osu.Tests.dll"
],
"cwd": "${workspaceRoot}",
"preLaunchTask": "Build (Release, dotnet)",
+3 -3
View File
@@ -40,7 +40,7 @@
"build",
"--no-restore",
"osu.Game.Rulesets.Osu.Tests.csproj",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:GenerateFullPaths=true",
"/m",
"/verbosity:m"
@@ -56,7 +56,7 @@
"build",
"--no-restore",
"osu.Game.Rulesets.Osu.Tests.csproj",
"/p:TargetFramework=netcoreapp2.0",
"/p:TargetFramework=netcoreapp2.1",
"/p:Configuration=Release",
"/p:GenerateFullPaths=true",
"/m",
@@ -75,7 +75,7 @@
"problemMatcher": []
},
{
"label": "Restore (netcoreapp2.0)",
"label": "Restore (netcoreapp2.1)",
"type": "shell",
"command": "dotnet",
"args": [
@@ -5,17 +5,15 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Tests.Beatmaps;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class OsuBeatmapConversionTest : BeatmapConversionTest<TestOsuRuleset, ConvertValue>
internal class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
@@ -42,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Tests
};
}
protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
protected override Ruleset CreateRuleset() => new OsuRuleset();
}
public struct ConvertValue : IEquatable<ConvertValue>
internal struct ConvertValue : IEquatable<ConvertValue>
{
/// <summary>
/// A sane value to account for osu!stable using ints everwhere.
@@ -67,8 +65,4 @@ namespace osu.Game.Rulesets.Osu.Tests
&& Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
&& Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
}
public class TestOsuRuleset : OsuRuleset
{
}
}
@@ -1,16 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{
public TestCasePerformancePoints()
: base(new OsuRuleset())
{
}
}
}
@@ -93,12 +93,36 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset());
AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1));
AddStep("Distance Overflow", () => testDistanceOverflow());
AddStep("Distance Overflow 1 Repeat", () => testDistanceOverflow(1));
}
private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
private void testDistanceOverflow(int repeats = 0)
{
var slider = new Slider
{
StartTime = Time.Current + 1000,
Position = new Vector2(239, 176),
ControlPoints = new List<Vector2>
{
Vector2.Zero,
new Vector2(154, 28),
new Vector2(52, -34)
},
Distance = 700,
RepeatCount = repeats,
RepeatSamples = createEmptySamples(repeats),
StackHeight = 10
};
addSlider(slider, 2, 2);
}
private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats);
private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats);
@@ -2,7 +2,7 @@
<Import Project="..\osu.TestProject.props" />
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
<TargetFrameworks>netcoreapp2.0;net471</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1;net471</TargetFrameworks>
</PropertyGroup>
<ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
@@ -50,8 +50,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
StartTime = original.StartTime,
Samples = original.Samples,
EndTime = endTimeData.EndTime,
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2,
Position = positionData?.Position ?? OsuPlayfield.BASE_SIZE / 2
};
}
else
@@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyAttributes : DifficultyAttributes
{
public double AimStrain;
public double SpeedStrain;
public OsuDifficultyAttributes(Mod[] mods, double starRating)
: base(mods, starRating)
{
}
}
}
@@ -0,0 +1,76 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Skills;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuDifficultyCalculator : DifficultyCalculator
{
private const int section_length = 400;
private const double difficulty_multiplier = 0.0675;
public OsuDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
}
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
{
OsuDifficultyBeatmap difficultyBeatmap = new OsuDifficultyBeatmap(beatmap.HitObjects.Cast<OsuHitObject>().ToList(), timeRate);
Skill[] skills =
{
new Aim(),
new Speed()
};
double sectionLength = section_length * timeRate;
// The first object doesn't generate a strain, so we begin with an incremented section end
double currentSectionEnd = 2 * sectionLength;
foreach (OsuDifficultyHitObject h in difficultyBeatmap)
{
while (h.BaseObject.StartTime > currentSectionEnd)
{
foreach (Skill s in skills)
{
s.SaveCurrentPeak();
s.StartNewSectionFrom(currentSectionEnd);
}
currentSectionEnd += sectionLength;
}
foreach (Skill s in skills)
s.Process(h);
}
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
return new OsuDifficultyAttributes(mods, starRating)
{
AimStrain = aimRating,
SpeedStrain = speedRating
};
}
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
{
new OsuModDoubleTime(),
new OsuModHalfTime(),
new OsuModEasy(),
new OsuModHardRock(),
};
}
}
@@ -5,34 +5,48 @@ using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring
namespace osu.Game.Rulesets.Osu.Difficulty
{
public class OsuPerformanceCalculator : PerformanceCalculator
{
public new OsuDifficultyAttributes Attributes => (OsuDifficultyAttributes)base.Attributes;
private readonly int countHitCircles;
private readonly int beatmapMaxCombo;
private Mod[] mods;
/// <summary>
/// Approach rate adjusted by mods.
/// </summary>
private double realApproachRate;
/// <summary>
/// Overall difficulty adjusted by mods.
/// </summary>
private double realOverallDifficulty;
private double accuracy;
private int scoreMaxCombo;
private int count300;
private int count100;
private int count50;
private int countGreat;
private int countGood;
private int countMeh;
private int countMiss;
public OsuPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
public OsuPerformanceCalculator(Ruleset ruleset, WorkingBeatmap beatmap, Score score)
: base(ruleset, beatmap, score)
{
countHitCircles = Beatmap.HitObjects.Count(h => h is HitCircle);
beatmapMaxCombo = Beatmap.HitObjects.Count();
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count) + 1;
// Add the ticks + tail of the slider. 1 is subtracted because the "headcircle" would be counted twice (once for the slider itself in the line above)
beatmapMaxCombo += Beatmap.HitObjects.OfType<Slider>().Sum(s => s.NestedHitObjects.Count - 1);
}
public override double Calculate(Dictionary<string, double> categoryRatings = null)
@@ -40,25 +54,21 @@ namespace osu.Game.Rulesets.Osu.Scoring
mods = Score.Mods;
accuracy = Score.Accuracy;
scoreMaxCombo = Score.MaxCombo;
count300 = Convert.ToInt32(Score.Statistics[HitResult.Great]);
count100 = Convert.ToInt32(Score.Statistics[HitResult.Good]);
count50 = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countGreat = Convert.ToInt32(Score.Statistics[HitResult.Great]);
countGood = Convert.ToInt32(Score.Statistics[HitResult.Good]);
countMeh = Convert.ToInt32(Score.Statistics[HitResult.Meh]);
countMiss = Convert.ToInt32(Score.Statistics[HitResult.Miss]);
// Don't count scores made with supposedly unranked mods
if (mods.Any(m => !m.Ranked))
return 0;
// Todo: In the future we should apply changes to PreEmpt/AR at an OsuHitObject/BaseDifficulty level, but this is done
// locally for now as doing so would modify animations and other things unexpectedly
// DO NOT MODIFY THIS
double ar = Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate;
if (mods.Any(m => m is OsuModHardRock))
ar = Math.Min(10, ar * 1.4);
if (mods.Any(m => m is OsuModEasy))
ar = Math.Max(0, ar / 2);
double preEmpt = BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450);
// Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be remoevd in the future
double hitWindowGreat = (int)(Beatmap.HitObjects.First().HitWindows.Great / 2) / TimeRate;
double preEmpt = (int)BeatmapDifficulty.DifficultyRange(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / TimeRate;
realApproachRate = preEmpt > 1200 ? (1800 - preEmpt) / 120 : (1200 - preEmpt) / 150 + 5;
realOverallDifficulty = (80 - hitWindowGreat) / 6;
// Custom multipliers for NoFail and SpunOut.
double multiplier = 1.12f; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
@@ -84,6 +94,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
categoryRatings.Add("Aim", aimValue);
categoryRatings.Add("Speed", speedValue);
categoryRatings.Add("Accuracy", accuracyValue);
categoryRatings.Add("OD", realOverallDifficulty);
categoryRatings.Add("AR", realApproachRate);
categoryRatings.Add("Max Combo", beatmapMaxCombo);
}
return totalValue;
@@ -91,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
private double computeAimValue()
{
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Aim"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
double aimValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.AimStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more
double lengthBonus = 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
@@ -120,8 +133,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
aimValue *= approachRateFactor;
// We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR.
if (mods.Any(h => h is OsuModHidden))
aimValue *= 1.18f;
aimValue *= 1.02 + (11.0f - realApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11.
if (mods.Any(h => h is OsuModFlashlight))
{
@@ -132,14 +146,14 @@ namespace osu.Game.Rulesets.Osu.Scoring
// Scale the aim value with accuracy _slightly_
aimValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
aimValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
aimValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
return aimValue;
}
private double computeSpeedValue()
{
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes["Speed"] / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
double speedValue = Math.Pow(5.0f * Math.Max(1.0f, Attributes.SpeedStrain / 0.0675f) - 4.0f, 3.0f) / 100000.0f;
// Longer maps are worth more
speedValue *= 0.95f + 0.4f * Math.Min(1.0f, totalHits / 2000.0f) +
@@ -152,10 +166,13 @@ namespace osu.Game.Rulesets.Osu.Scoring
if (beatmapMaxCombo > 0)
speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f);
if (mods.Any(m => m is OsuModHidden))
speedValue *= 1.18f;
// Scale the speed value with accuracy _slightly_
speedValue *= 0.5f + accuracy / 2.0f;
// It is important to also consider accuracy difficulty when doing that
speedValue *= 0.98f + Math.Pow(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 2) / 2500;
speedValue *= 0.98f + Math.Pow(realOverallDifficulty, 2) / 2500;
return speedValue;
}
@@ -167,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
int amountHitObjectsWithAccuracy = countHitCircles;
if (amountHitObjectsWithAccuracy > 0)
betterAccuracyPercentage = ((count300 - (totalHits - amountHitObjectsWithAccuracy)) * 6 + count100 * 2 + count50) / (amountHitObjectsWithAccuracy * 6);
betterAccuracyPercentage = ((countGreat - (totalHits - amountHitObjectsWithAccuracy)) * 6 + countGood * 2 + countMeh) / (amountHitObjectsWithAccuracy * 6);
else
betterAccuracyPercentage = 0;
@@ -177,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
// Lots of arbitrary values from testing.
// Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution
double accuracyValue = Math.Pow(1.52163f, Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
double accuracyValue = Math.Pow(1.52163f, realOverallDifficulty) * Math.Pow(betterAccuracyPercentage, 24) * 2.83f;
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f));
@@ -190,7 +207,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
return accuracyValue;
}
private double totalHits => count300 + count100 + count50 + countMiss;
private double totalSuccessfulHits => count300 + count100 + count50;
private double totalHits => countGreat + countGood + countMeh + countMiss;
private double totalSuccessfulHits => countGreat + countGood + countMeh;
}
}
@@ -0,0 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
/// <summary>
/// An enumerable container wrapping <see cref="OsuHitObject"/> input as <see cref="OsuDifficultyHitObject"/>
/// which contains extra data required for difficulty calculation.
/// </summary>
public class OsuDifficultyBeatmap : IEnumerable<OsuDifficultyHitObject>
{
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
/// <summary>
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
/// </summary>
public OsuDifficultyBeatmap(List<OsuHitObject> objects, double timeRate)
{
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
// This should probably happen before the objects reach the difficulty calculator.
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
}
/// <summary>
/// Returns an enumerator that enumerates all <see cref="OsuDifficultyHitObject"/>s in the <see cref="OsuDifficultyBeatmap"/>.
/// </summary>
public IEnumerator<OsuDifficultyHitObject> GetEnumerator() => difficultyObjects;
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
{
// The first jump is formed by the first two hitobjects of the map.
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
for (int i = 1; i < objects.Count; i++)
yield return new OsuDifficultyHitObject(objects[i], objects[i - 1], timeRate);
}
}
}
@@ -3,16 +3,18 @@
using System;
using System.Linq;
using OpenTK;
using osu.Game.Rulesets.Osu.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
{
/// <summary>
/// A wrapper around <see cref="OsuHitObject"/> extending it with additional data required for difficulty calculation.
/// </summary>
public class OsuDifficultyHitObject
{
private const int normalized_radius = 52;
/// <summary>
/// The <see cref="OsuHitObject"/> this <see cref="OsuDifficultyHitObject"/> refers to.
/// </summary>
@@ -28,26 +30,19 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
/// </summary>
public double DeltaTime { get; private set; }
/// <summary>
/// Number of milliseconds until the <see cref="OsuDifficultyHitObject"/> has to be hit.
/// </summary>
public double TimeUntilHit { get; set; }
private const int normalized_radius = 52;
private readonly OsuHitObject lastObject;
private readonly double timeRate;
private readonly OsuHitObject[] t;
/// <summary>
/// Initializes the object calculating extra data required for difficulty calculation.
/// </summary>
public OsuDifficultyHitObject(OsuHitObject[] triangle, double timeRate)
public OsuDifficultyHitObject(OsuHitObject currentObject, OsuHitObject lastObject, double timeRate)
{
this.lastObject = lastObject;
this.timeRate = timeRate;
t = triangle;
BaseObject = t[0];
BaseObject = currentObject;
setDistances();
setTimingValues();
// Calculate angle here
@@ -63,10 +58,10 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
scalingFactor *= 1 + smallCircleBonus;
}
Vector2 lastCursorPosition = t[1].StackedPosition;
Vector2 lastCursorPosition = lastObject.StackedPosition;
float lastTravelDistance = 0;
var lastSlider = t[1] as Slider;
var lastSlider = lastObject as Slider;
if (lastSlider != null)
{
computeSliderCursorPosition(lastSlider);
@@ -80,8 +75,7 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
private void setTimingValues()
{
// Every timing inverval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure.
DeltaTime = Math.Max(40, (t[0].StartTime - t[1].StartTime) / timeRate);
TimeUntilHit = 450; // BaseObject.PreEmpt;
DeltaTime = Math.Max(50, (BaseObject.StartTime - lastObject.StartTime) / timeRate);
}
private void computeSliderCursorPosition(Slider slider)
@@ -107,7 +101,8 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
}
});
var scoringTimes = slider.NestedHitObjects.Select(t => t.StartTime);
// Skip the head circle
var scoringTimes = slider.NestedHitObjects.Skip(1).Select(t => t.StartTime);
foreach (var time in scoringTimes)
computeVertex(time);
computeVertex(slider.EndTime);
@@ -2,9 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
/// <summary>
/// Represents the skill required to correctly aim at every object in the map with a uniform CircleSize and normalized distances.
@@ -3,11 +3,11 @@
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Utils;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
using osu.Game.Rulesets.Osu.OsuDifficulty.Utils;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
/// <summary>
/// Used to processes strain values of <see cref="OsuDifficultyHitObject"/>s, keep track of strain levels caused by the processed objects
@@ -1,9 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
using osu.Game.Rulesets.Osu.Difficulty.Preprocessing;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Skills
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
{
/// <summary>
/// Represents the skill required to press keys with regards to keeping up with the speed at which objects need to be hit.
@@ -5,7 +5,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Utils
namespace osu.Game.Rulesets.Osu.Difficulty.Utils
{
/// <summary>
/// An indexed stack with Push() only, which disposes items at the bottom after the capacity is full.
@@ -13,8 +13,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string ShortenedName => "AP";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_autopilot;
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 0;
public override bool Ranked => false;
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
}
}
+10 -9
View File
@@ -7,33 +7,34 @@ using System.Linq;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModHidden : ModHidden, IApplicableToDrawableHitObjects
public class OsuModHidden : ModHidden
{
public override string Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => 1.06;
private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
public override void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)
{
void adjustFadeIn(OsuHitObject h) => h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier;
foreach (var d in drawables.OfType<DrawableOsuHitObject>())
{
d.ApplyCustomUpdateState += ApplyHiddenState;
d.HitObject.TimeFadein = d.HitObject.TimePreempt * fade_in_duration_multiplier;
adjustFadeIn(d.HitObject);
foreach (var h in d.HitObject.NestedHitObjects.OfType<OsuHitObject>())
h.TimeFadein = h.TimePreempt * fade_in_duration_multiplier;
adjustFadeIn(h);
}
base.ApplyToDrawableHitObjects(drawables);
}
protected void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
protected override void ApplyHiddenState(DrawableHitObject drawable, ArmedState state)
{
if (!(drawable is DrawableOsuHitObject d))
return;
@@ -71,5 +71,7 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public virtual void OffsetPosition(Vector2 offset) => Position += offset;
protected override HitWindows CreateHitWindows() => new OsuHitWindows();
}
}
@@ -0,0 +1,29 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Osu.Objects
{
public class OsuHitWindows : HitWindows
{
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
{
{ HitResult.Great, (160, 100, 40) },
{ HitResult.Good, (280, 200, 120) },
{ HitResult.Meh, (400, 300, 200) },
{ HitResult.Miss, (400, 400, 400) },
};
public override void SetDifficulty(double difficulty)
{
Great = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Great]);
Good = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Good]);
Meh = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Meh]);
Miss = BeatmapDifficulty.DifficultyRange(difficulty, base_ranges[HitResult.Miss]);
}
}
}
@@ -1,70 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
using osu.Game.Rulesets.Osu.OsuDifficulty.Skills;
namespace osu.Game.Rulesets.Osu.OsuDifficulty
{
public class OsuDifficultyCalculator : DifficultyCalculator
{
private const int section_length = 400;
private const double difficulty_multiplier = 0.0675;
public OsuDifficultyCalculator(IBeatmap beatmap)
: base(beatmap)
{
}
public OsuDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
: base(beatmap, mods)
{
}
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{
OsuDifficultyBeatmap beatmap = new OsuDifficultyBeatmap((List<OsuHitObject>)Beatmap.HitObjects, TimeRate);
Skill[] skills =
{
new Aim(),
new Speed()
};
double sectionEnd = section_length / TimeRate;
foreach (OsuDifficultyHitObject h in beatmap)
{
while (h.BaseObject.StartTime > sectionEnd)
{
foreach (Skill s in skills)
{
s.SaveCurrentPeak();
s.StartNewSectionFrom(sectionEnd);
}
sectionEnd += section_length;
}
foreach (Skill s in skills)
s.Process(h);
}
double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier;
double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier;
double starRating = aimRating + speedRating + Math.Abs(aimRating - speedRating) / 2;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("Aim", aimRating);
categoryDifficulty.Add("Speed", speedRating);
}
return starRating;
}
}
}
@@ -1,94 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections;
using System.Collections.Generic;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing
{
/// <summary>
/// An enumerable container wrapping <see cref="OsuHitObject"/> input as <see cref="OsuDifficultyHitObject"/>
/// which contains extra data required for difficulty calculation.
/// </summary>
public class OsuDifficultyBeatmap : IEnumerable<OsuDifficultyHitObject>
{
private readonly IEnumerator<OsuDifficultyHitObject> difficultyObjects;
private readonly Queue<OsuDifficultyHitObject> onScreen = new Queue<OsuDifficultyHitObject>();
/// <summary>
/// Creates an enumerator, which preprocesses a list of <see cref="OsuHitObject"/>s recieved as input, wrapping them as
/// <see cref="OsuDifficultyHitObject"/> which contains extra data required for difficulty calculation.
/// </summary>
public OsuDifficultyBeatmap(List<OsuHitObject> objects, double timeRate)
{
// Sort OsuHitObjects by StartTime - they are not correctly ordered in some cases.
// This should probably happen before the objects reach the difficulty calculator.
objects.Sort((a, b) => a.StartTime.CompareTo(b.StartTime));
difficultyObjects = createDifficultyObjectEnumerator(objects, timeRate);
}
/// <summary>
/// Returns an enumerator that enumerates all <see cref="OsuDifficultyHitObject"/>s in the <see cref="OsuDifficultyBeatmap"/>.
/// The inner loop adds objects that appear on screen into a queue until we need to hit the next object.
/// The outer loop returns objects from this queue one at a time, only after they had to be hit, and should no longer be on screen.
/// This means that we can loop through every object that is on screen at the time when a new one appears,
/// allowing us to determine a reading strain for the object that just appeared.
/// </summary>
public IEnumerator<OsuDifficultyHitObject> GetEnumerator()
{
while (true)
{
// Add upcoming objects to the queue until we have at least one object that had been hit and can be dequeued.
// This means there is always at least one object in the queue unless we reached the end of the map.
do
{
if (!difficultyObjects.MoveNext())
break; // New objects can't be added anymore, but we still need to dequeue and return the ones already on screen.
OsuDifficultyHitObject latest = difficultyObjects.Current;
// Calculate flow values here
foreach (OsuDifficultyHitObject h in onScreen)
{
// ReSharper disable once PossibleNullReferenceException (resharper not smart enough to understand IEnumerator.MoveNext())
h.TimeUntilHit -= latest.DeltaTime;
// Calculate reading strain here
}
onScreen.Enqueue(latest);
}
while (onScreen.Peek().TimeUntilHit > 0); // Keep adding new objects on screen while there is still time before we have to hit the next one.
if (onScreen.Count == 0) break; // We have reached the end of the map and enumerated all the objects.
yield return onScreen.Dequeue(); // Remove and return objects one by one that had to be hit before the latest one appeared.
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private IEnumerator<OsuDifficultyHitObject> createDifficultyObjectEnumerator(List<OsuHitObject> objects, double timeRate)
{
// We will process OsuHitObjects in groups of three to form a triangle, so we can calculate an angle for each object.
OsuHitObject[] triangle = new OsuHitObject[3];
// OsuDifficultyHitObject construction requires three components, an extra copy of the first OsuHitObject is used at the beginning.
if (objects.Count > 1)
{
triangle[1] = objects[0]; // This copy will get shifted to the last spot in the triangle.
triangle[0] = objects[0]; // This component corresponds to the real first OsuHitOject.
}
// The final component of the first triangle will be the second OsuHitOject of the map, which forms the first jump.
// If the map has less than two OsuHitObjects, the enumerator will not return anything.
for (int i = 1; i < objects.Count; ++i)
{
triangle[2] = triangle[1];
triangle[1] = triangle[0];
triangle[0] = objects[i];
yield return new OsuDifficultyHitObject(triangle, timeRate);
}
}
}
}
+9 -40
View File
@@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.OsuDifficulty;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using System.Collections.Generic;
@@ -13,13 +12,14 @@ using osu.Framework.Graphics;
using osu.Game.Overlays.Settings;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Difficulty;
namespace osu.Game.Rulesets.Osu
{
@@ -93,57 +93,26 @@ namespace osu.Game.Rulesets.Osu
{
new OsuModEasy(),
new OsuModNoFail(),
new MultiMod
{
Mods = new Mod[]
{
new OsuModHalfTime(),
new OsuModDaycore(),
},
},
new MultiMod(new OsuModHalfTime(), new OsuModDaycore()),
};
case ModType.DifficultyIncrease:
return new Mod[]
{
new OsuModHardRock(),
new MultiMod
{
Mods = new Mod[]
{
new OsuModSuddenDeath(),
new OsuModPerfect(),
},
},
new MultiMod
{
Mods = new Mod[]
{
new OsuModDoubleTime(),
new OsuModNightcore(),
},
},
new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
new OsuModHidden(),
new OsuModFlashlight(),
};
case ModType.Special:
return new Mod[]
{
new OsuModRelax(),
new OsuModAutopilot(),
new OsuModSpunOut(),
new MultiMod
{
Mods = new Mod[]
{
new OsuModAutoplay(),
new ModCinema(),
},
},
new MultiMod(new OsuModAutoplay(), new ModCinema()),
new OsuModTarget(),
};
default:
return new Mod[] { };
}
@@ -151,9 +120,9 @@ namespace osu.Game.Rulesets.Osu
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_osu_o };
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new OsuDifficultyCalculator(beatmap, mods);
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new OsuDifficultyCalculator(this, beatmap);
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, Score score) => new OsuPerformanceCalculator(this, beatmap, score);
public override HitObjectComposer CreateHitObjectComposer() => new OsuHitObjectComposer(this);
@@ -161,7 +130,7 @@ namespace osu.Game.Rulesets.Osu
public override string ShortName => "osu";
public override SettingsSubsection CreateSettings() => new OsuSettings();
public override RulesetSettingsSubsection CreateSettings() => new OsuSettings(this);
public override int? LegacyID => 0;
@@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private Bindable<double> cursorScale;
private Bindable<bool> autoCursorScale;
private Bindable<WorkingBeatmap> beatmap;
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
public OsuCursor()
{
@@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, OsuGameBase game)
private void load(OsuConfigManager config, IBindableBeatmap beatmap)
{
Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer
{
@@ -160,8 +160,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
RelativeSizeAxes = Axes.Both,
};
beatmap = game.Beatmap.GetBoundCopy();
beatmap.ValueChanged += v => calculateScale();
this.beatmap.BindTo(beatmap);
this.beatmap.ValueChanged += v => calculateScale();
cursorScale = config.GetBindable<double>(OsuSetting.GameplayCursorSize);
cursorScale.ValueChanged += v => calculateScale();
+1 -3
View File
@@ -64,9 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI
public override void PostProcess()
{
connectionLayer.HitObjects = HitObjects.Objects
.Select(d => d.HitObject)
.OrderBy(h => h.StartTime).OfType<OsuHitObject>();
connectionLayer.HitObjects = HitObjects.Objects.Select(d => d.HitObject).OfType<OsuHitObject>();
}
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement)
+6 -1
View File
@@ -8,10 +8,15 @@ using osu.Game.Overlays.Settings;
namespace osu.Game.Rulesets.Osu.UI
{
public class OsuSettings : SettingsSubsection
public class OsuSettings : RulesetSettingsSubsection
{
protected override string Header => "osu!";
public OsuSettings(Ruleset ruleset)
: base(ruleset)
{
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{

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