mirror of
https://github.com/ppy/osu.git
synced 2026-05-14 15:43:18 +08:00
Compare commits
429 Commits
v2018.514.0
...
9
+3
-3
@@ -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>
|
||||
+3
-3
@@ -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>
|
||||
Vendored
+13
-13
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"type": "mono",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/osu.Game.Tests/bin/Debug/net471/osu.Game.Tests.exe",
|
||||
"program": "${workspaceRoot}/osu.Game.Tests/bin/Release/net471/osu.Game.Tests.exe",
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release, msbuild)",
|
||||
"runtimeExecutable": null,
|
||||
@@ -58,54 +58,54 @@
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "VisualTests (Debug, netcoreapp2.0)",
|
||||
"name": "VisualTests (Debug, netcoreapp2.1)",
|
||||
"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, dotnet)",
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "VisualTests (Release, netcoreapp2.0)",
|
||||
"name": "VisualTests (Release, netcoreapp2.1)",
|
||||
"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, dotnet)",
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "osu! (Debug, netcoreapp2.0)",
|
||||
"name": "osu! (Debug, netcoreapp2.1)",
|
||||
"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, dotnet)",
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
},
|
||||
{
|
||||
"name": "osu! (Release, netcoreapp2.0)",
|
||||
"name": "osu! (Release, netcoreapp2.1)",
|
||||
"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, dotnet)",
|
||||
"env": {},
|
||||
"console": "internalConsole"
|
||||
}
|
||||
|
||||
Vendored
+38
-5
@@ -31,14 +31,14 @@
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build (Debug, dotnet)",
|
||||
"label": "Build osu! (Debug, dotnet)",
|
||||
"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 +47,47 @@
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build (Release, dotnet)",
|
||||
"label": "Build osu! (Release, dotnet)",
|
||||
"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",
|
||||
"/verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build tests (Debug, dotnet)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Tests",
|
||||
"/p:TargetFramework=netcoreapp2.1",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
"/verbosity:m"
|
||||
],
|
||||
"group": "build",
|
||||
"problemMatcher": "$msCompile"
|
||||
},
|
||||
{
|
||||
"label": "Build tests (Release, dotnet)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Tests",
|
||||
"/p:TargetFramework=netcoreapp2.1",
|
||||
"/p:Configuration=Release",
|
||||
"/p:GenerateFullPaths=true",
|
||||
"/m",
|
||||
@@ -73,7 +106,7 @@
|
||||
"problemMatcher": []
|
||||
},
|
||||
{
|
||||
"label": "Restore (netcoreapp2.0)",
|
||||
"label": "Restore (netcoreapp2.1)",
|
||||
"type": "shell",
|
||||
"command": "dotnet",
|
||||
"args": [
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
clone_depth: 1
|
||||
version: '{branch}-{build}'
|
||||
image: Visual Studio 2017
|
||||
image: Visual Studio 2017 preview
|
||||
configuration: Debug
|
||||
cache:
|
||||
- C:\ProgramData\chocolatey\bin -> appveyor.yml
|
||||
|
||||
+17
-17
@@ -1,29 +1,26 @@
|
||||
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
|
||||
- '%USERPROFILE%\.nuget\packages -> **\*.csproj'
|
||||
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-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 +28,7 @@ environment:
|
||||
code_signing_password:
|
||||
secure: 34tLNqvjmmZEi97MLKfrnQ==
|
||||
artifacts:
|
||||
- path: 'Releases\*'
|
||||
- path: 'Releases\*'
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: github
|
||||
+1
-1
Submodule osu-framework updated: fac688633b...b963ce8250
@@ -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>
|
||||
@@ -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 Newtonsoft.Json;
|
||||
|
||||
namespace osu.Desktop.Deploy
|
||||
{
|
||||
public class GitHubObject
|
||||
{
|
||||
[JsonProperty(@"id")]
|
||||
public int Id;
|
||||
|
||||
[JsonProperty(@"name")]
|
||||
public string Name;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
@@ -30,10 +30,10 @@
|
||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.4.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.0.3" />
|
||||
<PackageReference Include="squirrel.windows" Version="1.8.0" Condition="'$(TargetFramework)' == 'net471'" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
+5
-5
@@ -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
@@ -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
|
||||
{
|
||||
internal class CatchBeatmapConversionTest : BeatmapConversionTest<TestCatchRuleset, ConvertValue>
|
||||
internal class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||
|
||||
@@ -47,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
}
|
||||
}
|
||||
|
||||
protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new CatchBeatmapConverter(beatmap);
|
||||
protected override Ruleset CreateRuleset() => new CatchRuleset();
|
||||
}
|
||||
|
||||
internal struct ConvertValue : IEquatable<ConvertValue>
|
||||
@@ -64,8 +62,4 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
||||
&& Precision.AlmostEquals(Position, other.Position, conversion_lenience);
|
||||
}
|
||||
|
||||
internal class TestCatchRuleset : CatchRuleset
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
+3
-2
@@ -1,10 +1,11 @@
|
||||
// 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;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch
|
||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||
{
|
||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -99,8 +99,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 +119,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 +192,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.
|
||||
@@ -411,6 +401,23 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
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
@@ -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
@@ -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
|
||||
{
|
||||
internal class ManiaBeatmapConversionTest : BeatmapConversionTest<TestManiaRuleset, ConvertValue>
|
||||
internal class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||
|
||||
@@ -35,7 +33,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
};
|
||||
}
|
||||
|
||||
protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap);
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
}
|
||||
|
||||
internal struct ConvertValue : IEquatable<ConvertValue>
|
||||
@@ -54,8 +52,4 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
|
||||
&& Column == other.Column;
|
||||
}
|
||||
|
||||
internal 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,13 +85,16 @@ 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 });
|
||||
});
|
||||
}
|
||||
|
||||
@@ -162,32 +167,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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
+9
-8
@@ -1,14 +1,16 @@
|
||||
// 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.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
{
|
||||
internal class ManiaDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
@@ -48,18 +50,17 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
|
||||
|
||||
foreach (var hitObject in Beatmap.HitObjects)
|
||||
difficultyHitObjects.Add(new ManiaHitObjectDifficulty((ManiaHitObject)hitObject, columnCount));
|
||||
|
||||
// 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;
|
||||
|
||||
double starRating = calculateDifficulty() * star_scaling_factor;
|
||||
|
||||
categoryDifficulty?.Add("Strain", starRating);
|
||||
if (categoryDifficulty != null)
|
||||
categoryDifficulty["Strain"] = starRating;
|
||||
|
||||
return starRating;
|
||||
}
|
||||
@@ -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, IBeatmap 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["Strain"] / 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;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,10 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Difficulty;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
@@ -23,6 +26,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(IBeatmap beatmap, Score score) => new ManiaPerformanceCalculator(this, beatmap, score);
|
||||
|
||||
public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -18,6 +18,8 @@ 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;
|
||||
|
||||
@@ -99,6 +101,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
protected override void UpdateState(ArmedState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Hit:
|
||||
// Good enough for now, we just want them to have a lifetime end
|
||||
this.Delay(2000).Expire();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||
{
|
||||
if (tail.AllJudged)
|
||||
AddJudgement(new HoldNoteJudgement { Result = HitResult.Perfect });
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@@ -191,6 +206,13 @@ 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)
|
||||
@@ -203,6 +225,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
|
||||
{
|
||||
// Factor in the release lenience
|
||||
timeOffset /= release_window_lenience;
|
||||
|
||||
if (!userTriggered)
|
||||
{
|
||||
if (!HitObject.HitWindows.CanBeHit(timeOffset))
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,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));
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
+5
-5
@@ -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
@@ -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
|
||||
{
|
||||
internal class OsuBeatmapConversionTest : BeatmapConversionTest<TestOsuRuleset, ConvertValue>
|
||||
internal class OsuBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Osu";
|
||||
|
||||
@@ -42,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
};
|
||||
}
|
||||
|
||||
protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap);
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
}
|
||||
|
||||
internal struct ConvertValue : IEquatable<ConvertValue>
|
||||
@@ -67,8 +65,4 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
&& Precision.AlmostEquals(EndX, other.EndX, conversion_lenience)
|
||||
&& Precision.AlmostEquals(EndY, other.EndY, conversion_lenience);
|
||||
}
|
||||
|
||||
internal class TestOsuRuleset : 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
|
||||
|
||||
+12
-7
@@ -4,12 +4,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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.Objects;
|
||||
using osu.Game.Rulesets.Osu.OsuDifficulty.Preprocessing;
|
||||
using osu.Game.Rulesets.Osu.OsuDifficulty.Skills;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.OsuDifficulty
|
||||
namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
{
|
||||
public class OsuDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
@@ -35,18 +36,22 @@ namespace osu.Game.Rulesets.Osu.OsuDifficulty
|
||||
new Speed()
|
||||
};
|
||||
|
||||
double sectionEnd = section_length / TimeRate;
|
||||
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 beatmap)
|
||||
{
|
||||
while (h.BaseObject.StartTime > sectionEnd)
|
||||
while (h.BaseObject.StartTime > currentSectionEnd)
|
||||
{
|
||||
foreach (Skill s in skills)
|
||||
{
|
||||
s.SaveCurrentPeak();
|
||||
s.StartNewSectionFrom(sectionEnd);
|
||||
s.StartNewSectionFrom(currentSectionEnd);
|
||||
}
|
||||
|
||||
sectionEnd += section_length;
|
||||
currentSectionEnd += sectionLength;
|
||||
}
|
||||
|
||||
foreach (Skill s in skills)
|
||||
+17
-23
@@ -5,12 +5,13 @@ 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
|
||||
{
|
||||
@@ -31,9 +32,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
|
||||
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)
|
||||
@@ -51,29 +52,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) / TimeRate;
|
||||
double hitWindow300 = (Beatmap.HitObjects.First().HitWindows.Great / 2 - 0.5) / TimeRate;
|
||||
// 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 - 0.5 - hitWindow300) / 6;
|
||||
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
|
||||
@@ -138,8 +131,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.03f;
|
||||
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))
|
||||
{
|
||||
@@ -188,7 +182,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;
|
||||
|
||||
@@ -211,7 +205,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-19
@@ -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
-2
@@ -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
-3
@@ -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
|
||||
+2
-2
@@ -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.
|
||||
+1
-1
@@ -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) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
|
||||
+5
-5
@@ -22,7 +22,7 @@
|
||||
},
|
||||
"type": "mono",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/bin/Debug/net471/osu.Game.Rulesets.Taiko.Tests.exe",
|
||||
"program": "${workspaceRoot}/bin/Release/net471/osu.Game.Rulesets.Taiko.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.Taiko.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/netcoreapp2.1/osu.Game.Rulesets.Taiko.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.Taiko.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/netcoreapp2.1/osu.Game.Rulesets.Taiko.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release, dotnet)",
|
||||
|
||||
+3
-3
@@ -40,7 +40,7 @@
|
||||
"build",
|
||||
"--no-restore",
|
||||
"osu.Game.Rulesets.Taiko.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.Taiko.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,22 +5,20 @@ 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.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
internal class TaikoBeatmapConversionTest : BeatmapConversionTest<TestTaikoRuleset, ConvertValue>
|
||||
internal class TaikoBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
|
||||
{
|
||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Taiko";
|
||||
|
||||
[NonParallelizable]
|
||||
[TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2152")]
|
||||
[TestCase("slider-generating-drumroll", false)]
|
||||
[TestCase("basic")]
|
||||
[TestCase("slider-generating-drumroll")]
|
||||
public new void Test(string name)
|
||||
{
|
||||
base.Test(name);
|
||||
@@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
};
|
||||
}
|
||||
|
||||
protected override IBeatmapConverter CreateConverter(IBeatmap beatmap) => new TaikoBeatmapConverter(beatmap);
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
}
|
||||
|
||||
internal struct ConvertValue : IEquatable<ConvertValue>
|
||||
@@ -67,8 +65,4 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
&& IsSwell == other.IsSwell
|
||||
&& IsStrong == other.IsStrong;
|
||||
}
|
||||
|
||||
internal class TestTaikoRuleset : TaikoRuleset
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
|
||||
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Taiko.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
|
||||
@@ -51,8 +50,9 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original)
|
||||
{
|
||||
// Rewrite the beatmap info to add the slider velocity multiplier
|
||||
BeatmapInfo info = original.BeatmapInfo.DeepClone();
|
||||
info.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
|
||||
original.BeatmapInfo = original.BeatmapInfo.Clone();
|
||||
original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
|
||||
original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier;
|
||||
|
||||
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
|
||||
|
||||
@@ -98,12 +98,12 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
double distance = distanceData.Distance * spans * legacy_velocity_multiplier;
|
||||
|
||||
// The velocity of the taiko hit object - calculated as the velocity of a drum roll
|
||||
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
|
||||
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
|
||||
// The duration of the taiko hit object
|
||||
double taikoDuration = distance / taikoVelocity;
|
||||
|
||||
// The velocity of the osu! hit object - calculated as the velocity of a slider
|
||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
|
||||
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;
|
||||
// The duration of the osu! hit object
|
||||
double osuDuration = distance / osuVelocity;
|
||||
|
||||
@@ -141,7 +141,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
StartTime = j,
|
||||
Samples = currentSamples,
|
||||
IsStrong = strong,
|
||||
IsStrong = strong
|
||||
};
|
||||
}
|
||||
|
||||
@@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
Duration = taikoDuration,
|
||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4,
|
||||
TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -170,7 +170,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
Duration = endTimeData.Duration,
|
||||
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier),
|
||||
RequiredHits = (int)Math.Max(1, endTimeData.Duration / 1000 * hitMultiplier)
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -183,7 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
IsStrong = strong
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
StartTime = obj.StartTime,
|
||||
Samples = obj.Samples,
|
||||
IsStrong = strong,
|
||||
IsStrong = strong
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+12
-8
@@ -1,12 +1,14 @@
|
||||
// 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.Taiko.Objects;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
internal class TaikoDifficultyCalculator : DifficultyCalculator
|
||||
{
|
||||
@@ -34,6 +36,11 @@ namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
}
|
||||
|
||||
public TaikoDifficultyCalculator(IBeatmap beatmap, Mod[] mods)
|
||||
: base(beatmap, mods)
|
||||
{
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
{
|
||||
// Fill our custom DifficultyHitObject class, that carries additional information
|
||||
@@ -50,10 +57,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
double starRating = calculateDifficulty() * star_scaling_factor;
|
||||
|
||||
if (categoryDifficulty != null)
|
||||
{
|
||||
categoryDifficulty.Add("Strain", starRating);
|
||||
categoryDifficulty.Add("Hit window 300", 35 /*HitObjectManager.HitWindow300*/ / TimeRate);
|
||||
}
|
||||
categoryDifficulty["Strain"] = starRating;
|
||||
|
||||
return starRating;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// 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;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Difficulty
|
||||
{
|
||||
public class TaikoPerformanceCalculator : PerformanceCalculator
|
||||
{
|
||||
private readonly int beatmapMaxCombo;
|
||||
|
||||
private Mod[] mods;
|
||||
private int countGreat;
|
||||
private int countGood;
|
||||
private int countMeh;
|
||||
private int countMiss;
|
||||
|
||||
public TaikoPerformanceCalculator(Ruleset ruleset, IBeatmap beatmap, Score score)
|
||||
: base(ruleset, beatmap, score)
|
||||
{
|
||||
beatmapMaxCombo = beatmap.HitObjects.Count(h => h is Hit);
|
||||
}
|
||||
|
||||
public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
|
||||
{
|
||||
mods = Score.Mods;
|
||||
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;
|
||||
|
||||
// Custom multipliers for NoFail and SpunOut.
|
||||
double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things
|
||||
|
||||
if (mods.Any(m => m is ModNoFail))
|
||||
multiplier *= 0.90;
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
multiplier *= 1.10;
|
||||
|
||||
double strainValue = computeStrainValue();
|
||||
double accuracyValue = computeAccuracyValue();
|
||||
double totalValue =
|
||||
Math.Pow(
|
||||
Math.Pow(strainValue, 1.1) +
|
||||
Math.Pow(accuracyValue, 1.1), 1.0 / 1.1
|
||||
) * multiplier;
|
||||
|
||||
if (categoryDifficulty != null)
|
||||
{
|
||||
categoryDifficulty["Strain"] = strainValue;
|
||||
categoryDifficulty["Accuracy"] = accuracyValue;
|
||||
}
|
||||
|
||||
return totalValue;
|
||||
}
|
||||
|
||||
private double computeStrainValue()
|
||||
{
|
||||
double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes["Strain"] / 0.0075) - 4.0, 2.0) / 100000.0;
|
||||
|
||||
// Longer maps are worth more
|
||||
double lengthBonus = 1 + 0.1f * Math.Min(1.0, totalHits / 1500.0);
|
||||
strainValue *= lengthBonus;
|
||||
|
||||
// Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available
|
||||
strainValue *= Math.Pow(0.985, countMiss);
|
||||
|
||||
// Combo scaling
|
||||
if (beatmapMaxCombo > 0)
|
||||
strainValue *= Math.Min(Math.Pow(Score.MaxCombo, 0.5) / Math.Pow(beatmapMaxCombo, 0.5), 1.0);
|
||||
|
||||
if (mods.Any(m => m is ModHidden))
|
||||
strainValue *= 1.025;
|
||||
|
||||
if (mods.Any(m => m is ModFlashlight))
|
||||
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
||||
strainValue *= 1.05 * lengthBonus;
|
||||
|
||||
// Scale the speed value with accuracy _slightly_
|
||||
return strainValue * Score.Accuracy;
|
||||
}
|
||||
|
||||
private double computeAccuracyValue()
|
||||
{
|
||||
// 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 accValue = Math.Pow(150.0 / hitWindowGreat, 1.1) * Math.Pow(Score.Accuracy, 15) * 22.0;
|
||||
|
||||
// Bonus for many hitcircles - it's harder to keep good accuracy up for longer
|
||||
return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3));
|
||||
}
|
||||
|
||||
private int totalHits => countGreat + countGood + countMeh + countMiss;
|
||||
}
|
||||
}
|
||||
@@ -27,5 +27,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
/// Strong hit objects give more points for hitting the hit object with both keys.
|
||||
/// </summary>
|
||||
public bool IsStrong;
|
||||
|
||||
protected override HitWindows CreateHitWindows() => new TaikoHitWindows();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.Taiko.Objects
|
||||
{
|
||||
public class TaikoHitWindows : HitWindows
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<HitResult, (double od0, double od5, double od10)> base_ranges = new Dictionary<HitResult, (double, double, double)>
|
||||
{
|
||||
{ HitResult.Great, (100, 70, 40) },
|
||||
{ HitResult.Good, (240, 160, 100) },
|
||||
{ HitResult.Meh, (270, 190, 140) },
|
||||
{ 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,10 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Rulesets.Replays.Types;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Difficulty;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
@@ -142,7 +145,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap);
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IBeatmap beatmap, Mod[] mods = null) => new TaikoDifficultyCalculator(beatmap, mods);
|
||||
|
||||
public override PerformanceCalculator CreatePerformanceCalculator(IBeatmap beatmap, Score score) => new TaikoPerformanceCalculator(this, beatmap, score);
|
||||
|
||||
public override int? LegacyID => 1;
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO.Serialization;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Tests.Resources;
|
||||
using OpenTK;
|
||||
@@ -117,7 +118,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
public void TestParity(string beatmap)
|
||||
{
|
||||
var legacy = decode(beatmap, out Beatmap json);
|
||||
json.ShouldDeepEqual(legacy);
|
||||
json.WithDeepEqual(legacy).IgnoreProperty(r => r.DeclaringType == typeof(HitWindows)).Assert();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -12,6 +12,7 @@ using osu.Framework.Platform;
|
||||
using osu.Game.IPC;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using SharpCompress.Archives.Zip;
|
||||
|
||||
namespace osu.Game.Tests.Beatmaps.IO
|
||||
{
|
||||
@@ -77,8 +78,69 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
|
||||
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRollbackOnFailure()
|
||||
{
|
||||
//unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here.
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost("TestRollbackOnFailure"))
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
int fireCount = 0;
|
||||
|
||||
// ReSharper disable once AccessToModifiedClosure
|
||||
manager.ItemAdded += _ => fireCount++;
|
||||
manager.ItemRemoved += _ => fireCount++;
|
||||
|
||||
var imported = loadOszIntoOsu(osu);
|
||||
|
||||
Assert.AreEqual(0, fireCount -= 1);
|
||||
|
||||
imported.Hash += "-changed";
|
||||
manager.Update(imported);
|
||||
|
||||
Assert.AreEqual(0, fireCount -= 2);
|
||||
|
||||
var breakTemp = createTemporaryBeatmap();
|
||||
|
||||
MemoryStream brokenOsu = new MemoryStream(new byte[] { 1, 3, 3, 7 });
|
||||
MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp));
|
||||
|
||||
File.Delete(breakTemp);
|
||||
|
||||
using (var outStream = File.Open(breakTemp, FileMode.CreateNew))
|
||||
using (var zip = ZipArchive.Open(brokenOsz))
|
||||
{
|
||||
zip.AddEntry("broken.osu", brokenOsu, false);
|
||||
zip.SaveTo(outStream, SharpCompress.Common.CompressionType.Deflate);
|
||||
}
|
||||
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
|
||||
|
||||
// this will trigger purging of the existing beatmap (online set id match) but should rollback due to broken osu.
|
||||
manager.Import(breakTemp);
|
||||
|
||||
// no events should be fired in the case of a rollback.
|
||||
Assert.AreEqual(0, fireCount);
|
||||
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
Assert.AreEqual(12, manager.QueryBeatmaps(_ => true).ToList().Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -100,18 +162,17 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var imported = loadOszIntoOsu(osu);
|
||||
|
||||
//var change = manager.QueryBeatmapSets(_ => true).First();
|
||||
imported.Hash += "-changed";
|
||||
manager.Update(imported);
|
||||
|
||||
var importedSecondTime = loadOszIntoOsu(osu);
|
||||
|
||||
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
|
||||
Assert.IsTrue(imported.ID != importedSecondTime.ID);
|
||||
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
|
||||
|
||||
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 1);
|
||||
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
|
||||
// only one beatmap will exist as the online set ID matched, causing purging of the first import.
|
||||
Assert.AreEqual(1, manager.GetAllUsableBeatmapSets().Count);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -162,8 +223,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
var osu = loadOsu(host);
|
||||
|
||||
var temp = prepareTempCopy(osz_path);
|
||||
Assert.IsTrue(File.Exists(temp));
|
||||
var temp = createTemporaryBeatmap();
|
||||
|
||||
var importer = new ArchiveImportIPCChannel(client);
|
||||
if (!importer.ImportAsync(temp).Wait(10000))
|
||||
@@ -188,8 +248,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
try
|
||||
{
|
||||
var osu = loadOsu(host);
|
||||
var temp = prepareTempCopy(osz_path);
|
||||
Assert.IsTrue(File.Exists(temp), "Temporary file copy never substantiated");
|
||||
var temp = createTemporaryBeatmap();
|
||||
using (File.OpenRead(temp))
|
||||
osu.Dependencies.Get<BeatmapManager>().Import(temp);
|
||||
ensureLoaded(osu);
|
||||
@@ -203,11 +262,16 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu)
|
||||
private string createTemporaryBeatmap()
|
||||
{
|
||||
var temp = prepareTempCopy(osz_path);
|
||||
|
||||
var temp = new FileInfo(osz_path).CopyTo(Path.GetTempFileName(), true).FullName;
|
||||
Assert.IsTrue(File.Exists(temp));
|
||||
return temp;
|
||||
}
|
||||
|
||||
private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu, string path = null)
|
||||
{
|
||||
var temp = path ?? createTemporaryBeatmap();
|
||||
|
||||
var manager = osu.Dependencies.Get<BeatmapManager>();
|
||||
|
||||
@@ -219,7 +283,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
|
||||
waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000);
|
||||
|
||||
return imported.FirstOrDefault();
|
||||
return imported.LastOrDefault();
|
||||
}
|
||||
|
||||
private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu)
|
||||
@@ -228,16 +292,10 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
manager.Delete(imported);
|
||||
|
||||
Assert.IsTrue(manager.GetAllUsableBeatmapSets().Count == 0);
|
||||
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).ToList().Count == 1);
|
||||
Assert.AreEqual(1, manager.QueryBeatmapSets(_ => true).ToList().Count);
|
||||
Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending);
|
||||
}
|
||||
|
||||
private string prepareTempCopy(string path)
|
||||
{
|
||||
var temp = Path.GetTempFileName();
|
||||
return new FileInfo(path).CopyTo(temp, true).FullName;
|
||||
}
|
||||
|
||||
private OsuGameBase loadOsu(GameHost host)
|
||||
{
|
||||
var osu = new OsuGameBase();
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
@@ -15,7 +16,20 @@ namespace osu.Game.Tests.Visual
|
||||
protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset)
|
||||
{
|
||||
beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
|
||||
return base.CreatePlayer(beatmap, ruleset);
|
||||
return new ScoreAccessiblePlayer
|
||||
{
|
||||
InitialBeatmap = beatmap,
|
||||
AllowPause = false,
|
||||
AllowLeadIn = false,
|
||||
AllowResults = false,
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool ContinueCondition(Player player) => base.ContinueCondition(player) && ((ScoreAccessiblePlayer)player).ScoreProcessor.TotalScore > 0;
|
||||
|
||||
private class ScoreAccessiblePlayer : Player
|
||||
{
|
||||
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,11 +65,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
carousel.SelectionChanged = s => currentSelection = s;
|
||||
|
||||
AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; });
|
||||
|
||||
bool changed = false;
|
||||
carousel.BeatmapSetsChanged = () => changed = true;
|
||||
AddUntilStep(() => changed, "Wait for load");
|
||||
loadBeatmaps(beatmapSets);
|
||||
|
||||
testTraversal();
|
||||
testFiltering();
|
||||
@@ -84,6 +80,17 @@ namespace osu.Game.Tests.Visual
|
||||
testCarouselRootIsRandom();
|
||||
}
|
||||
|
||||
private void loadBeatmaps(List<BeatmapSetInfo> beatmapSets)
|
||||
{
|
||||
bool changed = false;
|
||||
AddStep($"Load {beatmapSets.Count} Beatmaps", () =>
|
||||
{
|
||||
carousel.BeatmapSetsChanged = () => changed = true;
|
||||
carousel.BeatmapSets = beatmapSets;
|
||||
});
|
||||
AddUntilStep(() => changed, "Wait for load");
|
||||
}
|
||||
|
||||
private void ensureRandomFetchSuccess() =>
|
||||
AddAssert("ensure prev random fetch worked", () => selectedSets.Peek() == carousel.SelectedBeatmapSet);
|
||||
|
||||
@@ -423,7 +430,7 @@ namespace osu.Game.Tests.Visual
|
||||
for (int i = 1; i <= 50; i++)
|
||||
beatmapSets.Add(createTestBeatmapSet(i));
|
||||
|
||||
AddStep("Load 50 Beatmaps", () => { carousel.BeatmapSets = beatmapSets; });
|
||||
loadBeatmaps(beatmapSets);
|
||||
advanceSelection(direction: 1, diff: false);
|
||||
checkNonmatchingFilter();
|
||||
checkNonmatchingFilter();
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual
|
||||
AddStep("show", () =>
|
||||
{
|
||||
infoWedge.State = Visibility.Visible;
|
||||
infoWedge.UpdateBeatmap(beatmap);
|
||||
infoWedge.Beatmap = beatmap;
|
||||
});
|
||||
|
||||
// select part is redundant, but wait for load isn't
|
||||
@@ -133,7 +133,7 @@ namespace osu.Game.Tests.Visual
|
||||
AddStep($"select {b.Metadata.Title} beatmap", () =>
|
||||
{
|
||||
infoBefore = infoWedge.Info;
|
||||
infoWedge.UpdateBeatmap(beatmap.Value = new TestWorkingBeatmap(b));
|
||||
infoWedge.Beatmap = beatmap.Value = new TestWorkingBeatmap(b);
|
||||
});
|
||||
|
||||
AddUntilStep(() => infoWedge.Info != infoBefore, "wait for async load");
|
||||
@@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual
|
||||
AddStep("select null beatmap", () =>
|
||||
{
|
||||
beatmap.Value = beatmap.Default;
|
||||
infoWedge.UpdateBeatmap(beatmap);
|
||||
infoWedge.Beatmap = beatmap;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -19,12 +19,12 @@ namespace osu.Game.Tests.Visual
|
||||
[TestFixture]
|
||||
public class TestCaseCursors : ManualInputManagerTestCase
|
||||
{
|
||||
private readonly CursorOverrideContainer cursorOverrideContainer;
|
||||
private readonly MenuCursorContainer menuCursorContainer;
|
||||
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
|
||||
|
||||
public TestCaseCursors()
|
||||
{
|
||||
Child = cursorOverrideContainer = new CursorOverrideContainer
|
||||
Child = menuCursorContainer = new MenuCursorContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
@@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual
|
||||
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor));
|
||||
AddStep("Move out", moveOut);
|
||||
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -112,11 +112,11 @@ namespace osu.Game.Tests.Visual
|
||||
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor));
|
||||
AddAssert("Check global cursor at mouse", () => checkAtMouse(cursorOverrideContainer.Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
||||
AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor));
|
||||
AddStep("Move out", moveOut);
|
||||
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor));
|
||||
AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Multi.Components;
|
||||
@@ -111,6 +112,7 @@ namespace osu.Game.Tests.Visual
|
||||
}
|
||||
});
|
||||
|
||||
AddStep(@"select", () => first.State = SelectionState.Selected);
|
||||
AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name");
|
||||
AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
|
||||
AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying());
|
||||
@@ -121,6 +123,7 @@ namespace osu.Game.Tests.Visual
|
||||
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } },
|
||||
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } },
|
||||
});
|
||||
AddStep(@"deselect", () => first.State = SelectionState.NotSelected);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -0,0 +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 System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestCaseExternalLinkButton : OsuTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ExternalLinkButton) };
|
||||
|
||||
public TestCaseExternalLinkButton()
|
||||
{
|
||||
Child = new ExternalLinkButton("https://osu.ppy.sh/home")
|
||||
{
|
||||
Size = new Vector2(50)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseLoaderAnimation : OsuTestCase
|
||||
{
|
||||
private TestLoader loader;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
// required to preload the logo in a headless run (so it doesn't delay the loading itself).
|
||||
Add(new OsuLogo());
|
||||
|
||||
bool logoVisible = false;
|
||||
AddStep("almost instant display", () => Child = loader = new TestLoader(250));
|
||||
AddUntilStep(() =>
|
||||
{
|
||||
logoVisible = loader.Logo?.Alpha > 0;
|
||||
return loader.Logo != null && loader.ScreenLoaded;
|
||||
}, "loaded");
|
||||
AddAssert("logo not visible", () => !logoVisible);
|
||||
|
||||
AddStep("short load", () => Child = loader = new TestLoader(800));
|
||||
AddUntilStep(() =>
|
||||
{
|
||||
logoVisible = loader.Logo?.Alpha > 0;
|
||||
return loader.Logo != null && loader.ScreenLoaded;
|
||||
}, "loaded");
|
||||
AddAssert("logo visible", () => logoVisible);
|
||||
AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone");
|
||||
|
||||
AddStep("longer load", () => Child = loader = new TestLoader(1400));
|
||||
AddUntilStep(() =>
|
||||
{
|
||||
logoVisible = loader.Logo?.Alpha > 0;
|
||||
return loader.Logo != null && loader.ScreenLoaded;
|
||||
}, "loaded");
|
||||
AddAssert("logo visible", () => logoVisible);
|
||||
AddUntilStep(() => loader.Logo?.Alpha == 0, "logo gone");
|
||||
}
|
||||
|
||||
private class TestLoader : Loader
|
||||
{
|
||||
private readonly double delay;
|
||||
|
||||
public OsuLogo Logo;
|
||||
private TestScreen screen;
|
||||
|
||||
public bool ScreenLoaded => screen.IsCurrentScreen;
|
||||
|
||||
public TestLoader(double delay)
|
||||
{
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
protected override void LogoArriving(OsuLogo logo, bool resuming)
|
||||
{
|
||||
Logo = logo;
|
||||
base.LogoArriving(logo, resuming);
|
||||
}
|
||||
|
||||
protected override OsuScreen CreateLoadableScreen() => screen = new TestScreen();
|
||||
protected override ShaderPrecompiler CreateShaderPrecompiler() => new TestShaderPrecompiler(delay);
|
||||
|
||||
private class TestShaderPrecompiler : ShaderPrecompiler
|
||||
{
|
||||
private readonly double delay;
|
||||
private double startTime;
|
||||
|
||||
public TestShaderPrecompiler(double delay)
|
||||
{
|
||||
this.delay = delay;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
startTime = Time.Current;
|
||||
}
|
||||
|
||||
protected override bool AllLoaded => Time.Current > startTime + delay;
|
||||
}
|
||||
|
||||
private class TestScreen : OsuScreen
|
||||
{
|
||||
public TestScreen()
|
||||
{
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.DarkSlateGray,
|
||||
Alpha = 0,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LogoArriving(OsuLogo logo, bool resuming)
|
||||
{
|
||||
base.LogoArriving(logo, resuming);
|
||||
Child.FadeInFromZero(200);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
// 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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Multi.Components;
|
||||
using osu.Game.Screens.Multi.Screens.Lounge;
|
||||
using osu.Game.Users;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseLounge : ManualInputManagerTestCase
|
||||
{
|
||||
private TestLounge lounge;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
{
|
||||
lounge = new TestLounge();
|
||||
|
||||
Room[] rooms =
|
||||
{
|
||||
new Room
|
||||
{
|
||||
Name = { Value = @"Just Another Room" },
|
||||
Host = { Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } } },
|
||||
Status = { Value = new RoomStatusPlaying() },
|
||||
Availability = { Value = RoomAvailability.Public },
|
||||
Type = { Value = new GameTypeTagTeam() },
|
||||
Beatmap =
|
||||
{
|
||||
Value = new BeatmapInfo
|
||||
{
|
||||
StarDifficulty = 5.65,
|
||||
Ruleset = rulesets.GetRuleset(0),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = @"Sidetracked Day (Short Ver.)",
|
||||
Artist = @"VINXIS",
|
||||
AuthorString = @"Hobbes2",
|
||||
},
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
OnlineInfo = new BeatmapSetOnlineInfo
|
||||
{
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Cover = @"https://assets.ppy.sh/beatmaps/767600/covers/cover.jpg?1526243446",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
MaxParticipants = { Value = 10 },
|
||||
Participants =
|
||||
{
|
||||
Value = new[]
|
||||
{
|
||||
new User { Username = @"flyte", Id = 3103765, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 142 } } },
|
||||
new User { Username = @"Cookiezi", Id = 124493, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 546 } } },
|
||||
new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 287 } } },
|
||||
new User { Username = @"Rafis", Id = 2558286, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 468 } } },
|
||||
new User { Username = @"hvick225", Id = 50265, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 325 } } },
|
||||
new User { Username = @"peppy", Id = 2, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 625 } } },
|
||||
}
|
||||
}
|
||||
},
|
||||
new Room
|
||||
{
|
||||
Name = { Value = @"Not Just Any Room" },
|
||||
Host = { Value = new User { Username = @"Monstrata", Id = 2706438, Country = new Country { FlagName = @"CA" } } },
|
||||
Status = { Value = new RoomStatusOpen() },
|
||||
Availability = { Value = RoomAvailability.FriendsOnly },
|
||||
Type = { Value = new GameTypeTeamVersus() },
|
||||
Beatmap =
|
||||
{
|
||||
Value = new BeatmapInfo
|
||||
{
|
||||
StarDifficulty = 2.73,
|
||||
Ruleset = rulesets.GetRuleset(0),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = @"lit(var)",
|
||||
Artist = @"kensuke ushio",
|
||||
AuthorString = @"Monstrata",
|
||||
},
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
OnlineInfo = new BeatmapSetOnlineInfo
|
||||
{
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Cover = @"https://assets.ppy.sh/beatmaps/623972/covers/cover.jpg?1521167183",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
Participants =
|
||||
{
|
||||
Value = new[]
|
||||
{
|
||||
new User { Username = @"Jeby", Id = 3136279, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 3497 } } },
|
||||
new User { Username = @"DualAkira", Id = 5220933, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 643 } } },
|
||||
new User { Username = @"Datenshi Yohane", Id = 7171857, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10555 } } },
|
||||
}
|
||||
}
|
||||
},
|
||||
new Room
|
||||
{
|
||||
Name = { Value = @"room THE FINAL" },
|
||||
Host = { Value = new User { Username = @"Delis", Id = 1603923, Country = new Country { FlagName = @"JP" } } },
|
||||
Status = { Value = new RoomStatusPlaying() },
|
||||
Availability = { Value = RoomAvailability.Public },
|
||||
Type = { Value = new GameTypeTagTeam() },
|
||||
Beatmap =
|
||||
{
|
||||
Value = new BeatmapInfo
|
||||
{
|
||||
StarDifficulty = 4.48,
|
||||
Ruleset = rulesets.GetRuleset(3),
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Title = @"ONIGIRI FREEWAY",
|
||||
Artist = @"OISHII",
|
||||
AuthorString = @"Mentholzzz",
|
||||
},
|
||||
BeatmapSet = new BeatmapSetInfo
|
||||
{
|
||||
OnlineInfo = new BeatmapSetOnlineInfo
|
||||
{
|
||||
Covers = new BeatmapSetOnlineCovers
|
||||
{
|
||||
Cover = @"https://assets.ppy.sh/beatmaps/663098/covers/cover.jpg?1521898837",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
MaxParticipants = { Value = 30 },
|
||||
Participants =
|
||||
{
|
||||
Value = new[]
|
||||
{
|
||||
new User { Username = @"KizuA", Id = 6510442, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 5372 } } },
|
||||
new User { Username = @"Colored", Id = 827563, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 810 } } },
|
||||
new User { Username = @"Beryl", Id = 3817591, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10096 } } },
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
AddStep(@"show", () => Add(lounge));
|
||||
AddStep(@"set rooms", () => lounge.Rooms = rooms);
|
||||
selectAssert(0);
|
||||
AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {});
|
||||
AddAssert(@"no room selected", () => lounge.SelectedRoom == null);
|
||||
AddStep(@"set rooms", () => lounge.Rooms = rooms);
|
||||
selectAssert(1);
|
||||
AddStep(@"open room 1", () => clickRoom(1));
|
||||
AddStep(@"make lounge current", lounge.MakeCurrent);
|
||||
filterAssert(@"THE FINAL", LoungeTab.Public, 1);
|
||||
filterAssert(string.Empty, LoungeTab.Public, 2);
|
||||
filterAssert(string.Empty, LoungeTab.Private, 1);
|
||||
filterAssert(string.Empty, LoungeTab.Public, 2);
|
||||
filterAssert(@"no matches", LoungeTab.Public, 0);
|
||||
AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {});
|
||||
AddStep(@"set rooms", () => lounge.Rooms = rooms);
|
||||
AddAssert(@"no matches after clear", () => !lounge.ChildRooms.Any());
|
||||
filterAssert(string.Empty, LoungeTab.Public, 2);
|
||||
AddStep(@"exit", lounge.Exit);
|
||||
}
|
||||
|
||||
private void clickRoom(int n)
|
||||
{
|
||||
InputManager.MoveMouseTo(lounge.ChildRooms.ElementAt(n));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}
|
||||
|
||||
private void selectAssert(int n)
|
||||
{
|
||||
AddStep($@"select room {n}", () => clickRoom(n));
|
||||
AddAssert($@"room {n} selected", () => lounge.SelectedRoom == lounge.ChildRooms.ElementAt(n).Room);
|
||||
}
|
||||
|
||||
private void filterAssert(string filter, LoungeTab tab, int endCount)
|
||||
{
|
||||
AddStep($@"filter '{filter}', {tab}", () => lounge.SetFilter(filter, tab));
|
||||
AddAssert(@"filtered correctly", () => lounge.ChildRooms.Count() == endCount);
|
||||
}
|
||||
|
||||
private class TestLounge : Lounge
|
||||
{
|
||||
public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
|
||||
public Room SelectedRoom => Inspector.Room;
|
||||
|
||||
public void SetFilter(string filter, LoungeTab tab)
|
||||
{
|
||||
Filter.Search.Current.Value = filter;
|
||||
Filter.Tabs.Current.Value = tab;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,6 @@ namespace osu.Game.Tests.Visual
|
||||
typeof(SpecialSection),
|
||||
};
|
||||
|
||||
private const string unranked_suffix = " (Unranked)";
|
||||
|
||||
private RulesetStore rulesets;
|
||||
private ModDisplay modDisplay;
|
||||
private TestModSelectOverlay modSelect;
|
||||
@@ -121,7 +119,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
private void testManiaMods(ManiaRuleset ruleset)
|
||||
{
|
||||
testMultiplierTextUnranked(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom));
|
||||
testRankedText(ruleset.GetModsFor(ModType.Special).First(m => m is ManiaModRandom));
|
||||
}
|
||||
|
||||
private void testSingleMod(Mod mod)
|
||||
@@ -198,13 +196,16 @@ namespace osu.Game.Tests.Visual
|
||||
checkLabelColor(Color4.White);
|
||||
}
|
||||
|
||||
private void testMultiplierTextUnranked(Mod mod)
|
||||
private void testRankedText(Mod mod)
|
||||
{
|
||||
AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
|
||||
AddWaitStep(1, "wait for fade");
|
||||
AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
|
||||
selectNext(mod);
|
||||
AddAssert("check for unranked", () => modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
|
||||
AddWaitStep(1, "wait for fade");
|
||||
AddAssert("check for unranked", () => modSelect.UnrankedLabel.Alpha != 0);
|
||||
selectPrevious(mod);
|
||||
AddAssert("check for ranked", () => !modSelect.MultiplierLabel.Text.EndsWith(unranked_suffix));
|
||||
AddWaitStep(1, "wait for fade");
|
||||
AddAssert("check for ranked", () => modSelect.UnrankedLabel.Alpha == 0);
|
||||
}
|
||||
|
||||
private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1));
|
||||
@@ -240,6 +241,7 @@ namespace osu.Game.Tests.Visual
|
||||
}
|
||||
|
||||
public new OsuSpriteText MultiplierLabel => base.MultiplierLabel;
|
||||
public new OsuSpriteText UnrankedLabel => base.UnrankedLabel;
|
||||
public new TriangleButton DeselectAllButton => base.DeselectAllButton;
|
||||
|
||||
public new Color4 LowMultiplierColour => base.LowMultiplierColour;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Multi;
|
||||
using osu.Game.Screens.Multi.Screens.Lounge;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseMultiHeader : OsuTestCase
|
||||
{
|
||||
public TestCaseMultiHeader()
|
||||
{
|
||||
Lounge lounge;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
lounge = new Lounge
|
||||
{
|
||||
Padding = new MarginPadding { Top = Header.HEIGHT },
|
||||
},
|
||||
new Header(lounge),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// 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;
|
||||
using osu.Game.Screens.Multi;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseMultiScreen : OsuTestCase
|
||||
{
|
||||
public TestCaseMultiScreen()
|
||||
{
|
||||
Multiplayer multi = new Multiplayer();
|
||||
|
||||
AddStep(@"show", () => Add(multi));
|
||||
AddWaitStep(5);
|
||||
AddStep(@"exit", multi.Exit);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
@@ -11,36 +13,82 @@ namespace osu.Game.Tests.Visual
|
||||
[TestFixture]
|
||||
public class TestCaseOnScreenDisplay : OsuTestCase
|
||||
{
|
||||
private FrameworkConfigManager config;
|
||||
private Bindable<FrameSync> frameSyncMode;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Add(new OnScreenDisplay());
|
||||
|
||||
frameSyncMode = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync);
|
||||
|
||||
FrameSync initial = frameSyncMode.Value;
|
||||
|
||||
AddRepeatStep(@"Change frame limiter", setNextMode, 3);
|
||||
|
||||
AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial);
|
||||
}
|
||||
|
||||
private void setNextMode()
|
||||
{
|
||||
var nextMode = frameSyncMode.Value + 1;
|
||||
if (nextMode > FrameSync.Unlimited)
|
||||
nextMode = FrameSync.VSync;
|
||||
frameSyncMode.Value = nextMode;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(FrameworkConfigManager config)
|
||||
private void load()
|
||||
{
|
||||
this.config = config;
|
||||
var config = new TestConfigManager();
|
||||
|
||||
var osd = new TestOnScreenDisplay();
|
||||
osd.BeginTracking(this, config);
|
||||
Add(osd);
|
||||
|
||||
AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2);
|
||||
AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2);
|
||||
AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3);
|
||||
AddRepeatStep("Change enum (with bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingWithKeybind), 3);
|
||||
}
|
||||
|
||||
private class TestConfigManager : ConfigManager<TestConfigSetting>
|
||||
{
|
||||
public TestConfigManager()
|
||||
{
|
||||
InitialiseDefaults();
|
||||
}
|
||||
|
||||
protected override void InitialiseDefaults()
|
||||
{
|
||||
Set(TestConfigSetting.ToggleSettingNoKeybind, false);
|
||||
Set(TestConfigSetting.EnumSettingNoKeybind, EnumSetting.Setting1);
|
||||
Set(TestConfigSetting.ToggleSettingWithKeybind, false);
|
||||
Set(TestConfigSetting.EnumSettingWithKeybind, EnumSetting.Setting1);
|
||||
|
||||
base.InitialiseDefaults();
|
||||
}
|
||||
|
||||
public void ToggleSetting(TestConfigSetting setting) => Set(setting, !Get<bool>(setting));
|
||||
|
||||
public void IncrementEnumSetting(TestConfigSetting setting)
|
||||
{
|
||||
var nextValue = Get<EnumSetting>(setting) + 1;
|
||||
if (nextValue > EnumSetting.Setting4)
|
||||
nextValue = EnumSetting.Setting1;
|
||||
Set(setting, nextValue);
|
||||
}
|
||||
|
||||
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
|
||||
{
|
||||
new TrackedSetting<bool>(TestConfigSetting.ToggleSettingNoKeybind, b => new SettingDescription(b, "toggle setting with no keybind", b ? "enabled" : "disabled")),
|
||||
new TrackedSetting<EnumSetting>(TestConfigSetting.EnumSettingNoKeybind, v => new SettingDescription(v, "enum setting with no keybind", v.ToString())),
|
||||
new TrackedSetting<bool>(TestConfigSetting.ToggleSettingWithKeybind, b => new SettingDescription(b, "toggle setting with keybind", b ? "enabled" : "disabled", "fake keybind")),
|
||||
new TrackedSetting<EnumSetting>(TestConfigSetting.EnumSettingWithKeybind, v => new SettingDescription(v, "enum setting with keybind", v.ToString(), "fake keybind")),
|
||||
};
|
||||
|
||||
protected override void PerformLoad()
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool PerformSave() => false;
|
||||
}
|
||||
|
||||
private enum TestConfigSetting
|
||||
{
|
||||
ToggleSettingNoKeybind,
|
||||
EnumSettingNoKeybind,
|
||||
ToggleSettingWithKeybind,
|
||||
EnumSettingWithKeybind
|
||||
}
|
||||
|
||||
private enum EnumSetting
|
||||
{
|
||||
Setting1,
|
||||
Setting2,
|
||||
Setting3,
|
||||
Setting4
|
||||
}
|
||||
|
||||
private class TestOnScreenDisplay : OnScreenDisplay
|
||||
{
|
||||
protected override void Display(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using OpenTK;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[Description("'Hold to Quit' UI element")]
|
||||
public class TestCaseQuitButton : ManualInputManagerTestCase
|
||||
{
|
||||
private bool exitAction;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
QuitButton quitButton;
|
||||
|
||||
Add(quitButton = new QuitButton
|
||||
{
|
||||
Origin = Anchor.BottomRight,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Action = () => exitAction = true
|
||||
});
|
||||
|
||||
var text = quitButton.Children.OfType<SpriteText>().First();
|
||||
|
||||
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(quitButton));
|
||||
AddUntilStep(() => text.IsPresent && !exitAction, "Text visible");
|
||||
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible");
|
||||
|
||||
AddStep("Trigger exit action", () =>
|
||||
{
|
||||
exitAction = false;
|
||||
InputManager.MoveMouseTo(quitButton);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("Early release", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("action not triggered", () => !exitAction);
|
||||
|
||||
AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddUntilStep(() => exitAction, $"{nameof(quitButton.Action)} was triggered");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var room = new Room
|
||||
Room room = new Room
|
||||
{
|
||||
Name = { Value = @"My Awesome Room" },
|
||||
Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } },
|
||||
@@ -71,9 +71,13 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Room = room,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.5f,
|
||||
});
|
||||
|
||||
AddStep(@"set room", () => inspector.Room = room);
|
||||
AddStep(@"null room", () => inspector.Room = null);
|
||||
AddStep(@"set room", () => inspector.Room = room);
|
||||
AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above");
|
||||
AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
|
||||
AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
|
||||
@@ -88,7 +92,7 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
AddStep(@"change room", () =>
|
||||
{
|
||||
var newRoom = new Room
|
||||
Room newRoom = new Room
|
||||
{
|
||||
Name = { Value = @"My New, Better Than Ever Room" },
|
||||
Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } },
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Screens;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseScreenBreadcrumbControl : OsuTestCase
|
||||
{
|
||||
private readonly ScreenBreadcrumbControl breadcrumbs;
|
||||
private Screen currentScreen, changedScreen;
|
||||
|
||||
public TestCaseScreenBreadcrumbControl()
|
||||
{
|
||||
TestScreen startScreen;
|
||||
OsuSpriteText titleText;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
currentScreen = startScreen = new TestScreenOne(),
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
breadcrumbs = new ScreenBreadcrumbControl(startScreen)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
titleText = new OsuSpriteText(),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
breadcrumbs.Current.ValueChanged += s =>
|
||||
{
|
||||
titleText.Text = $"Changed to {s.ToString()}";
|
||||
changedScreen = s;
|
||||
};
|
||||
|
||||
breadcrumbs.Current.TriggerChange();
|
||||
|
||||
assertCurrent();
|
||||
pushNext();
|
||||
assertCurrent();
|
||||
pushNext();
|
||||
assertCurrent();
|
||||
|
||||
AddStep(@"make start current", () =>
|
||||
{
|
||||
startScreen.MakeCurrent();
|
||||
currentScreen = startScreen;
|
||||
});
|
||||
|
||||
assertCurrent();
|
||||
pushNext();
|
||||
AddAssert(@"only 2 items", () => breadcrumbs.Items.Count() == 2);
|
||||
AddStep(@"exit current", () => changedScreen.Exit());
|
||||
AddAssert(@"current screen is first", () => startScreen == changedScreen);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
breadcrumbs.StripColour = colours.Blue;
|
||||
}
|
||||
|
||||
private void pushNext() => AddStep(@"push next screen", () => currentScreen = ((TestScreen)currentScreen).PushNext());
|
||||
private void assertCurrent() => AddAssert(@"changedScreen correct", () => currentScreen == changedScreen);
|
||||
|
||||
private abstract class TestScreen : OsuScreen
|
||||
{
|
||||
protected abstract string NextTitle { get; }
|
||||
protected abstract TestScreen CreateNextScreen();
|
||||
|
||||
public TestScreen PushNext()
|
||||
{
|
||||
TestScreen screen = CreateNextScreen();
|
||||
Push(screen);
|
||||
|
||||
return screen;
|
||||
}
|
||||
|
||||
protected TestScreen()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = Title,
|
||||
},
|
||||
new TriangleButton
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 100,
|
||||
Text = $"Push {NextTitle}",
|
||||
Action = () => PushNext(),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private class TestScreenOne : TestScreen
|
||||
{
|
||||
public override string Title => @"Screen One";
|
||||
protected override string NextTitle => @"Two";
|
||||
protected override TestScreen CreateNextScreen() => new TestScreenTwo();
|
||||
}
|
||||
|
||||
private class TestScreenTwo : TestScreen
|
||||
{
|
||||
public override string Title => @"Screen Two";
|
||||
protected override string NextTitle => @"One";
|
||||
protected override TestScreen CreateNextScreen() => new TestScreenOne();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -6,7 +6,6 @@ using osu.Game.Rulesets.Objects;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.IO.Serialization;
|
||||
using Newtonsoft.Json;
|
||||
using osu.Game.IO.Serialization.Converters;
|
||||
|
||||
@@ -55,17 +54,11 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
IBeatmap IBeatmap.Clone() => Clone();
|
||||
|
||||
public Beatmap<T> Clone()
|
||||
{
|
||||
var newInstance = (Beatmap<T>)MemberwiseClone();
|
||||
newInstance.BeatmapInfo = BeatmapInfo.DeepClone();
|
||||
|
||||
return newInstance;
|
||||
}
|
||||
public Beatmap<T> Clone() => (Beatmap<T>)MemberwiseClone();
|
||||
}
|
||||
|
||||
public class Beatmap : Beatmap<HitObject>
|
||||
{
|
||||
public Beatmap Clone() => (Beatmap)base.Clone();
|
||||
public new Beatmap Clone() => (Beatmap)base.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,6 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
var beatmap = CreateBeatmap();
|
||||
|
||||
// todo: this *must* share logic (or directly use) Beatmap<T>'s constructor.
|
||||
// right now this isn't easily possible due to generic entanglement.
|
||||
beatmap.BeatmapInfo = original.BeatmapInfo;
|
||||
beatmap.ControlPointInfo = original.ControlPointInfo;
|
||||
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
|
||||
|
||||
@@ -32,6 +32,11 @@ namespace osu.Game.Beatmaps
|
||||
public double SliderMultiplier { get; set; } = 1;
|
||||
public double SliderTickRate { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a shallow-clone of this <see cref="BeatmapDifficulty"/>.
|
||||
/// </summary>
|
||||
public BeatmapDifficulty Clone() => (BeatmapDifficulty)MemberwiseClone();
|
||||
|
||||
/// <summary>
|
||||
/// Maps a difficulty value [0, 10] to a two-piece linear range of values.
|
||||
/// </summary>
|
||||
|
||||
@@ -143,5 +143,10 @@ namespace osu.Game.Beatmaps
|
||||
public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null &&
|
||||
BeatmapSet.Hash == other.BeatmapSet.Hash &&
|
||||
(Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a shallow-clone of this <see cref="BeatmapInfo"/>.
|
||||
/// </summary>
|
||||
public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
|
||||
{
|
||||
model.Beatmaps = createBeatmapDifficulties(archive);
|
||||
model.Beatmaps = createBeatmapDifficulties(model, archive);
|
||||
|
||||
// remove metadata from difficulties where it matches the set
|
||||
foreach (BeatmapInfo b in model.Beatmaps)
|
||||
@@ -107,6 +107,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
Delete(existingOnlineId);
|
||||
beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID);
|
||||
Logger.Log($"Found existing beatmap set with same OnlineBeatmapSetID ({model.OnlineBeatmapSetID}). It has been purged.", LoggingTarget.Database);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +170,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
if (error is OperationCanceledException) return;
|
||||
|
||||
downloadNotification.State = ProgressNotificationState.Completed;
|
||||
downloadNotification.State = ProgressNotificationState.Cancelled;
|
||||
Logger.Error(error, "Beatmap download failed!");
|
||||
currentDownloads.Remove(request);
|
||||
};
|
||||
@@ -241,7 +242,13 @@ namespace osu.Game.Beatmaps
|
||||
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
|
||||
/// </summary>
|
||||
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
||||
public List<BeatmapSetInfo> GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList();
|
||||
public List<BeatmapSetInfo> GetAllUsableBeatmapSets() => GetAllUsableBeatmapSetsEnumerable().ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
|
||||
/// </summary>
|
||||
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
||||
public IQueryable<BeatmapSetInfo> GetAllUsableBeatmapSetsEnumerable() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected);
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
|
||||
@@ -303,7 +310,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
// let's make sure there are actually .osu files to import.
|
||||
string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu"));
|
||||
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in the map folder.");
|
||||
if (string.IsNullOrEmpty(mapName)) throw new InvalidOperationException("No beatmap files found in this beatmap archive.");
|
||||
|
||||
BeatmapMetadata metadata;
|
||||
using (var stream = new StreamReader(reader.GetStream(mapName)))
|
||||
@@ -321,7 +328,7 @@ namespace osu.Game.Beatmaps
|
||||
/// <summary>
|
||||
/// Create all required <see cref="BeatmapInfo"/>s for the provided archive.
|
||||
/// </summary>
|
||||
private List<BeatmapInfo> createBeatmapDifficulties(ArchiveReader reader)
|
||||
private List<BeatmapInfo> createBeatmapDifficulties(BeatmapSetInfo model, ArchiveReader reader)
|
||||
{
|
||||
var beatmapInfos = new List<BeatmapInfo>();
|
||||
|
||||
@@ -341,6 +348,14 @@ namespace osu.Game.Beatmaps
|
||||
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
|
||||
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
|
||||
|
||||
// ensure we have the same online set ID as the set itself.
|
||||
beatmap.BeatmapInfo.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
|
||||
beatmap.BeatmapInfo.Metadata.OnlineBeatmapSetID = model.OnlineBeatmapSetID;
|
||||
|
||||
// check that no existing beatmap exists that is imported with the same online beatmap ID. if so, give it precedence.
|
||||
if (beatmap.BeatmapInfo.OnlineBeatmapID.HasValue && QueryBeatmap(b => b.OnlineBeatmapID.Value == beatmap.BeatmapInfo.OnlineBeatmapID.Value) != null)
|
||||
beatmap.BeatmapInfo.OnlineBeatmapID = null;
|
||||
|
||||
RulesetInfo ruleset = rulesets.GetRuleset(beatmap.BeatmapInfo.RulesetID);
|
||||
|
||||
beatmap.BeatmapInfo.Ruleset = ruleset;
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public bool HasVideo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not this beatmap set has a storyboard.
|
||||
/// </summary>
|
||||
public bool HasStoryboard { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The different sizes of cover art for this beatmap set.
|
||||
/// </summary>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user