1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-06 09:07:25 +08:00

Merge remote-tracking branch 'upstream/master' into tgi74-ctb_hr_mangling

This commit is contained in:
Dean Herbert 2018-04-18 16:46:02 +09:00
commit d307b8dc2e
1083 changed files with 96135 additions and 95337 deletions

42
.gitattributes vendored
View File

@ -1,19 +1,23 @@
# This won't normalise line endings, but it will ensure that merge drivers use CRLF # Autodetect text files and ensure that we normalise their
* -text eol=crlf # line endings to lf internally. When checked out they may
# use different line endings.
# Currently in-use binary file extensions * text=auto
*.blend binary
*.bmp binary # Check out with crlf (Windows) line endings
*.dll binary *.sln text eol=crlf
*.exe binary *.csproj text eol=crlf
*.icns binary *.cs text diff=csharp eol=crlf
*.ico binary *.resx text eol=crlf
*.jpg binary *.vsixmanifest text eol=crlf
*.osz2 binary packages.config text eol=crlf
*.pdn binary App.config text eol=crlf
*.psd binary *.bat text eol=crlf
*.PSD binary *.cmd text eol=crlf
*.tga binary *.snippet text eol=crlf
*.ttf binary
*.wav binary # Check out with lf (UNIX) line endings
*.xnb binary *.sh text eol=lf
.gitignore text eol=lf
.gitattributes text eol=lf
*.md text eol=lf
.travis.yml text eol=lf

518
.gitignore vendored
View File

@ -1,259 +1,259 @@
## Ignore Visual Studio temporary files, build results, and ## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons. ## files generated by popular Visual Studio add-ons.
# User-specific files # User-specific files
*.suo *.suo
*.user *.user
*.userosscache *.userosscache
*.sln.docstates *.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio) # User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs *.userprefs
# Build results # Build results
bin/[Dd]ebug/ bin/[Dd]ebug/
[Dd]ebugPublic/ [Dd]ebugPublic/
[Rr]elease/ [Rr]elease/
[Rr]eleases/ [Rr]eleases/
bld/ bld/
[Bb]in/ [Bb]in/
[Oo]bj/ [Oo]bj/
[Ll]og/ [Ll]og/
# Visual Studio 2015 cache/options directory # Visual Studio 2015 cache/options directory
.vs/ .vs/
# Uncomment if you have tasks that create the project's static files in wwwroot # Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/ #wwwroot/
# MSTest test Results # MSTest test Results
[Tt]est[Rr]esult*/ [Tt]est[Rr]esult*/
[Bb]uild[Ll]og.* [Bb]uild[Ll]og.*
# NUNIT # NUNIT
*.VisualState.xml *.VisualState.xml
TestResult.xml TestResult.xml
# Build Results of an ATL Project # Build Results of an ATL Project
[Dd]ebugPS/ [Dd]ebugPS/
[Rr]eleasePS/ [Rr]eleasePS/
dlldata.c dlldata.c
# DNX # DNX
project.lock.json project.lock.json
project.fragment.lock.json project.fragment.lock.json
artifacts/ artifacts/
*_i.c *_i.c
*_p.c *_p.c
*_i.h *_i.h
*.ilk *.ilk
*.meta *.meta
*.obj *.obj
*.pch *.pch
*.pdb *.pdb
*.pgc *.pgc
*.pgd *.pgd
*.rsp *.rsp
*.sbr *.sbr
*.tlb *.tlb
*.tli *.tli
*.tlh *.tlh
*.tmp *.tmp
*.tmp_proj *.tmp_proj
*.log *.log
*.vspscc *.vspscc
*.vssscc *.vssscc
.builds .builds
*.pidb *.pidb
*.svclog *.svclog
*.scc *.scc
# Chutzpah Test files # Chutzpah Test files
_Chutzpah* _Chutzpah*
# Visual C++ cache files # Visual C++ cache files
ipch/ ipch/
*.aps *.aps
*.ncb *.ncb
*.opendb *.opendb
*.opensdf *.opensdf
*.sdf *.sdf
*.cachefile *.cachefile
*.VC.db *.VC.db
*.VC.VC.opendb *.VC.VC.opendb
# Visual Studio profiler # Visual Studio profiler
*.psess *.psess
*.vsp *.vsp
*.vspx *.vspx
*.sap *.sap
# TFS 2012 Local Workspace # TFS 2012 Local Workspace
$tf/ $tf/
# Guidance Automation Toolkit # Guidance Automation Toolkit
*.gpState *.gpState
# ReSharper is a .NET coding add-in # ReSharper is a .NET coding add-in
_ReSharper*/ _ReSharper*/
*.[Rr]e[Ss]harper *.[Rr]e[Ss]harper
*.DotSettings.user *.DotSettings.user
# JustCode is a .NET coding add-in # JustCode is a .NET coding add-in
.JustCode .JustCode
# TeamCity is a build add-in # TeamCity is a build add-in
_TeamCity* _TeamCity*
# DotCover is a Code Coverage Tool # DotCover is a Code Coverage Tool
*.dotCover *.dotCover
# NCrunch # NCrunch
_NCrunch_* _NCrunch_*
.*crunch*.local.xml .*crunch*.local.xml
nCrunchTemp_* nCrunchTemp_*
# MightyMoose # MightyMoose
*.mm.* *.mm.*
AutoTest.Net/ AutoTest.Net/
# Web workbench (sass) # Web workbench (sass)
.sass-cache/ .sass-cache/
# Installshield output folder # Installshield output folder
[Ee]xpress/ [Ee]xpress/
# DocProject is a documentation generator add-in # DocProject is a documentation generator add-in
DocProject/buildhelp/ DocProject/buildhelp/
DocProject/Help/*.HxT DocProject/Help/*.HxT
DocProject/Help/*.HxC DocProject/Help/*.HxC
DocProject/Help/*.hhc DocProject/Help/*.hhc
DocProject/Help/*.hhk DocProject/Help/*.hhk
DocProject/Help/*.hhp DocProject/Help/*.hhp
DocProject/Help/Html2 DocProject/Help/Html2
DocProject/Help/html DocProject/Help/html
# Click-Once directory # Click-Once directory
publish/ publish/
# Publish Web Output # Publish Web Output
*.[Pp]ublish.xml *.[Pp]ublish.xml
*.azurePubxml *.azurePubxml
# TODO: Comment the next line if you want to checkin your web deploy settings # TODO: Comment the next line if you want to checkin your web deploy settings
# but database connection strings (with potential passwords) will be unencrypted # but database connection strings (with potential passwords) will be unencrypted
*.pubxml *.pubxml
*.publishproj *.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to # Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained # checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted # in these scripts will be unencrypted
PublishScripts/ PublishScripts/
# NuGet Packages # NuGet Packages
*.nupkg *.nupkg
# The packages folder can be ignored because of Package Restore # The packages folder can be ignored because of Package Restore
**/packages/* **/packages/*
# except build/, which is used as an MSBuild target. # except build/, which is used as an MSBuild target.
!**/packages/build/ !**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed # Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config #!**/packages/repositories.config
# NuGet v3's project.json files produces more ignoreable files # NuGet v3's project.json files produces more ignoreable files
*.nuget.props *.nuget.props
*.nuget.targets *.nuget.targets
# Microsoft Azure Build Output # Microsoft Azure Build Output
csx/ csx/
*.build.csdef *.build.csdef
# Microsoft Azure Emulator # Microsoft Azure Emulator
ecf/ ecf/
rcf/ rcf/
# Windows Store app package directories and files # Windows Store app package directories and files
AppPackages/ AppPackages/
BundleArtifacts/ BundleArtifacts/
Package.StoreAssociation.xml Package.StoreAssociation.xml
_pkginfo.txt _pkginfo.txt
# Visual Studio cache files # Visual Studio cache files
# files ending in .cache can be ignored # files ending in .cache can be ignored
*.[Cc]ache *.[Cc]ache
# but keep track of directories ending in .cache # but keep track of directories ending in .cache
!*.[Cc]ache/ !*.[Cc]ache/
# Others # Others
ClientBin/ ClientBin/
~$* ~$*
*~ *~
*.dbmdl *.dbmdl
*.dbproj.schemaview *.dbproj.schemaview
*.pfx *.pfx
*.publishsettings *.publishsettings
node_modules/ node_modules/
orleans.codegen.cs orleans.codegen.cs
# Since there are multiple workflows, uncomment next line to ignore bower_components # Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/ #bower_components/
# RIA/Silverlight projects # RIA/Silverlight projects
Generated_Code/ Generated_Code/
# Backup & report files from converting an old project file # Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed, # to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-) # because we have git ;-)
_UpgradeReport_Files/ _UpgradeReport_Files/
Backup*/ Backup*/
UpgradeLog*.XML UpgradeLog*.XML
UpgradeLog*.htm UpgradeLog*.htm
# SQL Server files # SQL Server files
*.mdf *.mdf
*.ldf *.ldf
# Business Intelligence projects # Business Intelligence projects
*.rdl.data *.rdl.data
*.bim.layout *.bim.layout
*.bim_*.settings *.bim_*.settings
# Microsoft Fakes # Microsoft Fakes
FakesAssemblies/ FakesAssemblies/
# GhostDoc plugin setting file # GhostDoc plugin setting file
*.GhostDoc.xml *.GhostDoc.xml
# Node.js Tools for Visual Studio # Node.js Tools for Visual Studio
.ntvs_analysis.dat .ntvs_analysis.dat
# Visual Studio 6 build log # Visual Studio 6 build log
*.plg *.plg
# Visual Studio 6 workspace options file # Visual Studio 6 workspace options file
*.opt *.opt
# Visual Studio LightSwitch build output # Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts **/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml **/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts **/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml **/*.Server/ModelManifest.xml
_Pvt_Extensions _Pvt_Extensions
# Paket dependency manager # Paket dependency manager
.paket/paket.exe .paket/paket.exe
paket-files/ paket-files/
# FAKE - F# Make # FAKE - F# Make
.fake/ .fake/
# JetBrains Rider # JetBrains Rider
.idea/ .idea/
*.sln.iml *.sln.iml
# CodeRush # CodeRush
.cr/ .cr/
# Python Tools for Visual Studio (PTVS) # Python Tools for Visual Studio (PTVS)
__pycache__/ __pycache__/
*.pyc *.pyc
Staging/ Staging/

View File

@ -1,2 +1,2 @@
language: csharp language: csharp
solution: osu.sln solution: osu.sln

46
app.manifest Normal file
View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity version="1.0.0.0" name="osu!" />
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
<applicationRequestMinimum>
<defaultAssemblyRequest permissionSetReference="Custom" />
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
</applicationRequestMinimum>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<asmv3:application>
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>true</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</asmv1:assembly>

@ -1 +1 @@
Subproject commit 02d7a0fa4798d197cd08570ee48951edbb7c7860 Subproject commit 16e6a453db9a8f4454238a2911eb5f1444b7ec2a

View File

@ -1,21 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
--> -->
<configuration> <configuration>
<appSettings> <appSettings>
<add key="StagingFolder" value="Staging" /> <add key="StagingFolder" value="Staging" />
<add key="ReleasesFolder" value="Releases" /> <add key="ReleasesFolder" value="Releases" />
<add key="GitHubAccessToken" value="" /> <add key="GitHubAccessToken" value="" />
<add key="GitHubUsername" value="ppy" /> <add key="GitHubUsername" value="ppy" />
<add key="GitHubRepoName" value="osu" /> <add key="GitHubRepoName" value="osu" />
<add key="ProjectName" value="osu.Desktop" /> <add key="ProjectName" value="osu.Desktop" />
<add key="NuSpecName" value="osu.Desktop\osu.nuspec" /> <add key="NuSpecName" value="osu.Desktop\osu.nuspec" />
<add key="SolutionName" value="osu" /> <add key="SolutionName" value="osu" />
<add key="TargetName" value="osu.Desktop" /> <add key="TargetName" value="osu.Desktop" />
<add key="PackageName" value="osulazer" /> <add key="PackageName" value="osulazer" />
<add key="IconName" value="lazer.ico" /> <add key="IconName" value="lazer.ico" />
<add key="CodeSigningCertificate" value="" /> <add key="CodeSigningCertificate" value="" />
</appSettings> </appSettings>
</configuration> </configuration>

View File

@ -1,16 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json; using Newtonsoft.Json;
namespace osu.Desktop.Deploy namespace osu.Desktop.Deploy
{ {
public class GitHubObject public class GitHubObject
{ {
[JsonProperty(@"id")] [JsonProperty(@"id")]
public int Id; public int Id;
[JsonProperty(@"name")] [JsonProperty(@"name")]
public string Name; public string Name;
} }
} }

View File

@ -1,28 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json; using Newtonsoft.Json;
namespace osu.Desktop.Deploy namespace osu.Desktop.Deploy
{ {
public class GitHubRelease public class GitHubRelease
{ {
[JsonProperty(@"id")] [JsonProperty(@"id")]
public int Id; public int Id;
[JsonProperty(@"tag_name")] [JsonProperty(@"tag_name")]
public string TagName => $"v{Name}"; public string TagName => $"v{Name}";
[JsonProperty(@"name")] [JsonProperty(@"name")]
public string Name; public string Name;
[JsonProperty(@"draft")] [JsonProperty(@"draft")]
public bool Draft; public bool Draft;
[JsonProperty(@"prerelease")] [JsonProperty(@"prerelease")]
public bool PreRelease; public bool PreRelease;
[JsonProperty(@"upload_url")] [JsonProperty(@"upload_url")]
public string UploadUrl; public string UploadUrl;
} }
} }

View File

@ -1,438 +1,438 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Configuration; using System.Configuration;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.IO.Network; using osu.Framework.IO.Network;
using FileWebRequest = osu.Framework.IO.Network.FileWebRequest; using FileWebRequest = osu.Framework.IO.Network.FileWebRequest;
using WebRequest = osu.Framework.IO.Network.WebRequest; using WebRequest = osu.Framework.IO.Network.WebRequest;
namespace osu.Desktop.Deploy namespace osu.Desktop.Deploy
{ {
internal static class Program internal static class Program
{ {
private static string packages => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages"); 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 nugetPath => Path.Combine(packages, @"nuget.commandline\4.5.1\tools\NuGet.exe");
private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.7.8\tools\Squirrel.exe"); private static string squirrelPath => Path.Combine(packages, @"squirrel.windows\1.7.8\tools\Squirrel.exe");
private const string msbuild_path = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\Bin\MSBuild.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 StagingFolder = ConfigurationManager.AppSettings["StagingFolder"];
public static string ReleasesFolder = ConfigurationManager.AppSettings["ReleasesFolder"]; public static string ReleasesFolder = ConfigurationManager.AppSettings["ReleasesFolder"];
public static string GitHubAccessToken = ConfigurationManager.AppSettings["GitHubAccessToken"]; public static string GitHubAccessToken = ConfigurationManager.AppSettings["GitHubAccessToken"];
public static string GitHubUsername = ConfigurationManager.AppSettings["GitHubUsername"]; public static string GitHubUsername = ConfigurationManager.AppSettings["GitHubUsername"];
public static string GitHubRepoName = ConfigurationManager.AppSettings["GitHubRepoName"]; public static string GitHubRepoName = ConfigurationManager.AppSettings["GitHubRepoName"];
public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"]; public static string SolutionName = ConfigurationManager.AppSettings["SolutionName"];
public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"]; public static string ProjectName = ConfigurationManager.AppSettings["ProjectName"];
public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"]; public static string NuSpecName = ConfigurationManager.AppSettings["NuSpecName"];
public static string TargetNames = ConfigurationManager.AppSettings["TargetName"]; public static string TargetNames = ConfigurationManager.AppSettings["TargetName"];
public static string PackageName = ConfigurationManager.AppSettings["PackageName"]; public static string PackageName = ConfigurationManager.AppSettings["PackageName"];
public static string IconName = ConfigurationManager.AppSettings["IconName"]; public static string IconName = ConfigurationManager.AppSettings["IconName"];
public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"]; public static string CodeSigningCertificate = ConfigurationManager.AppSettings["CodeSigningCertificate"];
public static string GitHubApiEndpoint => $"https://api.github.com/repos/{GitHubUsername}/{GitHubRepoName}/releases"; public static string GitHubApiEndpoint => $"https://api.github.com/repos/{GitHubUsername}/{GitHubRepoName}/releases";
public static string GitHubReleasePage => $"https://github.com/{GitHubUsername}/{GitHubRepoName}/releases"; public static string GitHubReleasePage => $"https://github.com/{GitHubUsername}/{GitHubRepoName}/releases";
/// <summary> /// <summary>
/// How many previous build deltas we want to keep when publishing. /// How many previous build deltas we want to keep when publishing.
/// </summary> /// </summary>
private const int keep_delta_count = 4; 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 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 homeDir => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
private static string codeSigningCertPath => Path.Combine(homeDir, CodeSigningCertificate); private static string codeSigningCertPath => Path.Combine(homeDir, CodeSigningCertificate);
private static string solutionPath => Environment.CurrentDirectory; private static string solutionPath => Environment.CurrentDirectory;
private static string stagingPath => Path.Combine(solutionPath, StagingFolder); private static string stagingPath => Path.Combine(solutionPath, StagingFolder);
private static string iconPath => Path.Combine(solutionPath, ProjectName, IconName); private static string iconPath => Path.Combine(solutionPath, ProjectName, IconName);
private static string nupkgFilename(string ver) => $"{PackageName}.{ver}.nupkg"; private static string nupkgFilename(string ver) => $"{PackageName}.{ver}.nupkg";
private static string nupkgDistroFilename(string ver) => $"{PackageName}-{ver}-full.nupkg"; private static string nupkgDistroFilename(string ver) => $"{PackageName}-{ver}-full.nupkg";
private static readonly Stopwatch sw = new Stopwatch(); private static readonly Stopwatch sw = new Stopwatch();
private static string codeSigningPassword; private static string codeSigningPassword;
public static void Main(string[] args) public static void Main(string[] args)
{ {
displayHeader(); displayHeader();
findSolutionPath(); findSolutionPath();
if (!Directory.Exists(ReleasesFolder)) if (!Directory.Exists(ReleasesFolder))
{ {
write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow); write("WARNING: No release directory found. Make sure you want this!", ConsoleColor.Yellow);
Directory.CreateDirectory(ReleasesFolder); Directory.CreateDirectory(ReleasesFolder);
} }
checkGitHubReleases(); checkGitHubReleases();
refreshDirectory(StagingFolder); refreshDirectory(StagingFolder);
//increment build number until we have a unique one. //increment build number until we have a unique one.
string verBase = DateTime.Now.ToString("yyyy.Mdd."); string verBase = DateTime.Now.ToString("yyyy.Mdd.");
int increment = 0; int increment = 0;
while (Directory.GetFiles(ReleasesFolder, $"*{verBase}{increment}*").Any()) while (Directory.GetFiles(ReleasesFolder, $"*{verBase}{increment}*").Any())
increment++; increment++;
string version = $"{verBase}{increment}"; string version = $"{verBase}{increment}";
Console.ForegroundColor = ConsoleColor.White; Console.ForegroundColor = ConsoleColor.White;
Console.Write($"Ready to deploy {version}: "); Console.Write($"Ready to deploy {version}: ");
Console.ReadLine(); Console.ReadLine();
sw.Start(); sw.Start();
if (!string.IsNullOrEmpty(CodeSigningCertificate)) if (!string.IsNullOrEmpty(CodeSigningCertificate))
{ {
Console.Write("Enter code signing password: "); Console.Write("Enter code signing password: ");
codeSigningPassword = readLineMasked(); codeSigningPassword = readLineMasked();
} }
write("Updating AssemblyInfo..."); write("Updating AssemblyInfo...");
updateCsprojVersion(version); updateCsprojVersion(version);
write("Running build process..."); write("Running build process...");
foreach (string targetName in TargetNames.Split(',')) 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"); runCommand(msbuild_path, $"/v:quiet /m /t:{targetName.Replace('.', '_')} /p:OutputPath={stagingPath};Targets=\"Clean;Build\";Configuration=Release {SolutionName}.sln");
write("Creating NuGet deployment package..."); write("Creating NuGet deployment package...");
runCommand(nugetPath, $"pack {NuSpecName} -Version {version} -Properties Configuration=Deploy -OutputDirectory {stagingPath} -BasePath {stagingPath}"); 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. //prune once before checking for files so we can avoid erroring on files which aren't even needed for this build.
pruneReleases(); pruneReleases();
checkReleaseFiles(); checkReleaseFiles();
write("Running squirrel build..."); write("Running squirrel build...");
runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi"); runCommand(squirrelPath, $"--releasify {stagingPath}\\{nupkgFilename(version)} --setupIcon {iconPath} --icon {iconPath} {codeSigningCmd} --no-msi");
//prune again to clean up before upload. //prune again to clean up before upload.
pruneReleases(); pruneReleases();
//rename setup to install. //rename setup to install.
File.Copy(Path.Combine(ReleasesFolder, "Setup.exe"), Path.Combine(ReleasesFolder, "install.exe"), true); File.Copy(Path.Combine(ReleasesFolder, "Setup.exe"), Path.Combine(ReleasesFolder, "install.exe"), true);
File.Delete(Path.Combine(ReleasesFolder, "Setup.exe")); File.Delete(Path.Combine(ReleasesFolder, "Setup.exe"));
uploadBuild(version); uploadBuild(version);
//reset assemblyinfo. //reset assemblyinfo.
updateCsprojVersion("0.0.0"); updateCsprojVersion("0.0.0");
write("Done!", ConsoleColor.White); write("Done!", ConsoleColor.White);
Console.ReadLine(); Console.ReadLine();
} }
private static void displayHeader() private static void displayHeader()
{ {
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(); Console.WriteLine();
Console.WriteLine(" Please note that OSU! and PPY are registered trademarks and as such covered by trademark law."); 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.WriteLine(" Do not distribute builds of this project publicly that make use of these.");
Console.ResetColor(); Console.ResetColor();
Console.WriteLine(); Console.WriteLine();
} }
/// <summary> /// <summary>
/// Ensure we have all the files in the release directory which are expected to be there. /// 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. /// This should have been accounted for in earlier steps, and just serves as a verification step.
/// </summary> /// </summary>
private static void checkReleaseFiles() private static void checkReleaseFiles()
{ {
if (!canGitHub) return; if (!canGitHub) return;
var releaseLines = getReleaseLines(); var releaseLines = getReleaseLines();
//ensure we have all files necessary //ensure we have all files necessary
foreach (var l in releaseLines) foreach (var l in releaseLines)
if (!File.Exists(Path.Combine(ReleasesFolder, l.Filename))) if (!File.Exists(Path.Combine(ReleasesFolder, l.Filename)))
error($"Local file missing {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 IEnumerable<ReleaseLine> getReleaseLines() => File.ReadAllLines(Path.Combine(ReleasesFolder, "RELEASES")).Select(l => new ReleaseLine(l));
private static void pruneReleases() private static void pruneReleases()
{ {
if (!canGitHub) return; if (!canGitHub) return;
write("Pruning RELEASES..."); write("Pruning RELEASES...");
var releaseLines = getReleaseLines().ToList(); var releaseLines = getReleaseLines().ToList();
var fulls = releaseLines.Where(l => l.Filename.Contains("-full")).Reverse().Skip(1); var fulls = releaseLines.Where(l => l.Filename.Contains("-full")).Reverse().Skip(1);
//remove any FULL releases (except most recent) //remove any FULL releases (except most recent)
foreach (var l in fulls) foreach (var l in fulls)
{ {
write($"- Removing old release {l.Filename}", ConsoleColor.Yellow); write($"- Removing old release {l.Filename}", ConsoleColor.Yellow);
File.Delete(Path.Combine(ReleasesFolder, l.Filename)); File.Delete(Path.Combine(ReleasesFolder, l.Filename));
releaseLines.Remove(l); releaseLines.Remove(l);
} }
//remove excess deltas //remove excess deltas
var deltas = releaseLines.Where(l => l.Filename.Contains("-delta")).ToArray(); var deltas = releaseLines.Where(l => l.Filename.Contains("-delta")).ToArray();
if (deltas.Length > keep_delta_count) if (deltas.Length > keep_delta_count)
{ {
foreach (var l in deltas.Take(deltas.Length - keep_delta_count)) foreach (var l in deltas.Take(deltas.Length - keep_delta_count))
{ {
write($"- Removing old delta {l.Filename}", ConsoleColor.Yellow); write($"- Removing old delta {l.Filename}", ConsoleColor.Yellow);
File.Delete(Path.Combine(ReleasesFolder, l.Filename)); File.Delete(Path.Combine(ReleasesFolder, l.Filename));
releaseLines.Remove(l); releaseLines.Remove(l);
} }
} }
var lines = new List<string>(); var lines = new List<string>();
releaseLines.ForEach(l => lines.Add(l.ToString())); releaseLines.ForEach(l => lines.Add(l.ToString()));
File.WriteAllLines(Path.Combine(ReleasesFolder, "RELEASES"), lines); File.WriteAllLines(Path.Combine(ReleasesFolder, "RELEASES"), lines);
} }
private static void uploadBuild(string version) private static void uploadBuild(string version)
{ {
if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate)) if (!canGitHub || string.IsNullOrEmpty(CodeSigningCertificate))
return; return;
write("Publishing to GitHub..."); write("Publishing to GitHub...");
write($"- Creating release {version}...", ConsoleColor.Yellow); write($"- Creating release {version}...", ConsoleColor.Yellow);
var req = new JsonWebRequest<GitHubRelease>($"{GitHubApiEndpoint}") var req = new JsonWebRequest<GitHubRelease>($"{GitHubApiEndpoint}")
{ {
Method = HttpMethod.POST, Method = HttpMethod.POST,
}; };
req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease req.AddRaw(JsonConvert.SerializeObject(new GitHubRelease
{ {
Name = version, Name = version,
Draft = true, Draft = true,
PreRelease = true PreRelease = true
})); }));
req.AuthenticatedBlockingPerform(); req.AuthenticatedBlockingPerform();
var assetUploadUrl = req.ResponseObject.UploadUrl.Replace("{?name,label}", "?name={0}"); var assetUploadUrl = req.ResponseObject.UploadUrl.Replace("{?name,label}", "?name={0}");
foreach (var a in Directory.GetFiles(ReleasesFolder).Reverse()) //reverse to upload RELEASES first. foreach (var a in Directory.GetFiles(ReleasesFolder).Reverse()) //reverse to upload RELEASES first.
{ {
write($"- Adding asset {a}...", ConsoleColor.Yellow); write($"- Adding asset {a}...", ConsoleColor.Yellow);
var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a)) var upload = new WebRequest(assetUploadUrl, Path.GetFileName(a))
{ {
Method = HttpMethod.POST, Method = HttpMethod.POST,
Timeout = 240000, Timeout = 240000,
ContentType = "application/octet-stream", ContentType = "application/octet-stream",
}; };
upload.AddRaw(File.ReadAllBytes(a)); upload.AddRaw(File.ReadAllBytes(a));
upload.AuthenticatedBlockingPerform(); upload.AuthenticatedBlockingPerform();
} }
openGitHubReleasePage(); openGitHubReleasePage();
} }
private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage); private static void openGitHubReleasePage() => Process.Start(GitHubReleasePage);
private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken); private static bool canGitHub => !string.IsNullOrEmpty(GitHubAccessToken);
private static void checkGitHubReleases() private static void checkGitHubReleases()
{ {
if (!canGitHub) return; if (!canGitHub) return;
write("Checking GitHub releases..."); write("Checking GitHub releases...");
var req = new JsonWebRequest<List<GitHubRelease>>($"{GitHubApiEndpoint}"); var req = new JsonWebRequest<List<GitHubRelease>>($"{GitHubApiEndpoint}");
req.AuthenticatedBlockingPerform(); req.AuthenticatedBlockingPerform();
var lastRelease = req.ResponseObject.FirstOrDefault(); var lastRelease = req.ResponseObject.FirstOrDefault();
if (lastRelease == null) if (lastRelease == null)
return; return;
if (lastRelease.Draft) if (lastRelease.Draft)
{ {
openGitHubReleasePage(); openGitHubReleasePage();
error("There's a pending draft release! You probably don't want to push a build with this present."); 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. //there's a previous release for this project.
var assetReq = new JsonWebRequest<List<GitHubObject>>($"{GitHubApiEndpoint}/{lastRelease.Id}/assets"); var assetReq = new JsonWebRequest<List<GitHubObject>>($"{GitHubApiEndpoint}/{lastRelease.Id}/assets");
assetReq.AuthenticatedBlockingPerform(); assetReq.AuthenticatedBlockingPerform();
var assets = assetReq.ResponseObject; var assets = assetReq.ResponseObject;
//make sure our RELEASES file is the same as the last build on the server. //make sure our RELEASES file is the same as the last build on the server.
var releaseAsset = assets.FirstOrDefault(a => a.Name == "RELEASES"); 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 we don't have a RELEASES asset then the previous release likely wasn't a Squirrel one.
if (releaseAsset == null) return; if (releaseAsset == null) return;
write($"Last GitHub release was {lastRelease.Name}."); write($"Last GitHub release was {lastRelease.Name}.");
bool requireDownload = false; bool requireDownload = false;
if (!File.Exists(Path.Combine(ReleasesFolder, nupkgDistroFilename(lastRelease.Name)))) if (!File.Exists(Path.Combine(ReleasesFolder, nupkgDistroFilename(lastRelease.Name))))
{ {
write("Last version's package not found locally.", ConsoleColor.Red); write("Last version's package not found locally.", ConsoleColor.Red);
requireDownload = true; requireDownload = true;
} }
else else
{ {
var lastReleases = new RawFileWebRequest($"{GitHubApiEndpoint}/assets/{releaseAsset.Id}"); var lastReleases = new RawFileWebRequest($"{GitHubApiEndpoint}/assets/{releaseAsset.Id}");
lastReleases.AuthenticatedBlockingPerform(); lastReleases.AuthenticatedBlockingPerform();
if (File.ReadAllText(Path.Combine(ReleasesFolder, "RELEASES")) != lastReleases.ResponseString) if (File.ReadAllText(Path.Combine(ReleasesFolder, "RELEASES")) != lastReleases.ResponseString)
{ {
write("Server's RELEASES differed from ours.", ConsoleColor.Red); write("Server's RELEASES differed from ours.", ConsoleColor.Red);
requireDownload = true; requireDownload = true;
} }
} }
if (!requireDownload) return; if (!requireDownload) return;
write("Refreshing local releases directory..."); write("Refreshing local releases directory...");
refreshDirectory(ReleasesFolder); refreshDirectory(ReleasesFolder);
foreach (var a in assets) foreach (var a in assets)
{ {
if (a.Name.EndsWith(".exe")) continue; if (a.Name.EndsWith(".exe")) continue;
write($"- Downloading {a.Name}...", ConsoleColor.Yellow); write($"- Downloading {a.Name}...", ConsoleColor.Yellow);
new FileWebRequest(Path.Combine(ReleasesFolder, a.Name), $"{GitHubApiEndpoint}/assets/{a.Id}").AuthenticatedBlockingPerform(); new FileWebRequest(Path.Combine(ReleasesFolder, a.Name), $"{GitHubApiEndpoint}/assets/{a.Id}").AuthenticatedBlockingPerform();
} }
} }
private static void refreshDirectory(string directory) private static void refreshDirectory(string directory)
{ {
if (Directory.Exists(directory)) if (Directory.Exists(directory))
Directory.Delete(directory, true); Directory.Delete(directory, true);
Directory.CreateDirectory(directory); Directory.CreateDirectory(directory);
} }
private static void updateCsprojVersion(string version) private static void updateCsprojVersion(string version)
{ {
var toUpdate = new[] { "<Version>", "<FileVersion>" }; var toUpdate = new[] { "<Version>", "<FileVersion>" };
string file = Path.Combine(ProjectName, $"{ProjectName}.csproj"); string file = Path.Combine(ProjectName, $"{ProjectName}.csproj");
var l1 = File.ReadAllLines(file); var l1 = File.ReadAllLines(file);
List<string> l2 = new List<string>(); List<string> l2 = new List<string>();
foreach (var l in l1) foreach (var l in l1)
{ {
string line = l; string line = l;
foreach (var tag in toUpdate) foreach (var tag in toUpdate)
{ {
int startIndex = l.IndexOf(tag, StringComparison.InvariantCulture); int startIndex = l.IndexOf(tag, StringComparison.InvariantCulture);
if (startIndex == -1) if (startIndex == -1)
continue; continue;
startIndex += tag.Length; startIndex += tag.Length;
int endIndex = l.IndexOf("<", startIndex, StringComparison.InvariantCulture); int endIndex = l.IndexOf("<", startIndex, StringComparison.InvariantCulture);
line = $"{l.Substring(0, startIndex)}{version}{l.Substring(endIndex)}"; line = $"{l.Substring(0, startIndex)}{version}{l.Substring(endIndex)}";
} }
l2.Add(line); l2.Add(line);
} }
File.WriteAllLines(file, l2); File.WriteAllLines(file, l2);
} }
/// <summary> /// <summary>
/// Find the base path of the active solution (git checkout location) /// Find the base path of the active solution (git checkout location)
/// </summary> /// </summary>
private static void findSolutionPath() private static void findSolutionPath()
{ {
string path = Path.GetDirectoryName(Environment.CommandLine.Replace("\"", "").Trim()); string path = Path.GetDirectoryName(Environment.CommandLine.Replace("\"", "").Trim());
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
path = Environment.CurrentDirectory; path = Environment.CurrentDirectory;
while (!File.Exists(Path.Combine(path, $"{SolutionName}.sln"))) while (!File.Exists(Path.Combine(path, $"{SolutionName}.sln")))
path = path.Remove(path.LastIndexOf(Path.DirectorySeparatorChar)); path = path.Remove(path.LastIndexOf(Path.DirectorySeparatorChar));
path += Path.DirectorySeparatorChar; path += Path.DirectorySeparatorChar;
Environment.CurrentDirectory = path; Environment.CurrentDirectory = path;
} }
private static bool runCommand(string command, string args) private static bool runCommand(string command, string args)
{ {
var psi = new ProcessStartInfo(command, args) var psi = new ProcessStartInfo(command, args)
{ {
WorkingDirectory = solutionPath, WorkingDirectory = solutionPath,
CreateNoWindow = true, CreateNoWindow = true,
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = true, RedirectStandardError = true,
UseShellExecute = false, UseShellExecute = false,
WindowStyle = ProcessWindowStyle.Hidden WindowStyle = ProcessWindowStyle.Hidden
}; };
Process p = Process.Start(psi); Process p = Process.Start(psi);
if (p == null) return false; if (p == null) return false;
string output = p.StandardOutput.ReadToEnd(); string output = p.StandardOutput.ReadToEnd();
output += p.StandardError.ReadToEnd(); output += p.StandardError.ReadToEnd();
if (p.ExitCode == 0) return true; if (p.ExitCode == 0) return true;
write(output); write(output);
error($"Command {command} {args} failed!"); error($"Command {command} {args} failed!");
return false; return false;
} }
private static string readLineMasked() private static string readLineMasked()
{ {
var fg = Console.ForegroundColor; var fg = Console.ForegroundColor;
Console.ForegroundColor = Console.BackgroundColor; Console.ForegroundColor = Console.BackgroundColor;
var ret = Console.ReadLine(); var ret = Console.ReadLine();
Console.ForegroundColor = fg; Console.ForegroundColor = fg;
return ret; return ret;
} }
private static void error(string message) private static void error(string message)
{ {
Console.ForegroundColor = ConsoleColor.Red; Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"FATAL ERROR: {message}"); Console.WriteLine($"FATAL ERROR: {message}");
Console.ReadLine(); Console.ReadLine();
Environment.Exit(-1); Environment.Exit(-1);
} }
private static void write(string message, ConsoleColor col = ConsoleColor.Gray) private static void write(string message, ConsoleColor col = ConsoleColor.Gray)
{ {
if (sw.ElapsedMilliseconds > 0) if (sw.ElapsedMilliseconds > 0)
{ {
Console.ForegroundColor = ConsoleColor.Green; Console.ForegroundColor = ConsoleColor.Green;
Console.Write(sw.ElapsedMilliseconds.ToString().PadRight(8)); Console.Write(sw.ElapsedMilliseconds.ToString().PadRight(8));
} }
Console.ForegroundColor = col; Console.ForegroundColor = col;
Console.WriteLine(message); Console.WriteLine(message);
} }
public static void AuthenticatedBlockingPerform(this WebRequest r) public static void AuthenticatedBlockingPerform(this WebRequest r)
{ {
r.AddHeader("Authorization", $"token {GitHubAccessToken}"); r.AddHeader("Authorization", $"token {GitHubAccessToken}");
r.Perform(); r.Perform();
} }
} }
internal class RawFileWebRequest : WebRequest internal class RawFileWebRequest : WebRequest
{ {
public RawFileWebRequest(string url) : base(url) public RawFileWebRequest(string url) : base(url)
{ {
} }
protected override string Accept => "application/octet-stream"; protected override string Accept => "application/octet-stream";
} }
internal class ReleaseLine internal class ReleaseLine
{ {
public string Hash; public string Hash;
public string Filename; public string Filename;
public int Filesize; public int Filesize;
public ReleaseLine(string line) public ReleaseLine(string line)
{ {
var split = line.Split(' '); var split = line.Split(' ');
Hash = split[0]; Hash = split[0];
Filename = split[1]; Filename = split[1];
Filesize = int.Parse(split[2]); Filesize = int.Parse(split[2]);
} }
public override string ToString() => $"{Hash} {Filename} {Filesize}"; public override string ToString() => $"{Hash} {Filename} {Filesize}";
} }
} }

View File

@ -11,7 +11,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="NuGet.CommandLine" Version="4.5.1" /> <PackageReference Include="NuGet.CommandLine" Version="4.5.1" />
<PackageReference Include="NUnit" Version="3.8.1" /> <PackageReference Include="NUnit" Version="3.10.1" />
<PackageReference Include="squirrel.windows" Version="1.7.8" Condition="'$(TargetFramework)' == 'net461'" /> <PackageReference Include="squirrel.windows" Version="1.7.8" Condition="'$(TargetFramework)' == 'net461'" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" /> <PackageReference Include="System.Configuration.ConfigurationManager" Version="4.4.0" />
</ItemGroup> </ItemGroup>

View File

@ -1,120 +1,120 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Desktop.Overlays; using osu.Desktop.Overlays;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game; using osu.Game;
using OpenTK.Input; using OpenTK.Input;
using Microsoft.Win32; using Microsoft.Win32;
namespace osu.Desktop namespace osu.Desktop
{ {
internal class OsuGameDesktop : OsuGame internal class OsuGameDesktop : OsuGame
{ {
private readonly bool noVersionOverlay; private readonly bool noVersionOverlay;
public OsuGameDesktop(string[] args = null) public OsuGameDesktop(string[] args = null)
: base(args) : base(args)
{ {
noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false; noVersionOverlay = args?.Any(a => a == "--no-version-overlay") ?? false;
} }
public override Storage GetStorageForStableInstall() public override Storage GetStorageForStableInstall()
{ {
try try
{ {
return new StableStorage(); return new StableStorage();
} }
catch catch
{ {
return null; return null;
} }
} }
/// <summary> /// <summary>
/// A method of accessing an osu-stable install in a controlled fashion. /// A method of accessing an osu-stable install in a controlled fashion.
/// </summary> /// </summary>
private class StableStorage : DesktopStorage private class StableStorage : DesktopStorage
{ {
protected override string LocateBasePath() protected override string LocateBasePath()
{ {
bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs")); bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
string stableInstallPath; string stableInstallPath;
try try
{ {
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(String.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
if (checkExists(stableInstallPath)) if (checkExists(stableInstallPath))
return stableInstallPath; return stableInstallPath;
} }
catch catch
{ {
} }
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
if (checkExists(stableInstallPath)) if (checkExists(stableInstallPath))
return stableInstallPath; return stableInstallPath;
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
if (checkExists(stableInstallPath)) if (checkExists(stableInstallPath))
return stableInstallPath; return stableInstallPath;
return null; return null;
} }
public StableStorage() public StableStorage()
: base(string.Empty) : base(string.Empty)
{ {
} }
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
if (!noVersionOverlay) if (!noVersionOverlay)
{ {
LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v =>
{ {
Add(v); Add(v);
v.State = Visibility.Visible; v.State = Visibility.Visible;
}); });
} }
} }
public override void SetHost(GameHost host) public override void SetHost(GameHost host)
{ {
base.SetHost(host); base.SetHost(host);
var desktopWindow = host.Window as DesktopGameWindow; var desktopWindow = host.Window as DesktopGameWindow;
if (desktopWindow != null) if (desktopWindow != null)
{ {
desktopWindow.CursorState |= CursorState.Hidden; desktopWindow.CursorState |= CursorState.Hidden;
desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
desktopWindow.Title = Name; desktopWindow.Title = Name;
desktopWindow.FileDrop += fileDrop; desktopWindow.FileDrop += fileDrop;
} }
} }
private void fileDrop(object sender, FileDropEventArgs e) private void fileDrop(object sender, FileDropEventArgs e)
{ {
var filePaths = new[] { e.FileName }; var filePaths = new[] { e.FileName };
var firstExtension = Path.GetExtension(filePaths.First()); var firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning); Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning);
} }
} }
} }

View File

@ -1,164 +1,164 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
#if NET_FRAMEWORK #if NET_FRAMEWORK
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game; using osu.Game;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using Squirrel; using Squirrel;
namespace osu.Desktop.Overlays namespace osu.Desktop.Overlays
{ {
public class SquirrelUpdateManager : Component public class SquirrelUpdateManager : Component
{ {
private UpdateManager updateManager; private UpdateManager updateManager;
private NotificationOverlay notificationOverlay; private NotificationOverlay notificationOverlay;
public void PrepareUpdate() public void PrepareUpdate()
{ {
// Squirrel returns execution to us after the update process is started, so it's safe to use Wait() here // Squirrel returns execution to us after the update process is started, so it's safe to use Wait() here
UpdateManager.RestartAppWhenExited().Wait(); UpdateManager.RestartAppWhenExited().Wait();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuGameBase game) private void load(NotificationOverlay notification, OsuGameBase game)
{ {
notificationOverlay = notification; notificationOverlay = notification;
if (game.IsDeployedBuild) if (game.IsDeployedBuild)
Schedule(() => checkForUpdateAsync()); Schedule(() => checkForUpdateAsync());
} }
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{ {
//should we schedule a retry on completion of this check? //should we schedule a retry on completion of this check?
bool scheduleRetry = true; bool scheduleRetry = true;
try try
{ {
if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
var info = await updateManager.CheckForUpdate(!useDeltaPatching); var info = await updateManager.CheckForUpdate(!useDeltaPatching);
if (info.ReleasesToApply.Count == 0) if (info.ReleasesToApply.Count == 0)
//no updates available. bail and retry later. //no updates available. bail and retry later.
return; return;
if (notification == null) if (notification == null)
{ {
notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active }; notification = new UpdateProgressNotification(this) { State = ProgressNotificationState.Active };
Schedule(() => notificationOverlay.Post(notification)); Schedule(() => notificationOverlay.Post(notification));
} }
notification.Progress = 0; notification.Progress = 0;
notification.Text = @"Downloading update..."; notification.Text = @"Downloading update...";
try try
{ {
await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f); await updateManager.DownloadReleases(info.ReleasesToApply, p => notification.Progress = p / 100f);
notification.Progress = 0; notification.Progress = 0;
notification.Text = @"Installing update..."; notification.Text = @"Installing update...";
await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f); await updateManager.ApplyReleases(info, p => notification.Progress = p / 100f);
notification.State = ProgressNotificationState.Completed; notification.State = ProgressNotificationState.Completed;
} }
catch (Exception e) catch (Exception e)
{ {
if (useDeltaPatching) if (useDeltaPatching)
{ {
Logger.Error(e, @"delta patching failed!"); Logger.Error(e, @"delta patching failed!");
//could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) //could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
//try again without deltas. //try again without deltas.
checkForUpdateAsync(false, notification); checkForUpdateAsync(false, notification);
scheduleRetry = false; scheduleRetry = false;
} }
else else
{ {
Logger.Error(e, @"update failed!"); Logger.Error(e, @"update failed!");
} }
} }
} }
catch (Exception) catch (Exception)
{ {
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion. // we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
} }
finally finally
{ {
if (scheduleRetry) if (scheduleRetry)
{ {
if (notification != null) if (notification != null)
notification.State = ProgressNotificationState.Cancelled; notification.State = ProgressNotificationState.Cancelled;
//check again in 30 minutes. //check again in 30 minutes.
Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30); Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
} }
} }
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
updateManager?.Dispose(); updateManager?.Dispose();
} }
private class UpdateProgressNotification : ProgressNotification private class UpdateProgressNotification : ProgressNotification
{ {
private readonly SquirrelUpdateManager updateManager; private readonly SquirrelUpdateManager updateManager;
private OsuGame game; private OsuGame game;
public UpdateProgressNotification(SquirrelUpdateManager updateManager) public UpdateProgressNotification(SquirrelUpdateManager updateManager)
{ {
this.updateManager = updateManager; this.updateManager = updateManager;
} }
protected override Notification CreateCompletionNotification() protected override Notification CreateCompletionNotification()
{ {
return new ProgressCompletionNotification return new ProgressCompletionNotification
{ {
Text = @"Update ready to install. Click to restart!", Text = @"Update ready to install. Click to restart!",
Activated = () => Activated = () =>
{ {
updateManager.PrepareUpdate(); updateManager.PrepareUpdate();
game.GracefullyExit(); game.GracefullyExit();
return true; return true;
} }
}; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, OsuGame game) private void load(OsuColour colours, OsuGame game)
{ {
this.game = game; this.game = game;
IconContent.AddRange(new Drawable[] IconContent.AddRange(new Drawable[]
{ {
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(colours.YellowDark, colours.Yellow) Colour = ColourInfo.GradientVertical(colours.YellowDark, colours.Yellow)
}, },
new SpriteIcon new SpriteIcon
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Icon = FontAwesome.fa_upload, Icon = FontAwesome.fa_upload,
Colour = Color4.White, Colour = Color4.White,
Size = new Vector2(20), Size = new Vector2(20),
} }
}); });
} }
} }
} }
} }
#endif #endif

View File

@ -1,142 +1,142 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Development; using osu.Framework.Development;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game; using osu.Game;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Desktop.Overlays namespace osu.Desktop.Overlays
{ {
public class VersionManager : OverlayContainer public class VersionManager : OverlayContainer
{ {
private OsuConfigManager config; private OsuConfigManager config;
private OsuGameBase game; private OsuGameBase game;
private NotificationOverlay notificationOverlay; private NotificationOverlay notificationOverlay;
public override bool HandleKeyboardInput => false; public override bool HandleKeyboardInput => false;
public override bool HandleMouseInput => false; public override bool HandleMouseInput => false;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config) private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config)
{ {
notificationOverlay = notification; notificationOverlay = notification;
this.config = config; this.config = config;
this.game = game; this.game = game;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre; Origin = Anchor.BottomCentre;
Alpha = 0; Alpha = 0;
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(5), Spacing = new Vector2(5),
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuSpriteText new OsuSpriteText
{ {
Font = @"Exo2.0-Bold", Font = @"Exo2.0-Bold",
Text = game.Name Text = game.Name
}, },
new OsuSpriteText new OsuSpriteText
{ {
Colour = DebugUtils.IsDebug ? colours.Red : Color4.White, Colour = DebugUtils.IsDebug ? colours.Red : Color4.White,
Text = game.Version Text = game.Version
}, },
} }
}, },
new OsuSpriteText new OsuSpriteText
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
TextSize = 12, TextSize = 12,
Colour = colours.Yellow, Colour = colours.Yellow,
Font = @"Venera", Font = @"Venera",
Text = @"Development Build" Text = @"Development Build"
}, },
new Sprite new Sprite
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Texture = textures.Get(@"Menu/dev-build-footer"), Texture = textures.Get(@"Menu/dev-build-footer"),
}, },
} }
} }
}; };
#if NET_FRAMEWORK #if NET_FRAMEWORK
Add(new SquirrelUpdateManager()); Add(new SquirrelUpdateManager());
#endif #endif
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
var version = game.Version; var version = game.Version;
var lastVersion = config.Get<string>(OsuSetting.Version); var lastVersion = config.Get<string>(OsuSetting.Version);
if (game.IsDeployedBuild && version != lastVersion) if (game.IsDeployedBuild && version != lastVersion)
{ {
config.Set(OsuSetting.Version, version); config.Set(OsuSetting.Version, version);
// only show a notification if we've previously saved a version to the config file (ie. not the first run). // only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion)) if (!string.IsNullOrEmpty(lastVersion))
notificationOverlay.Post(new UpdateCompleteNotification(version)); notificationOverlay.Post(new UpdateCompleteNotification(version));
} }
} }
private class UpdateCompleteNotification : SimpleNotification private class UpdateCompleteNotification : SimpleNotification
{ {
public UpdateCompleteNotification(string version) public UpdateCompleteNotification(string version)
{ {
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!"; Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
Icon = FontAwesome.fa_check_square; Icon = FontAwesome.fa_check_square;
Activated = delegate Activated = delegate
{ {
Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}"); Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}");
return true; return true;
}; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
IconBackgound.Colour = colours.BlueDark; IconBackgound.Colour = colours.BlueDark;
} }
} }
protected override void PopIn() protected override void PopIn()
{ {
this.FadeIn(1000); this.FadeIn(1000);
} }
protected override void PopOut() protected override void PopOut()
{ {
} }
} }
} }

View File

@ -1,66 +1,66 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using osu.Framework; using osu.Framework;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.IPC; using osu.Game.IPC;
#if NET_FRAMEWORK #if NET_FRAMEWORK
using System.Runtime; using System.Runtime;
#endif #endif
namespace osu.Desktop namespace osu.Desktop
{ {
public static class Program public static class Program
{ {
[STAThread] [STAThread]
public static int Main(string[] args) public static int Main(string[] args)
{ {
// required to initialise native SQLite libraries on some platforms. // required to initialise native SQLite libraries on some platforms.
if (!RuntimeInfo.IsMono) if (!RuntimeInfo.IsMono)
useMulticoreJit(); useMulticoreJit();
// Back up the cwd before DesktopGameHost changes it // Back up the cwd before DesktopGameHost changes it
var cwd = Environment.CurrentDirectory; var cwd = Environment.CurrentDirectory;
using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true)) using (DesktopGameHost host = Host.GetSuitableHost(@"osu", true))
{ {
if (!host.IsPrimaryInstance) if (!host.IsPrimaryInstance)
{ {
var importer = new ArchiveImportIPCChannel(host); var importer = new ArchiveImportIPCChannel(host);
// Restore the cwd so relative paths given at the command line work correctly // Restore the cwd so relative paths given at the command line work correctly
Directory.SetCurrentDirectory(cwd); Directory.SetCurrentDirectory(cwd);
foreach (var file in args) foreach (var file in args)
{ {
Console.WriteLine(@"Importing {0}", file); Console.WriteLine(@"Importing {0}", file);
if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000))
throw new TimeoutException(@"IPC took too long to send"); throw new TimeoutException(@"IPC took too long to send");
} }
} }
else else
{ {
switch (args.FirstOrDefault() ?? string.Empty) switch (args.FirstOrDefault() ?? string.Empty)
{ {
default: default:
host.Run(new OsuGameDesktop(args)); host.Run(new OsuGameDesktop(args));
break; break;
} }
} }
return 0; return 0;
} }
} }
private static void useMulticoreJit() private static void useMulticoreJit()
{ {
#if NET_FRAMEWORK #if NET_FRAMEWORK
var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles")); var directory = Directory.CreateDirectory(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Profiles"));
ProfileOptimization.SetProfileRoot(directory.FullName); ProfileOptimization.SetProfileRoot(directory.FullName);
ProfileOptimization.StartProfile("Startup.Profile"); ProfileOptimization.StartProfile("Startup.Profile");
#endif #endif
} }
} }
} }

View File

@ -1,67 +1,67 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class CatchBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch"; protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
[TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")] [TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")]
public new void Test(string name) public new void Test(string name)
{ {
base.Test(name); base.Test(name);
} }
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
{ {
if (hitObject is JuiceStream stream) if (hitObject is JuiceStream stream)
{ {
foreach (var nested in stream.NestedHitObjects) foreach (var nested in stream.NestedHitObjects)
{ {
yield return new ConvertValue yield return new ConvertValue
{ {
StartTime = nested.StartTime, StartTime = nested.StartTime,
Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH Position = ((CatchHitObject)nested).X * CatchPlayfield.BASE_WIDTH
}; };
} }
} }
else else
{ {
yield return new ConvertValue yield return new ConvertValue
{ {
StartTime = hitObject.StartTime, StartTime = hitObject.StartTime,
Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH Position = ((CatchHitObject)hitObject).X * CatchPlayfield.BASE_WIDTH
}; };
} }
} }
protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter(); protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new CatchBeatmapConverter();
} }
public struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.
/// </summary> /// </summary>
private const float conversion_lenience = 2; private const float conversion_lenience = 2;
public double StartTime; public double StartTime;
public float Position; public float Position;
public bool Equals(ConvertValue other) public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(Position, other.Position, conversion_lenience); && Precision.AlmostEquals(Position, other.Position, conversion_lenience);
} }
} }

View File

@ -1,62 +1,62 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
public class TestCaseAutoJuiceStream : TestCasePlayer public class TestCaseAutoJuiceStream : TestCasePlayer
{ {
public TestCaseAutoJuiceStream() public TestCaseAutoJuiceStream()
: base(new CatchRuleset()) : base(new CatchRuleset())
{ {
} }
protected override Beatmap CreateBeatmap(Ruleset ruleset) protected override Beatmap CreateBeatmap(Ruleset ruleset)
{ {
var beatmap = new Beatmap var beatmap = new Beatmap
{ {
BeatmapInfo = new BeatmapInfo BeatmapInfo = new BeatmapInfo
{ {
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
Ruleset = ruleset.RulesetInfo Ruleset = ruleset.RulesetInfo
} }
}; };
for (int i = 0; i < 100; i++) for (int i = 0; i < 100; i++)
{ {
float width = (i % 10 + 1) / 20f; float width = (i % 10 + 1) / 20f;
beatmap.HitObjects.Add(new JuiceStream beatmap.HitObjects.Add(new JuiceStream
{ {
X = 0.5f - width / 2, X = 0.5f - width / 2,
ControlPoints = new List<Vector2> ControlPoints = new List<Vector2>
{ {
Vector2.Zero, Vector2.Zero,
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
}, },
CurveType = CurveType.Linear, CurveType = CurveType.Linear,
Distance = width * CatchPlayfield.BASE_WIDTH, Distance = width * CatchPlayfield.BASE_WIDTH,
StartTime = i * 2000, StartTime = i * 2000,
NewCombo = i % 8 == 0 NewCombo = i % 8 == 0
}); });
} }
return beatmap; return beatmap;
} }
protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset) protected override Player CreatePlayer(WorkingBeatmap beatmap, Ruleset ruleset)
{ {
beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }); beatmap.Mods.Value = beatmap.Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() });
return base.CreatePlayer(beatmap, ruleset); return base.CreatePlayer(beatmap, ruleset);
} }
} }
} }

View File

@ -1,47 +1,47 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer public class TestCaseBananaShower : Game.Tests.Visual.TestCasePlayer
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(BananaShower), typeof(BananaShower),
typeof(DrawableBananaShower), typeof(DrawableBananaShower),
typeof(CatchRuleset), typeof(CatchRuleset),
typeof(CatchRulesetContainer), typeof(CatchRulesetContainer),
}; };
public TestCaseBananaShower() public TestCaseBananaShower()
: base(new CatchRuleset()) : base(new CatchRuleset())
{ {
} }
protected override Beatmap CreateBeatmap(Ruleset ruleset) protected override Beatmap CreateBeatmap(Ruleset ruleset)
{ {
var beatmap = new Beatmap var beatmap = new Beatmap
{ {
BeatmapInfo = new BeatmapInfo BeatmapInfo = new BeatmapInfo
{ {
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset.RulesetInfo Ruleset = ruleset.RulesetInfo
} }
}; };
beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true }); beatmap.HitObjects.Add(new BananaShower { StartTime = 200, Duration = 5000, NewCombo = true });
return beatmap; return beatmap;
} }
} }
} }

View File

@ -1,15 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework; using NUnit.Framework;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer public class TestCaseCatchPlayer : Game.Tests.Visual.TestCasePlayer
{ {
public TestCaseCatchPlayer() : base(new CatchRuleset()) public TestCaseCatchPlayer() : base(new CatchRuleset())
{ {
} }
} }
} }

View File

@ -1,36 +1,36 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
{ {
public TestCaseCatchStacker() public TestCaseCatchStacker()
: base(new CatchRuleset()) : base(new CatchRuleset())
{ {
} }
protected override Beatmap CreateBeatmap(Ruleset ruleset) protected override Beatmap CreateBeatmap(Ruleset ruleset)
{ {
var beatmap = new Beatmap var beatmap = new Beatmap
{ {
BeatmapInfo = new BeatmapInfo BeatmapInfo = new BeatmapInfo
{ {
BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 }, BaseDifficulty = new BeatmapDifficulty { CircleSize = 6 },
Ruleset = ruleset.RulesetInfo Ruleset = ruleset.RulesetInfo
} }
}; };
for (int i = 0; i < 512; i++) for (int i = 0; i < 512; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap; return beatmap;
} }
} }
} }

View File

@ -1,61 +1,61 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseCatcherArea : OsuTestCase public class TestCaseCatcherArea : OsuTestCase
{ {
private RulesetInfo catchRuleset; private RulesetInfo catchRuleset;
private TestCatcherArea catcherArea; private TestCatcherArea catcherArea;
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(CatcherArea), typeof(CatcherArea),
}; };
public TestCaseCatcherArea() public TestCaseCatcherArea()
{ {
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher); AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t)); AddToggleStep("Hyperdash", t => catcherArea.ToggleHyperDash(t));
} }
private void createCatcher(float size) private void createCatcher(float size)
{ {
Child = new CatchInputManager(catchRuleset) Child = new CatchInputManager(catchRuleset)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) Child = catcherArea = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.BottomLeft Origin = Anchor.BottomLeft
}, },
}; };
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets) private void load(RulesetStore rulesets)
{ {
catchRuleset = rulesets.GetRuleset(2); catchRuleset = rulesets.GetRuleset(2);
} }
private class TestCatcherArea : CatcherArea private class TestCatcherArea : CatcherArea
{ {
public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) public TestCatcherArea(BeatmapDifficulty beatmapDifficulty)
: base(beatmapDifficulty) : base(beatmapDifficulty)
{ {
} }
public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1; public void ToggleHyperDash(bool status) => MovableCatcher.HyperDashModifier = status ? 2 : 1;
} }
} }
} }

View File

@ -1,74 +1,74 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseFruitObjects : OsuTestCase public class TestCaseFruitObjects : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(CatchHitObject), typeof(CatchHitObject),
typeof(Fruit), typeof(Fruit),
typeof(Droplet), typeof(Droplet),
typeof(DrawableCatchHitObject), typeof(DrawableCatchHitObject),
typeof(DrawableFruit), typeof(DrawableFruit),
typeof(DrawableDroplet), typeof(DrawableDroplet),
typeof(Pulp), typeof(Pulp),
}; };
public TestCaseFruitObjects() public TestCaseFruitObjects()
{ {
Add(new GridContainer Add(new GridContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Content = new[] Content = new[]
{ {
new Drawable[] new Drawable[]
{ {
createDrawable(0), createDrawable(0),
createDrawable(1), createDrawable(1),
createDrawable(2), createDrawable(2),
}, },
new Drawable[] new Drawable[]
{ {
createDrawable(3), createDrawable(3),
createDrawable(4), createDrawable(4),
createDrawable(5), createDrawable(5),
}, },
} }
}); });
} }
private DrawableFruit createDrawable(int index) private DrawableFruit createDrawable(int index)
{ {
var fruit = new Fruit var fruit = new Fruit
{ {
StartTime = 1000000000000, StartTime = 1000000000000,
IndexInBeatmap = index, IndexInBeatmap = index,
Scale = 1.5f, Scale = 1.5f,
}; };
return new DrawableFruit(fruit) return new DrawableFruit(fruit)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Position = Vector2.Zero, Position = Vector2.Zero,
Alpha = 1, Alpha = 1,
LifetimeStart = double.NegativeInfinity, LifetimeStart = double.NegativeInfinity,
LifetimeEnd = double.PositiveInfinity, LifetimeEnd = double.PositiveInfinity,
}; };
} }
} }
} }

View File

@ -1,30 +1,30 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer public class TestCaseHyperdash : Game.Tests.Visual.TestCasePlayer
{ {
public TestCaseHyperdash() public TestCaseHyperdash()
: base(new CatchRuleset()) : base(new CatchRuleset())
{ {
} }
protected override Beatmap CreateBeatmap(Ruleset ruleset) protected override Beatmap CreateBeatmap(Ruleset ruleset)
{ {
var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } }; var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset.RulesetInfo } };
for (int i = 0; i < 512; i++) for (int i = 0; i < 512; i++)
if (i % 5 < 3) if (i % 5 < 3)
beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 }); beatmap.HitObjects.Add(new Fruit { X = i % 10 < 5 ? 0.02f : 0.98f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap; return beatmap;
} }
} }
} }

View File

@ -1,16 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework; using NUnit.Framework;
namespace osu.Game.Rulesets.Catch.Tests namespace osu.Game.Rulesets.Catch.Tests
{ {
[TestFixture] [TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{ {
public TestCasePerformancePoints() public TestCasePerformancePoints()
: base(new CatchRuleset()) : base(new CatchRuleset())
{ {
} }
} }
} }

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,68 +1,68 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Catch.Beatmaps namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
public class CatchBeatmapConverter : BeatmapConverter<CatchHitObject> public class CatchBeatmapConverter : BeatmapConverter<CatchHitObject>
{ {
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, Beatmap beatmap) protected override IEnumerable<CatchHitObject> ConvertHitObject(HitObject obj, Beatmap beatmap)
{ {
var curveData = obj as IHasCurve; var curveData = obj as IHasCurve;
var positionData = obj as IHasXPosition; var positionData = obj as IHasXPosition;
var comboData = obj as IHasCombo; var comboData = obj as IHasCombo;
var endTime = obj as IHasEndTime; var endTime = obj as IHasEndTime;
if (positionData == null) if (positionData == null)
yield break; yield break;
if (curveData != null) if (curveData != null)
{ {
yield return new JuiceStream yield return new JuiceStream
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
ControlPoints = curveData.ControlPoints, ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType, CurveType = curveData.CurveType,
Distance = curveData.Distance, Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples, RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount, RepeatCount = curveData.RepeatCount,
X = positionData.X / CatchPlayfield.BASE_WIDTH, X = positionData.X / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false
}; };
yield break; yield break;
} }
if (endTime != null) if (endTime != null)
{ {
yield return new BananaShower yield return new BananaShower
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
Duration = endTime.Duration, Duration = endTime.Duration,
NewCombo = comboData?.NewCombo ?? false NewCombo = comboData?.NewCombo ?? false
}; };
yield break; yield break;
} }
yield return new Fruit yield return new Fruit
{ {
StartTime = obj.StartTime, StartTime = obj.StartTime,
Samples = obj.Samples, Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false, NewCombo = comboData?.NewCombo ?? false,
X = positionData.X / CatchPlayfield.BASE_WIDTH X = positionData.X / CatchPlayfield.BASE_WIDTH
}; };
} }
} }
} }

View File

@ -1,72 +1,72 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Catch.Beatmaps namespace osu.Game.Rulesets.Catch.Beatmaps
{ {
public class CatchBeatmapProcessor : BeatmapProcessor<CatchHitObject> public class CatchBeatmapProcessor : BeatmapProcessor<CatchHitObject>
{ {
public override void PostProcess(Beatmap<CatchHitObject> beatmap) public override void PostProcess(Beatmap<CatchHitObject> beatmap)
{ {
initialiseHyperDash(beatmap.HitObjects); initialiseHyperDash(beatmap.HitObjects);
base.PostProcess(beatmap); base.PostProcess(beatmap);
int index = 0; int index = 0;
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
obj.IndexInBeatmap = index++; obj.IndexInBeatmap = index++;
} }
private void initialiseHyperDash(List<CatchHitObject> objects) private void initialiseHyperDash(List<CatchHitObject> objects)
{ {
// todo: add difficulty adjust. // todo: add difficulty adjust.
double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2; double halfCatcherWidth = CatcherArea.CATCHER_SIZE * (objects.FirstOrDefault()?.Scale ?? 1) / CatchPlayfield.BASE_WIDTH / 2;
int lastDirection = 0; int lastDirection = 0;
double lastExcess = halfCatcherWidth; double lastExcess = halfCatcherWidth;
int objCount = objects.Count; int objCount = objects.Count;
for (int i = 0; i < objCount - 1; i++) for (int i = 0; i < objCount - 1; i++)
{ {
CatchHitObject currentObject = objects[i]; CatchHitObject currentObject = objects[i];
// not needed? // not needed?
// if (currentObject is TinyDroplet) continue; // if (currentObject is TinyDroplet) continue;
CatchHitObject nextObject = objects[i + 1]; CatchHitObject nextObject = objects[i + 1];
// while (nextObject is TinyDroplet) // while (nextObject is TinyDroplet)
// { // {
// if (++i == objCount - 1) break; // if (++i == objCount - 1) break;
// nextObject = objects[i + 1]; // nextObject = objects[i + 1];
// } // }
int thisDirection = nextObject.X > currentObject.X ? 1 : -1; int thisDirection = nextObject.X > currentObject.X ? 1 : -1;
double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4; double timeToNext = nextObject.StartTime - ((currentObject as IHasEndTime)?.EndTime ?? currentObject.StartTime) - 4;
double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth); double distanceToNext = Math.Abs(nextObject.X - currentObject.X) - (lastDirection == thisDirection ? lastExcess : halfCatcherWidth);
if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext) if (timeToNext * CatcherArea.Catcher.BASE_SPEED < distanceToNext)
{ {
currentObject.HyperDashTarget = nextObject; currentObject.HyperDashTarget = nextObject;
lastExcess = halfCatcherWidth; lastExcess = halfCatcherWidth;
} }
else else
{ {
//currentObject.DistanceToHyperDash = timeToNext - distanceToNext; //currentObject.DistanceToHyperDash = timeToNext - distanceToNext;
lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth); lastExcess = MathHelper.Clamp(timeToNext - distanceToNext, 0, halfCatcherWidth);
} }
lastDirection = thisDirection; lastDirection = thisDirection;
} }
} }
} }
} }

View File

@ -1,21 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic; using System.Collections.Generic;
namespace osu.Game.Rulesets.Catch namespace osu.Game.Rulesets.Catch
{ {
public class CatchDifficultyCalculator : DifficultyCalculator<CatchHitObject> public class CatchDifficultyCalculator : DifficultyCalculator<CatchHitObject>
{ {
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap) public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{ {
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0; public override double Calculate(Dictionary<string, double> categoryDifficulty = null) => 0;
protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter(); protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter(Beatmap beatmap) => new CatchBeatmapConverter();
} }
} }

View File

@ -1,27 +1,27 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch namespace osu.Game.Rulesets.Catch
{ {
public class CatchInputManager : RulesetInputManager<CatchAction> public class CatchInputManager : RulesetInputManager<CatchAction>
{ {
public CatchInputManager(RulesetInfo ruleset) public CatchInputManager(RulesetInfo ruleset)
: base(ruleset, 0, SimultaneousBindingMode.Unique) : base(ruleset, 0, SimultaneousBindingMode.Unique)
{ {
} }
} }
public enum CatchAction public enum CatchAction
{ {
[Description("Move left")] [Description("Move left")]
MoveLeft, MoveLeft,
[Description("Move right")] [Description("Move right")]
MoveRight, MoveRight,
[Description("Engage dash")] [Description("Engage dash")]
Dash, Dash,
} }
} }

View File

@ -1,113 +1,152 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Rulesets.Catch
{ namespace osu.Game.Rulesets.Catch
public class CatchRuleset : Ruleset {
{ public class CatchRuleset : Ruleset
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset); {
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new CatchRulesetContainer(this, beatmap, isForCurrentRuleset);
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{ public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
new KeyBinding(InputKey.Z, CatchAction.MoveLeft), {
new KeyBinding(InputKey.Left, CatchAction.MoveLeft), new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
new KeyBinding(InputKey.X, CatchAction.MoveRight), new KeyBinding(InputKey.Left, CatchAction.MoveLeft),
new KeyBinding(InputKey.Right, CatchAction.MoveRight), new KeyBinding(InputKey.X, CatchAction.MoveRight),
new KeyBinding(InputKey.Shift, CatchAction.Dash), new KeyBinding(InputKey.Right, CatchAction.MoveRight),
new KeyBinding(InputKey.Shift, CatchAction.Dash), new KeyBinding(InputKey.Shift, CatchAction.Dash),
}; new KeyBinding(InputKey.Shift, CatchAction.Dash),
};
public override IEnumerable<Mod> GetModsFor(ModType type)
{ public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
switch (type) {
{ if (mods.HasFlag(LegacyMods.Nightcore))
case ModType.DifficultyReduction: yield return new CatchModNightcore();
return new Mod[] else if (mods.HasFlag(LegacyMods.DoubleTime))
{ yield return new CatchModDoubleTime();
new CatchModEasy(),
new CatchModNoFail(), if (mods.HasFlag(LegacyMods.Autoplay))
new MultiMod yield return new CatchModAutoplay();
{
Mods = new Mod[] if (mods.HasFlag(LegacyMods.Easy))
{ yield return new CatchModEasy();
new CatchModHalfTime(),
new CatchModDaycore(), if (mods.HasFlag(LegacyMods.Flashlight))
}, yield return new CatchModFlashlight();
},
}; if (mods.HasFlag(LegacyMods.HalfTime))
yield return new CatchModHalfTime();
case ModType.DifficultyIncrease:
return new Mod[] if (mods.HasFlag(LegacyMods.HardRock))
{ yield return new CatchModHardRock();
new CatchModHardRock(),
new MultiMod if (mods.HasFlag(LegacyMods.Hidden))
{ yield return new CatchModHidden();
Mods = new Mod[]
{ if (mods.HasFlag(LegacyMods.NoFail))
new CatchModSuddenDeath(), yield return new CatchModNoFail();
new CatchModPerfect(),
}, if (mods.HasFlag(LegacyMods.Perfect))
}, yield return new CatchModPerfect();
new MultiMod
{ if (mods.HasFlag(LegacyMods.Relax))
Mods = new Mod[] yield return new CatchModRelax();
{
new CatchModDoubleTime(), if (mods.HasFlag(LegacyMods.SuddenDeath))
new CatchModNightcore(), yield return new CatchModSuddenDeath();
}, }
},
new CatchModHidden(), public override IEnumerable<Mod> GetModsFor(ModType type)
new CatchModFlashlight(), {
}; switch (type)
{
case ModType.Special: case ModType.DifficultyReduction:
return new Mod[] return new Mod[]
{ {
new CatchModRelax(), new CatchModEasy(),
null, new CatchModNoFail(),
null, new MultiMod
new MultiMod {
{ Mods = new Mod[]
Mods = new Mod[] {
{ new CatchModHalfTime(),
new CatchModAutoplay(), new CatchModDaycore(),
new ModCinema(), },
}, },
}, };
};
case ModType.DifficultyIncrease:
default: return new Mod[]
return new Mod[] { }; {
} new CatchModHardRock(),
} new MultiMod
{
public override string Description => "osu!catch"; Mods = new Mod[]
{
public override string ShortName => "fruits"; new CatchModSuddenDeath(),
new CatchModPerfect(),
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o }; },
},
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap); new MultiMod
{
public override int? LegacyID => 2; Mods = new Mod[]
{
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame(); new CatchModDoubleTime(),
new CatchModNightcore(),
public CatchRuleset(RulesetInfo rulesetInfo = null) },
: base(rulesetInfo) },
{ new CatchModHidden(),
} new CatchModFlashlight(),
} };
}
case ModType.Special:
return new Mod[]
{
new CatchModRelax(),
null,
null,
new MultiMod
{
Mods = new Mod[]
{
new CatchModAutoplay(),
new ModCinema(),
},
},
};
default:
return new Mod[] { };
}
}
public override string Description => "osu!catch";
public override string ShortName => "fruits";
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new CatchDifficultyCalculator(beatmap);
public override int? LegacyID => 2;
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new CatchReplayFrame();
public CatchRuleset(RulesetInfo rulesetInfo = null)
: base(rulesetInfo)
{
}
}
}

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
namespace osu.Game.Rulesets.Catch.Judgements namespace osu.Game.Rulesets.Catch.Judgements
{ {
public class CatchJudgement : Judgement public class CatchJudgement : Judgement
{ {
// todo: wangs // todo: wangs
} }
} }

View File

@ -1,24 +1,24 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModAutoplay : ModAutoplay<CatchHitObject> public class CatchModAutoplay : ModAutoplay<CatchHitObject>
{ {
protected override Score CreateReplayScore(Beatmap<CatchHitObject> beatmap) protected override Score CreateReplayScore(Beatmap<CatchHitObject> beatmap)
{ {
return new Score return new Score
{ {
User = new User { Username = "osu!salad!" }, User = new User { Username = "osu!salad!" },
Replay = new CatchAutoGenerator(beatmap).Generate(), Replay = new CatchAutoGenerator(beatmap).Generate(),
}; };
} }
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModDaycore : ModDaycore public class CatchModDaycore : ModDaycore
{ {
public override double ScoreMultiplier => 0.3; public override double ScoreMultiplier => 0.3;
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModDoubleTime : ModDoubleTime public class CatchModDoubleTime : ModDoubleTime
{ {
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModEasy : ModEasy public class CatchModEasy : ModEasy
{ {
public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!"; public override string Description => @"Larger fruits, more forgiving HP drain, less accuracy required, and three lives!";
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModFlashlight : ModFlashlight public class CatchModFlashlight : ModFlashlight
{ {
public override double ScoreMultiplier => 1.12; public override double ScoreMultiplier => 1.12;
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModHalfTime : ModHalfTime public class CatchModHalfTime : ModHalfTime
{ {
public override double ScoreMultiplier => 0.3; public override double ScoreMultiplier => 0.3;
} }
} }

View File

@ -1,88 +1,88 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using System; using System;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModHardRock : ModHardRock, IApplicableToHitObject<CatchHitObject> public class CatchModHardRock : ModHardRock, IApplicableToHitObject<CatchHitObject>
{ {
public override double ScoreMultiplier => 1.12; public override double ScoreMultiplier => 1.12;
public override bool Ranked => true; public override bool Ranked => true;
private float lastStartX; private float lastStartX;
private int lastStartTime; private int lastStartTime;
public void ApplyToHitObject(CatchHitObject hitObject) public void ApplyToHitObject(CatchHitObject hitObject)
{ {
float position = hitObject.X; float position = hitObject.X;
int startTime = (int)hitObject.StartTime; int startTime = (int)hitObject.StartTime;
if (lastStartX == 0) if (lastStartX == 0)
{ {
lastStartX = position; lastStartX = position;
lastStartTime = startTime; lastStartTime = startTime;
return; return;
} }
float diff = lastStartX - position; float diff = lastStartX - position;
int timeDiff = startTime - lastStartTime; int timeDiff = startTime - lastStartTime;
if (timeDiff > 1000) if (timeDiff > 1000)
{ {
lastStartX = position; lastStartX = position;
lastStartTime = startTime; lastStartTime = startTime;
return; return;
} }
if (diff == 0) if (diff == 0)
{ {
bool right = RNG.NextBool(); bool right = RNG.NextBool();
float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH; float rand = Math.Min(20, (float)RNG.NextDouble(0, timeDiff / 4d)) / CatchPlayfield.BASE_WIDTH;
if (right) if (right)
{ {
if (position + rand <= 1) if (position + rand <= 1)
position += rand; position += rand;
else else
position -= rand; position -= rand;
} }
else else
{ {
if (position - rand >= 0) if (position - rand >= 0)
position -= rand; position -= rand;
else else
position += rand; position += rand;
} }
hitObject.X = position; hitObject.X = position;
return; return;
} }
if (Math.Abs(diff) < timeDiff / 3d) if (Math.Abs(diff) < timeDiff / 3d)
{ {
if (diff > 0) if (diff > 0)
{ {
if (position - diff > 0) if (position - diff > 0)
position -= diff; position -= diff;
} }
else else
{ {
if (position - diff < 1) if (position - diff < 1)
position -= diff; position -= diff;
} }
} }
hitObject.X = position; hitObject.X = position;
lastStartX = position; lastStartX = position;
lastStartTime = startTime; lastStartTime = startTime;
} }
} }
} }

View File

@ -1,13 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModHidden : ModHidden public class CatchModHidden : ModHidden
{ {
public override string Description => @"Play with fading fruits."; public override string Description => @"Play with fading fruits.";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModNightcore : ModNightcore public class CatchModNightcore : ModNightcore
{ {
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
} }
} }

View File

@ -1,11 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModNoFail : ModNoFail public class CatchModNoFail : ModNoFail
{ {
} }
} }

View File

@ -1,11 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModPerfect : ModPerfect public class CatchModPerfect : ModPerfect
{ {
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModRelax : ModRelax public class CatchModRelax : ModRelax
{ {
public override string Description => @"Use the mouse to control the catcher."; public override string Description => @"Use the mouse to control the catcher.";
} }
} }

View File

@ -1,11 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Catch.Mods namespace osu.Game.Rulesets.Catch.Mods
{ {
public class CatchModSuddenDeath : ModSuddenDeath public class CatchModSuddenDeath : ModSuddenDeath
{ {
} }
} }

View File

@ -1,48 +1,48 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class BananaShower : CatchHitObject, IHasEndTime public class BananaShower : CatchHitObject, IHasEndTime
{ {
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
public override bool LastInCombo => true; public override bool LastInCombo => true;
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
createBananas(); createBananas();
} }
private void createBananas() private void createBananas()
{ {
double spacing = Duration; double spacing = Duration;
while (spacing > 100) while (spacing > 100)
spacing /= 2; spacing /= 2;
if (spacing <= 0) if (spacing <= 0)
return; return;
for (double i = StartTime; i <= EndTime; i += spacing) for (double i = StartTime; i <= EndTime; i += spacing)
AddNested(new Banana AddNested(new Banana
{ {
Samples = Samples, Samples = Samples,
StartTime = i, StartTime = i,
X = RNG.NextSingle() X = RNG.NextSingle()
}); });
} }
public double EndTime => StartTime + Duration; public double EndTime => StartTime + Duration;
public double Duration { get; set; } public double Duration { get; set; }
public class Banana : Fruit public class Banana : Fruit
{ {
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
} }
} }
} }

View File

@ -1,60 +1,60 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation
{ {
public const double OBJECT_RADIUS = 44; public const double OBJECT_RADIUS = 44;
public float X { get; set; } public float X { get; set; }
public int IndexInBeatmap { get; set; } public int IndexInBeatmap { get; set; }
public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4); public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
public virtual bool NewCombo { get; set; } public virtual bool NewCombo { get; set; }
public int IndexInCurrentCombo { get; set; } public int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; } public int ComboIndex { get; set; }
/// <summary> /// <summary>
/// The next fruit starts a new combo. Used for explodey. /// The next fruit starts a new combo. Used for explodey.
/// </summary> /// </summary>
public virtual bool LastInCombo { get; set; } public virtual bool LastInCombo { get; set; }
public float Scale { get; set; } = 1; public float Scale { get; set; } = 1;
/// <summary> /// <summary>
/// Whether this fruit can initiate a hyperdash. /// Whether this fruit can initiate a hyperdash.
/// </summary> /// </summary>
public bool HyperDash => HyperDashTarget != null; public bool HyperDash => HyperDashTarget != null;
/// <summary> /// <summary>
/// The target fruit if we are to initiate a hyperdash. /// The target fruit if we are to initiate a hyperdash.
/// </summary> /// </summary>
public CatchHitObject HyperDashTarget; public CatchHitObject HyperDashTarget;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5; Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
} }
} }
public enum FruitVisualRepresentation public enum FruitVisualRepresentation
{ {
Pear, Pear,
Grape, Grape,
Raspberry, Raspberry,
Pineapple, Pineapple,
Banana // banananananannaanana Banana // banananananannaanana
} }
} }

View File

@ -1,44 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower> public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
{ {
private readonly Container bananaContainer; private readonly Container bananaContainer;
public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation = null) public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation = null)
: base(s) : base(s)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Origin = Anchor.BottomLeft; Origin = Anchor.BottomLeft;
X = 0; X = 0;
InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both }; InternalChild = bananaContainer = new Container { RelativeSizeAxes = Axes.Both };
foreach (var b in s.NestedHitObjects.Cast<BananaShower.Banana>()) foreach (var b in s.NestedHitObjects.Cast<BananaShower.Banana>())
AddNested(getVisualRepresentation?.Invoke(b)); AddNested(getVisualRepresentation?.Invoke(b));
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {
if (timeOffset >= 0) if (timeOffset >= 0)
AddJudgement(new Judgement { Result = NestedHitObjects.Cast<DrawableCatchHitObject>().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss }); AddJudgement(new Judgement { Result = NestedHitObjects.Cast<DrawableCatchHitObject>().Any(n => n.Judgements.Any(j => j.IsHit)) ? HitResult.Perfect : HitResult.Miss });
} }
protected override void AddNested(DrawableHitObject h) protected override void AddNested(DrawableHitObject h)
{ {
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false; ((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
bananaContainer.Add(h); bananaContainer.Add(h);
base.AddNested(h); base.AddNested(h);
} }
} }
} }

View File

@ -1,93 +1,93 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning; using osu.Game.Skinning;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public abstract class PalpableCatchHitObject<TObject> : DrawableCatchHitObject<TObject> public abstract class PalpableCatchHitObject<TObject> : DrawableCatchHitObject<TObject>
where TObject : CatchHitObject where TObject : CatchHitObject
{ {
public override bool CanBePlated => true; public override bool CanBePlated => true;
protected PalpableCatchHitObject(TObject hitObject) protected PalpableCatchHitObject(TObject hitObject)
: base(hitObject) : base(hitObject)
{ {
Scale = new Vector2(HitObject.Scale); Scale = new Vector2(HitObject.Scale);
} }
} }
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
where TObject : CatchHitObject where TObject : CatchHitObject
{ {
public new TObject HitObject; public new TObject HitObject;
protected DrawableCatchHitObject(TObject hitObject) protected DrawableCatchHitObject(TObject hitObject)
: base(hitObject) : base(hitObject)
{ {
HitObject = hitObject; HitObject = hitObject;
Anchor = Anchor.BottomLeft; Anchor = Anchor.BottomLeft;
} }
} }
public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject> public abstract class DrawableCatchHitObject : DrawableHitObject<CatchHitObject>
{ {
public virtual bool CanBePlated => false; public virtual bool CanBePlated => false;
protected DrawableCatchHitObject(CatchHitObject hitObject) protected DrawableCatchHitObject(CatchHitObject hitObject)
: base(hitObject) : base(hitObject)
{ {
RelativePositionAxes = Axes.X; RelativePositionAxes = Axes.X;
X = hitObject.X; X = hitObject.X;
} }
public Func<CatchHitObject, bool> CheckPosition; public Func<CatchHitObject, bool> CheckPosition;
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{ {
if (CheckPosition == null) return; if (CheckPosition == null) return;
if (timeOffset >= 0) if (timeOffset >= 0)
AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss }); AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
} }
protected override void SkinChanged(ISkinSource skin, bool allowFallback) protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{ {
base.SkinChanged(skin, allowFallback); base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo) if (HitObject is IHasComboInformation combo)
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White; AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
} }
private const float preempt = 1000; private const float preempt = 1000;
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)
{ {
using (BeginAbsoluteSequence(HitObject.StartTime - preempt)) using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
this.FadeIn(200); this.FadeIn(200);
var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime; var endTime = (HitObject as IHasEndTime)?.EndTime ?? HitObject.StartTime;
using (BeginAbsoluteSequence(endTime, true)) using (BeginAbsoluteSequence(endTime, true))
{ {
switch (state) switch (state)
{ {
case ArmedState.Miss: case ArmedState.Miss:
this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire(); this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out).Expire();
break; break;
case ArmedState.Hit: case ArmedState.Hit:
this.FadeOut().Expire(); this.FadeOut().Expire();
break; break;
} }
} }
} }
} }
} }

View File

@ -1,43 +1,43 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableDroplet : PalpableCatchHitObject<Droplet> public class DrawableDroplet : PalpableCatchHitObject<Droplet>
{ {
private Pulp pulp; private Pulp pulp;
public DrawableDroplet(Droplet h) public DrawableDroplet(Droplet h)
: base(h) : base(h)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4; Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4;
Masking = false; Masking = false;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
InternalChild = pulp = new Pulp InternalChild = pulp = new Pulp
{ {
Size = Size Size = Size
}; };
} }
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get { return base.AccentColour; }
set set
{ {
base.AccentColour = value; base.AccentColour = value;
pulp.AccentColour = AccentColour; pulp.AccentColour = AccentColour;
} }
} }
} }
} }

View File

@ -1,305 +1,305 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces; using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableFruit : PalpableCatchHitObject<Fruit> public class DrawableFruit : PalpableCatchHitObject<Fruit>
{ {
private Circle border; private Circle border;
public DrawableFruit(Fruit h) public DrawableFruit(Fruit h)
: base(h) : base(h)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS); Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS);
Masking = false; Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40; Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
// todo: this should come from the skin. // todo: this should come from the skin.
AccentColour = colourForRrepesentation(HitObject.VisualRepresentation); AccentColour = colourForRrepesentation(HitObject.VisualRepresentation);
InternalChildren = new[] InternalChildren = new[]
{ {
createPulp(HitObject.VisualRepresentation), createPulp(HitObject.VisualRepresentation),
border = new Circle border = new Circle
{ {
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
{ {
Hollow = !HitObject.HyperDash, Hollow = !HitObject.HyperDash,
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 4, Radius = 4,
Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f) Colour = HitObject.HyperDash ? Color4.Red : AccentColour.Darken(1).Opacity(0.6f)
}, },
Size = new Vector2(Height * 1.5f), Size = new Vector2(Height * 1.5f),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
BorderColour = Color4.White, BorderColour = Color4.White,
BorderThickness = 4f, BorderThickness = 4f,
Children = new Framework.Graphics.Drawable[] Children = new Framework.Graphics.Drawable[]
{ {
new Box new Box
{ {
AlwaysPresent = true, AlwaysPresent = true,
Colour = AccentColour, Colour = AccentColour,
Alpha = 0, Alpha = 0,
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
} }
} }
}, },
}; };
if (HitObject.HyperDash) if (HitObject.HyperDash)
{ {
AddInternal(new Pulp AddInternal(new Pulp
{ {
RelativePositionAxes = Axes.Both, RelativePositionAxes = Axes.Both,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
AccentColour = Color4.Red, AccentColour = Color4.Red,
Blending = BlendingMode.Additive, Blending = BlendingMode.Additive,
Alpha = 0.5f, Alpha = 0.5f,
Scale = new Vector2(1.333f) Scale = new Vector2(1.333f)
}); });
} }
} }
private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation) private Framework.Graphics.Drawable createPulp(FruitVisualRepresentation representation)
{ {
const float large_pulp_3 = 13f; const float large_pulp_3 = 13f;
const float distance_from_centre_3 = 0.23f; const float distance_from_centre_3 = 0.23f;
const float large_pulp_4 = large_pulp_3 * 0.925f; const float large_pulp_4 = large_pulp_3 * 0.925f;
const float distance_from_centre_4 = distance_from_centre_3 / 0.925f; const float distance_from_centre_4 = distance_from_centre_3 / 0.925f;
const float small_pulp = large_pulp_3 / 2; const float small_pulp = large_pulp_3 / 2;
Vector2 positionAt(float angle, float distance) => new Vector2( Vector2 positionAt(float angle, float distance) => new Vector2(
distance * (float)Math.Sin(angle * Math.PI / 180), distance * (float)Math.Sin(angle * Math.PI / 180),
distance * (float)Math.Cos(angle * Math.PI / 180)); distance * (float)Math.Cos(angle * Math.PI / 180));
switch (representation) switch (representation)
{ {
default: default:
return new Container(); return new Container();
case FruitVisualRepresentation.Raspberry: case FruitVisualRepresentation.Raspberry:
return new Container return new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Framework.Graphics.Drawable[] Children = new Framework.Graphics.Drawable[]
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = 0.05f, Y = 0.05f,
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
Position = positionAt(0, distance_from_centre_4), Position = positionAt(0, distance_from_centre_4),
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
Position = positionAt(90, distance_from_centre_4), Position = positionAt(90, distance_from_centre_4),
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
Position = positionAt(180, distance_from_centre_4), Position = positionAt(180, distance_from_centre_4),
}, },
new Pulp new Pulp
{ {
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
AccentColour = AccentColour, AccentColour = AccentColour,
Position = positionAt(270, distance_from_centre_4), Position = positionAt(270, distance_from_centre_4),
}, },
} }
}; };
case FruitVisualRepresentation.Pineapple: case FruitVisualRepresentation.Pineapple:
return new Container return new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Framework.Graphics.Drawable[] Children = new Framework.Graphics.Drawable[]
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = 0.1f, Y = 0.1f,
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
Position = positionAt(45, distance_from_centre_4), Position = positionAt(45, distance_from_centre_4),
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
Position = positionAt(135, distance_from_centre_4), Position = positionAt(135, distance_from_centre_4),
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
Position = positionAt(225, distance_from_centre_4), Position = positionAt(225, distance_from_centre_4),
}, },
new Pulp new Pulp
{ {
Size = new Vector2(large_pulp_4), Size = new Vector2(large_pulp_4),
AccentColour = AccentColour, AccentColour = AccentColour,
Position = positionAt(315, distance_from_centre_4), Position = positionAt(315, distance_from_centre_4),
}, },
} }
}; };
case FruitVisualRepresentation.Pear: case FruitVisualRepresentation.Pear:
return new Container return new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Framework.Graphics.Drawable[] Children = new Framework.Graphics.Drawable[]
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = -0.1f, Y = -0.1f,
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_3), Size = new Vector2(large_pulp_3),
Position = positionAt(60, distance_from_centre_3), Position = positionAt(60, distance_from_centre_3),
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_3), Size = new Vector2(large_pulp_3),
Position = positionAt(180, distance_from_centre_3), Position = positionAt(180, distance_from_centre_3),
}, },
new Pulp new Pulp
{ {
Size = new Vector2(large_pulp_3), Size = new Vector2(large_pulp_3),
AccentColour = AccentColour, AccentColour = AccentColour,
Position = positionAt(300, distance_from_centre_3), Position = positionAt(300, distance_from_centre_3),
}, },
} }
}; };
case FruitVisualRepresentation.Grape: case FruitVisualRepresentation.Grape:
return new Container return new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Framework.Graphics.Drawable[] Children = new Framework.Graphics.Drawable[]
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_3), Size = new Vector2(large_pulp_3),
Position = positionAt(0, distance_from_centre_3), Position = positionAt(0, distance_from_centre_3),
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_3), Size = new Vector2(large_pulp_3),
Position = positionAt(120, distance_from_centre_3), Position = positionAt(120, distance_from_centre_3),
}, },
new Pulp new Pulp
{ {
Size = new Vector2(large_pulp_3), Size = new Vector2(large_pulp_3),
AccentColour = AccentColour, AccentColour = AccentColour,
Position = positionAt(240, distance_from_centre_3), Position = positionAt(240, distance_from_centre_3),
}, },
} }
}; };
case FruitVisualRepresentation.Banana: case FruitVisualRepresentation.Banana:
return new Container return new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Children = new Framework.Graphics.Drawable[] Children = new Framework.Graphics.Drawable[]
{ {
new Pulp new Pulp
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(small_pulp), Size = new Vector2(small_pulp),
Y = -0.15f Y = -0.15f
}, },
new Pulp new Pulp
{ {
AccentColour = AccentColour, AccentColour = AccentColour,
Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3), Size = new Vector2(large_pulp_4 * 1.2f, large_pulp_4 * 3),
}, },
} }
}; };
} }
} }
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1); border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
} }
private Color4 colourForRrepesentation(FruitVisualRepresentation representation) private Color4 colourForRrepesentation(FruitVisualRepresentation representation)
{ {
switch (representation) switch (representation)
{ {
default: default:
case FruitVisualRepresentation.Pear: case FruitVisualRepresentation.Pear:
return new Color4(17, 136, 170, 255); return new Color4(17, 136, 170, 255);
case FruitVisualRepresentation.Grape: case FruitVisualRepresentation.Grape:
return new Color4(204, 102, 0, 255); return new Color4(204, 102, 0, 255);
case FruitVisualRepresentation.Raspberry: case FruitVisualRepresentation.Raspberry:
return new Color4(121, 9, 13, 255); return new Color4(121, 9, 13, 255);
case FruitVisualRepresentation.Pineapple: case FruitVisualRepresentation.Pineapple:
return new Color4(102, 136, 0, 255); return new Color4(102, 136, 0, 255);
case FruitVisualRepresentation.Banana: case FruitVisualRepresentation.Banana:
switch (RNG.Next(0, 3)) switch (RNG.Next(0, 3))
{ {
default: default:
return new Color4(255, 240, 0, 255); return new Color4(255, 240, 0, 255);
case 1: case 1:
return new Color4(255, 192, 0, 255); return new Color4(255, 192, 0, 255);
case 2: case 2:
return new Color4(214, 221, 28, 255); return new Color4(214, 221, 28, 255);
} }
} }
} }
} }
} }

View File

@ -1,41 +1,41 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable namespace osu.Game.Rulesets.Catch.Objects.Drawable
{ {
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream> public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
{ {
private readonly Container dropletContainer; private readonly Container dropletContainer;
public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation = null) public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation = null)
: base(s) : base(s)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Origin = Anchor.BottomLeft; Origin = Anchor.BottomLeft;
X = 0; X = 0;
InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, }; InternalChild = dropletContainer = new Container { RelativeSizeAxes = Axes.Both, };
foreach (var o in s.NestedHitObjects.Cast<CatchHitObject>()) foreach (var o in s.NestedHitObjects.Cast<CatchHitObject>())
AddNested(getVisualRepresentation?.Invoke(o)); AddNested(getVisualRepresentation?.Invoke(o));
} }
protected override bool ProvidesJudgement => false; protected override bool ProvidesJudgement => false;
protected override void AddNested(DrawableHitObject h) protected override void AddNested(DrawableHitObject h)
{ {
var catchObject = (DrawableCatchHitObject)h; var catchObject = (DrawableCatchHitObject)h;
catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false; catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
dropletContainer.Add(h); dropletContainer.Add(h);
base.AddNested(h); base.AddNested(h);
} }
} }
} }

View File

@ -1,42 +1,42 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
{ {
public class Pulp : Circle, IHasAccentColour public class Pulp : Circle, IHasAccentColour
{ {
public Pulp() public Pulp()
{ {
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
Blending = BlendingMode.Additive; Blending = BlendingMode.Additive;
Colour = Color4.White.Opacity(0.9f); Colour = Color4.White.Opacity(0.9f);
} }
private Color4 accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour; } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
{ {
Type = EdgeEffectType.Glow, Type = EdgeEffectType.Glow,
Radius = 8, Radius = 8,
Colour = accentColour.Darken(0.2f).Opacity(0.75f) Colour = accentColour.Darken(0.2f).Opacity(0.75f)
}; };
} }
} }
} }
} }

View File

@ -1,9 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class Droplet : CatchHitObject public class Droplet : CatchHitObject
{ {
} }
} }

View File

@ -1,9 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class Fruit : CatchHitObject public class Fruit : CatchHitObject
{ {
} }
} }

View File

@ -1,157 +1,157 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class JuiceStream : CatchHitObject, IHasCurve public class JuiceStream : CatchHitObject, IHasCurve
{ {
/// <summary> /// <summary>
/// Positional distance that results in a duration of one second, before any speed adjustments. /// Positional distance that results in a duration of one second, before any speed adjustments.
/// </summary> /// </summary>
private const float base_scoring_distance = 100; private const float base_scoring_distance = 100;
public int RepeatCount { get; set; } public int RepeatCount { get; set; }
public double Velocity; public double Velocity;
public double TickDistance; public double TickDistance;
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{ {
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime); DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier; double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength; Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate; TickDistance = scoringDistance / difficulty.SliderTickRate;
} }
protected override void CreateNestedHitObjects() protected override void CreateNestedHitObjects()
{ {
base.CreateNestedHitObjects(); base.CreateNestedHitObjects();
createTicks(); createTicks();
} }
private void createTicks() private void createTicks()
{ {
if (TickDistance == 0) if (TickDistance == 0)
return; return;
var length = Curve.Distance; var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length); var tickDistance = Math.Min(TickDistance, length);
var spanDuration = length / Velocity; var spanDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01; var minDistanceFromEnd = Velocity * 0.01;
AddNested(new Fruit AddNested(new Fruit
{ {
Samples = Samples, Samples = Samples,
StartTime = StartTime, StartTime = StartTime,
X = X X = X
}); });
double lastDropletTime = StartTime; double lastDropletTime = StartTime;
for (int span = 0; span < this.SpanCount(); span++) for (int span = 0; span < this.SpanCount(); span++)
{ {
var spanStartTime = StartTime + span * spanDuration; var spanStartTime = StartTime + span * spanDuration;
var reversed = span % 2 == 1; var reversed = span % 2 == 1;
for (double d = 0; d <= length; d += tickDistance) for (double d = 0; d <= length; d += tickDistance)
{ {
var timeProgress = d / length; var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress; var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
double time = spanStartTime + timeProgress * spanDuration; double time = spanStartTime + timeProgress * spanDuration;
double tinyTickInterval = time - lastDropletTime; double tinyTickInterval = time - lastDropletTime;
while (tinyTickInterval > 100) while (tinyTickInterval > 100)
tinyTickInterval /= 2; tinyTickInterval /= 2;
for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval) for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval)
{ {
double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration; double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration;
AddNested(new TinyDroplet AddNested(new TinyDroplet
{ {
StartTime = t, StartTime = t,
X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH, X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
Name = @"slidertick", Name = @"slidertick",
Volume = s.Volume Volume = s.Volume
})) }))
}); });
} }
if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd) if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd)
{ {
AddNested(new Droplet AddNested(new Droplet
{ {
StartTime = time, StartTime = time,
X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH, X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{ {
Bank = s.Bank, Bank = s.Bank,
Name = @"slidertick", Name = @"slidertick",
Volume = s.Volume Volume = s.Volume
})) }))
}); });
} }
lastDropletTime = time; lastDropletTime = time;
} }
AddNested(new Fruit AddNested(new Fruit
{ {
Samples = Samples, Samples = Samples,
StartTime = spanStartTime + spanDuration, StartTime = spanStartTime + spanDuration,
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
}); });
} }
} }
public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Curve.Distance / Velocity;
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
public double Distance public double Distance
{ {
get { return Curve.Distance; } get { return Curve.Distance; }
set { Curve.Distance = value; } set { Curve.Distance = value; }
} }
public SliderCurve Curve { get; } = new SliderCurve(); public SliderCurve Curve { get; } = new SliderCurve();
public List<Vector2> ControlPoints public List<Vector2> ControlPoints
{ {
get { return Curve.ControlPoints; } get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; } set { Curve.ControlPoints = value; }
} }
public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>(); public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public CurveType CurveType public CurveType CurveType
{ {
get { return Curve.CurveType; } get { return Curve.CurveType; }
set { Curve.CurveType = value; } set { Curve.CurveType = value; }
} }
} }
} }

View File

@ -1,9 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Catch.Objects namespace osu.Game.Rulesets.Catch.Objects
{ {
public class TinyDroplet : Droplet public class TinyDroplet : Droplet
{ {
} }
} }

View File

@ -1,11 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
// We publish our internal attributes to other sub-projects of the framework. // We publish our internal attributes to other sub-projects of the framework.
// Note, that we omit visual tests as they are meant to test the framework // Note, that we omit visual tests as they are meant to test the framework
// behavior "in the wild". // behavior "in the wild".
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")]
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")] [assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")]

View File

@ -1,121 +1,121 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Rulesets.Catch.Replays namespace osu.Game.Rulesets.Catch.Replays
{ {
internal class CatchAutoGenerator : AutoGenerator<CatchHitObject> internal class CatchAutoGenerator : AutoGenerator<CatchHitObject>
{ {
public const double RELEASE_DELAY = 20; public const double RELEASE_DELAY = 20;
public CatchAutoGenerator(Beatmap<CatchHitObject> beatmap) public CatchAutoGenerator(Beatmap<CatchHitObject> beatmap)
: base(beatmap) : base(beatmap)
{ {
Replay = new Replay { User = new User { Username = @"Autoplay" } }; Replay = new Replay { User = new User { Username = @"Autoplay" } };
} }
protected Replay Replay; protected Replay Replay;
public override Replay Generate() public override Replay Generate()
{ {
// todo: add support for HT DT // todo: add support for HT DT
const double dash_speed = CatcherArea.Catcher.BASE_SPEED; const double dash_speed = CatcherArea.Catcher.BASE_SPEED;
const double movement_speed = dash_speed / 2; const double movement_speed = dash_speed / 2;
float lastPosition = 0.5f; float lastPosition = 0.5f;
double lastTime = 0; double lastTime = 0;
// Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled // Todo: Realistically this shouldn't be needed, but the first frame is skipped with the way replays are currently handled
Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition)); Replay.Frames.Add(new CatchReplayFrame(-100000, lastPosition));
void moveToNext(CatchHitObject h) void moveToNext(CatchHitObject h)
{ {
float positionChange = Math.Abs(lastPosition - h.X); float positionChange = Math.Abs(lastPosition - h.X);
double timeAvailable = h.StartTime - lastTime; double timeAvailable = h.StartTime - lastTime;
//So we can either make it there without a dash or not. //So we can either make it there without a dash or not.
double speedRequired = positionChange / timeAvailable; double speedRequired = positionChange / timeAvailable;
bool dashRequired = speedRequired > movement_speed && h.StartTime != 0; bool dashRequired = speedRequired > movement_speed && h.StartTime != 0;
// todo: get correct catcher size, based on difficulty CS. // todo: get correct catcher size, based on difficulty CS.
const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f; const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f;
if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X)
{ {
//we are already in the correct range. //we are already in the correct range.
lastTime = h.StartTime; lastTime = h.StartTime;
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, lastPosition));
return; return;
} }
if (h is BananaShower.Banana) if (h is BananaShower.Banana)
{ {
// auto bananas unrealistically warp to catch 100% combo. // auto bananas unrealistically warp to catch 100% combo.
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
} }
else if (h.HyperDash) else if (h.HyperDash)
{ {
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable, lastPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
} }
else if (dashRequired) else if (dashRequired)
{ {
//we do a movement in two parts - the dash part then the normal part... //we do a movement in two parts - the dash part then the normal part...
double timeAtNormalSpeed = positionChange / movement_speed; double timeAtNormalSpeed = positionChange / movement_speed;
double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable; double timeWeNeedToSave = timeAtNormalSpeed - timeAvailable;
double timeAtDashSpeed = timeWeNeedToSave / 2; double timeAtDashSpeed = timeWeNeedToSave / 2;
float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable); float midPosition = (float)Interpolation.Lerp(lastPosition, h.X, (float)timeAtDashSpeed / timeAvailable);
//dash movement //dash movement
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + 1, lastPosition, true));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
} }
else else
{ {
double timeBefore = positionChange / movement_speed; double timeBefore = positionChange / movement_speed;
Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime - timeBefore, lastPosition));
Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X)); Replay.Frames.Add(new CatchReplayFrame(h.StartTime, h.X));
} }
lastTime = h.StartTime; lastTime = h.StartTime;
lastPosition = h.X; lastPosition = h.X;
} }
foreach (var obj in Beatmap.HitObjects) foreach (var obj in Beatmap.HitObjects)
{ {
switch (obj) switch (obj)
{ {
case Fruit _: case Fruit _:
moveToNext(obj); moveToNext(obj);
break; break;
} }
foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>()) foreach (var nestedObj in obj.NestedHitObjects.Cast<CatchHitObject>())
{ {
switch (nestedObj) switch (nestedObj)
{ {
case BananaShower.Banana _: case BananaShower.Banana _:
case TinyDroplet _: case TinyDroplet _:
case Droplet _: case Droplet _:
case Fruit _: case Fruit _:
moveToNext(nestedObj); moveToNext(nestedObj);
break; break;
} }
} }
} }
return Replay; return Replay;
} }
} }
} }

View File

@ -1,60 +1,60 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Catch.Replays namespace osu.Game.Rulesets.Catch.Replays
{ {
public class CatchFramedReplayInputHandler : FramedReplayInputHandler<CatchReplayFrame> public class CatchFramedReplayInputHandler : FramedReplayInputHandler<CatchReplayFrame>
{ {
public CatchFramedReplayInputHandler(Replay replay) public CatchFramedReplayInputHandler(Replay replay)
: base(replay) : base(replay)
{ {
} }
protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0; protected override bool IsImportant(CatchReplayFrame frame) => frame.Position > 0;
protected float? Position protected float? Position
{ {
get get
{ {
if (!HasFrames) if (!HasFrames)
return null; return null;
return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time); return Interpolation.ValueAt(CurrentTime, CurrentFrame.Position, NextFrame.Position, CurrentFrame.Time, NextFrame.Time);
} }
} }
public override List<InputState> GetPendingStates() public override List<InputState> GetPendingStates()
{ {
if (!Position.HasValue) return new List<InputState>(); if (!Position.HasValue) return new List<InputState>();
var actions = new List<CatchAction>(); var actions = new List<CatchAction>();
if (CurrentFrame.Dashing) if (CurrentFrame.Dashing)
actions.Add(CatchAction.Dash); actions.Add(CatchAction.Dash);
if (Position.Value > CurrentFrame.Position) if (Position.Value > CurrentFrame.Position)
actions.Add(CatchAction.MoveRight); actions.Add(CatchAction.MoveRight);
else if (Position.Value < CurrentFrame.Position) else if (Position.Value < CurrentFrame.Position)
actions.Add(CatchAction.MoveLeft); actions.Add(CatchAction.MoveLeft);
return new List<InputState> return new List<InputState>
{ {
new CatchReplayState new CatchReplayState
{ {
PressedActions = actions, PressedActions = actions,
CatcherX = Position.Value CatcherX = Position.Value
}, },
}; };
} }
public class CatchReplayState : ReplayState<CatchAction> public class CatchReplayState : ReplayState<CatchAction>
{ {
public float? CatcherX { get; set; } public float? CatcherX { get; set; }
} }
} }
} }

View File

@ -1,34 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Legacy; using osu.Game.Rulesets.Replays.Legacy;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
namespace osu.Game.Rulesets.Catch.Replays namespace osu.Game.Rulesets.Catch.Replays
{ {
public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame public class CatchReplayFrame : ReplayFrame, IConvertibleReplayFrame
{ {
public float Position; public float Position;
public bool Dashing; public bool Dashing;
public CatchReplayFrame() public CatchReplayFrame()
{ {
} }
public CatchReplayFrame(double time, float? position = null, bool dashing = false) public CatchReplayFrame(double time, float? position = null, bool dashing = false)
: base(time) : base(time)
{ {
Position = position ?? -1; Position = position ?? -1;
Dashing = dashing; Dashing = dashing;
} }
public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap) public void ConvertFrom(LegacyReplayFrame legacyFrame, Beatmap beatmap)
{ {
Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH; Position = legacyFrame.Position.X / CatchPlayfield.BASE_WIDTH;
Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1; Dashing = legacyFrame.ButtonState == ReplayButtonState.Left1;
} }
} }
} }

View File

@ -1,44 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Judgements; using osu.Game.Rulesets.Catch.Judgements;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch.Scoring namespace osu.Game.Rulesets.Catch.Scoring
{ {
public class CatchScoreProcessor : ScoreProcessor<CatchHitObject> public class CatchScoreProcessor : ScoreProcessor<CatchHitObject>
{ {
public CatchScoreProcessor(RulesetContainer<CatchHitObject> rulesetContainer) public CatchScoreProcessor(RulesetContainer<CatchHitObject> rulesetContainer)
: base(rulesetContainer) : base(rulesetContainer)
{ {
} }
protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap) protected override void SimulateAutoplay(Beatmap<CatchHitObject> beatmap)
{ {
foreach (var obj in beatmap.HitObjects) foreach (var obj in beatmap.HitObjects)
{ {
switch (obj) switch (obj)
{ {
case JuiceStream stream: case JuiceStream stream:
foreach (var _ in stream.NestedHitObjects.Cast<CatchHitObject>()) foreach (var _ in stream.NestedHitObjects.Cast<CatchHitObject>())
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
case BananaShower shower: case BananaShower shower:
foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>()) foreach (var _ in shower.NestedHitObjects.Cast<CatchHitObject>())
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
case Fruit _: case Fruit _:
AddJudgement(new CatchJudgement { Result = HitResult.Perfect }); AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
break; break;
} }
} }
base.SimulateAutoplay(beatmap); base.SimulateAutoplay(beatmap);
} }
} }
} }

View File

@ -1,70 +1,70 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Catch.UI namespace osu.Game.Rulesets.Catch.UI
{ {
public class CatchPlayfield : ScrollingPlayfield public class CatchPlayfield : ScrollingPlayfield
{ {
public const float BASE_WIDTH = 512; public const float BASE_WIDTH = 512;
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content; private readonly Container<Drawable> content;
private readonly CatcherArea catcherArea; private readonly CatcherArea catcherArea;
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation) public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation)
: base(ScrollingDirection.Down, BASE_WIDTH) : base(ScrollingDirection.Down, BASE_WIDTH)
{ {
Container explodingFruitContainer; Container explodingFruitContainer;
Anchor = Anchor.TopCentre; Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre; Origin = Anchor.TopCentre;
base.Content.Anchor = Anchor.BottomLeft; base.Content.Anchor = Anchor.BottomLeft;
base.Content.Origin = Anchor.BottomLeft; base.Content.Origin = Anchor.BottomLeft;
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
{ {
explodingFruitContainer = new Container explodingFruitContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
catcherArea = new CatcherArea(difficulty) catcherArea = new CatcherArea(difficulty)
{ {
GetVisualRepresentation = getVisualRepresentation, GetVisualRepresentation = getVisualRepresentation,
ExplodingFruitTarget = explodingFruitContainer, ExplodingFruitTarget = explodingFruitContainer,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
}, },
content = new Container<Drawable> content = new Container<Drawable>
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
}); });
} }
public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj); public bool CheckIfWeCanCatch(CatchHitObject obj) => catcherArea.AttemptCatch(obj);
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
{ {
h.OnJudgement += onJudgement; h.OnJudgement += onJudgement;
base.Add(h); base.Add(h);
var fruit = (DrawableCatchHitObject)h; var fruit = (DrawableCatchHitObject)h;
fruit.CheckPosition = CheckIfWeCanCatch; fruit.CheckPosition = CheckIfWeCanCatch;
} }
private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) => catcherArea.OnJudgement((DrawableCatchHitObject)judgedObject, judgement); private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) => catcherArea.OnJudgement((DrawableCatchHitObject)judgedObject, judgement);
} }
} }

View File

@ -1,59 +1,59 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Catch.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Catch.Scoring; using osu.Game.Rulesets.Catch.Scoring;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Catch.UI namespace osu.Game.Rulesets.Catch.UI
{ {
public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchHitObject> public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchHitObject>
{ {
public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset)
{ {
} }
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay);
protected override BeatmapProcessor<CatchHitObject> CreateBeatmapProcessor() => new CatchBeatmapProcessor(); protected override BeatmapProcessor<CatchHitObject> CreateBeatmapProcessor() => new CatchBeatmapProcessor();
protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter() => new CatchBeatmapConverter(); protected override BeatmapConverter<CatchHitObject> CreateBeatmapConverter() => new CatchBeatmapConverter();
protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation); protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty, GetVisualRepresentation);
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h) protected override DrawableHitObject<CatchHitObject> GetVisualRepresentation(CatchHitObject h)
{ {
switch (h) switch (h)
{ {
case Fruit fruit: case Fruit fruit:
return new DrawableFruit(fruit); return new DrawableFruit(fruit);
case JuiceStream stream: case JuiceStream stream:
return new DrawableJuiceStream(stream, GetVisualRepresentation); return new DrawableJuiceStream(stream, GetVisualRepresentation);
case BananaShower banana: case BananaShower banana:
return new DrawableBananaShower(banana, GetVisualRepresentation); return new DrawableBananaShower(banana, GetVisualRepresentation);
case TinyDroplet tiny: case TinyDroplet tiny:
return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) }; return new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) };
case Droplet droplet: case Droplet droplet:
return new DrawableDroplet(droplet); return new DrawableDroplet(droplet);
} }
return null; return null;
} }
} }
} }

View File

@ -1,416 +1,416 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable; using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Replays; using osu.Game.Rulesets.Catch.Replays;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.UI namespace osu.Game.Rulesets.Catch.UI
{ {
public class CatcherArea : Container public class CatcherArea : Container
{ {
public const float CATCHER_SIZE = 172; public const float CATCHER_SIZE = 172;
protected readonly Catcher MovableCatcher; protected readonly Catcher MovableCatcher;
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> GetVisualRepresentation; public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> GetVisualRepresentation;
public Container ExplodingFruitTarget public Container ExplodingFruitTarget
{ {
set { MovableCatcher.ExplodingFruitTarget = value; } set { MovableCatcher.ExplodingFruitTarget = value; }
} }
public CatcherArea(BeatmapDifficulty difficulty = null) public CatcherArea(BeatmapDifficulty difficulty = null)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = CATCHER_SIZE; Height = CATCHER_SIZE;
Child = MovableCatcher = new Catcher(difficulty) Child = MovableCatcher = new Catcher(difficulty)
{ {
AdditiveTarget = this, AdditiveTarget = this,
}; };
} }
private DrawableCatchHitObject lastPlateableFruit; private DrawableCatchHitObject lastPlateableFruit;
public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement) public void OnJudgement(DrawableCatchHitObject fruit, Judgement judgement)
{ {
if (judgement.IsHit && fruit.CanBePlated) if (judgement.IsHit && fruit.CanBePlated)
{ {
var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject); var caughtFruit = (DrawableCatchHitObject)GetVisualRepresentation?.Invoke(fruit.HitObject);
if (caughtFruit == null) return; if (caughtFruit == null) return;
caughtFruit.RelativePositionAxes = Axes.None; caughtFruit.RelativePositionAxes = Axes.None;
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0); caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
caughtFruit.Anchor = Anchor.TopCentre; caughtFruit.Anchor = Anchor.TopCentre;
caughtFruit.Origin = Anchor.Centre; caughtFruit.Origin = Anchor.Centre;
caughtFruit.Scale *= 0.7f; caughtFruit.Scale *= 0.7f;
caughtFruit.LifetimeEnd = double.MaxValue; caughtFruit.LifetimeEnd = double.MaxValue;
MovableCatcher.Add(caughtFruit); MovableCatcher.Add(caughtFruit);
lastPlateableFruit = caughtFruit; lastPlateableFruit = caughtFruit;
} }
if (fruit.HitObject.LastInCombo) if (fruit.HitObject.LastInCombo)
{ {
if (judgement.IsHit) if (judgement.IsHit)
{ {
// this is required to make this run after the last caught fruit runs UpdateState at least once. // this is required to make this run after the last caught fruit runs UpdateState at least once.
// TODO: find a better alternative // TODO: find a better alternative
if (lastPlateableFruit.IsLoaded) if (lastPlateableFruit.IsLoaded)
MovableCatcher.Explode(); MovableCatcher.Explode();
else else
lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); }; lastPlateableFruit.OnLoadComplete = _ => { MovableCatcher.Explode(); };
} }
else else
MovableCatcher.Drop(); MovableCatcher.Drop();
} }
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState; var state = GetContainingInputManager().CurrentState as CatchFramedReplayInputHandler.CatchReplayState;
if (state?.CatcherX != null) if (state?.CatcherX != null)
MovableCatcher.X = state.CatcherX.Value; MovableCatcher.X = state.CatcherX.Value;
} }
public bool OnReleased(CatchAction action) => false; public bool OnReleased(CatchAction action) => false;
public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj); public bool AttemptCatch(CatchHitObject obj) => MovableCatcher.AttemptCatch(obj);
public class Catcher : Container, IKeyBindingHandler<CatchAction> public class Catcher : Container, IKeyBindingHandler<CatchAction>
{ {
private Texture texture; private Texture texture;
private Container<DrawableHitObject> caughtFruit; private Container<DrawableHitObject> caughtFruit;
public Container ExplodingFruitTarget; public Container ExplodingFruitTarget;
public Container AdditiveTarget; public Container AdditiveTarget;
public Catcher(BeatmapDifficulty difficulty = null) public Catcher(BeatmapDifficulty difficulty = null)
{ {
RelativePositionAxes = Axes.X; RelativePositionAxes = Axes.X;
X = 0.5f; X = 0.5f;
Origin = Anchor.TopCentre; Origin = Anchor.TopCentre;
Anchor = Anchor.TopLeft; Anchor = Anchor.TopLeft;
Size = new Vector2(CATCHER_SIZE); Size = new Vector2(CATCHER_SIZE);
if (difficulty != null) if (difficulty != null)
Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(TextureStore textures)
{ {
texture = textures.Get(@"Play/Catch/fruit-catcher-idle"); texture = textures.Get(@"Play/Catch/fruit-catcher-idle");
Children = new Drawable[] Children = new Drawable[]
{ {
caughtFruit = new Container<DrawableHitObject> caughtFruit = new Container<DrawableHitObject>
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
}, },
createCatcherSprite(), createCatcherSprite(),
}; };
} }
private int currentDirection; private int currentDirection;
private bool dashing; private bool dashing;
protected bool Dashing protected bool Dashing
{ {
get { return dashing; } get { return dashing; }
set set
{ {
if (value == dashing) return; if (value == dashing) return;
dashing = value; dashing = value;
Trail |= dashing; Trail |= dashing;
} }
} }
private bool trail; private bool trail;
/// <summary> /// <summary>
/// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met. /// Activate or deactive the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
/// </summary> /// </summary>
protected bool Trail protected bool Trail
{ {
get { return trail; } get { return trail; }
set set
{ {
if (value == trail) return; if (value == trail) return;
trail = value; trail = value;
if (Trail) if (Trail)
beginTrail(); beginTrail();
} }
} }
private void beginTrail() private void beginTrail()
{ {
Trail &= dashing || HyperDashing; Trail &= dashing || HyperDashing;
Trail &= AdditiveTarget != null; Trail &= AdditiveTarget != null;
if (!Trail) return; if (!Trail) return;
var additive = createCatcherSprite(); var additive = createCatcherSprite();
additive.Anchor = Anchor; additive.Anchor = Anchor;
additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly. additive.OriginPosition = additive.OriginPosition + new Vector2(DrawWidth / 2, 0); // also temporary to align sprite correctly.
additive.Position = Position; additive.Position = Position;
additive.Scale = Scale; additive.Scale = Scale;
additive.Colour = HyperDashing ? Color4.Red : Color4.White; additive.Colour = HyperDashing ? Color4.Red : Color4.White;
additive.RelativePositionAxes = RelativePositionAxes; additive.RelativePositionAxes = RelativePositionAxes;
additive.Blending = BlendingMode.Additive; additive.Blending = BlendingMode.Additive;
AdditiveTarget.Add(additive); AdditiveTarget.Add(additive);
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire(); additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint).Expire();
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50); Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
} }
private Sprite createCatcherSprite() => new Sprite private Sprite createCatcherSprite() => new Sprite
{ {
Size = new Vector2(CATCHER_SIZE), Size = new Vector2(CATCHER_SIZE),
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
Texture = texture, Texture = texture,
OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly. OriginPosition = new Vector2(-3, 10) // temporary until the sprite is aligned correctly.
}; };
/// <summary> /// <summary>
/// Add a caught fruit to the catcher's stack. /// Add a caught fruit to the catcher's stack.
/// </summary> /// </summary>
/// <param name="fruit">The fruit that was caught.</param> /// <param name="fruit">The fruit that was caught.</param>
public void Add(DrawableHitObject fruit) public void Add(DrawableHitObject fruit)
{ {
float ourRadius = fruit.DrawSize.X / 2 * fruit.Scale.X; float ourRadius = fruit.DrawSize.X / 2 * fruit.Scale.X;
float theirRadius = 0; float theirRadius = 0;
const float allowance = 6; const float allowance = 6;
while (caughtFruit.Any(f => while (caughtFruit.Any(f =>
f.LifetimeEnd == double.MaxValue && f.LifetimeEnd == double.MaxValue &&
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
{ {
float diff = (ourRadius + theirRadius) / allowance; float diff = (ourRadius + theirRadius) / allowance;
fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff; fruit.X += (RNG.NextSingle() - 0.5f) * 2 * diff;
fruit.Y -= RNG.NextSingle() * diff; fruit.Y -= RNG.NextSingle() * diff;
} }
fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2); fruit.X = MathHelper.Clamp(fruit.X, -CATCHER_SIZE / 2, CATCHER_SIZE / 2);
caughtFruit.Add(fruit); caughtFruit.Add(fruit);
} }
/// <summary> /// <summary>
/// Let the catcher attempt to catch a fruit. /// Let the catcher attempt to catch a fruit.
/// </summary> /// </summary>
/// <param name="fruit">The fruit to catch.</param> /// <param name="fruit">The fruit to catch.</param>
/// <returns>Whether the catch is possible.</returns> /// <returns>Whether the catch is possible.</returns>
public bool AttemptCatch(CatchHitObject fruit) public bool AttemptCatch(CatchHitObject fruit)
{ {
double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f; double halfCatcherWidth = CATCHER_SIZE * Math.Abs(Scale.X) * 0.5f;
// this stuff wil disappear once we move fruit to non-relative coordinate space in the future. // this stuff wil disappear once we move fruit to non-relative coordinate space in the future.
var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH;
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
var validCatch = var validCatch =
catchObjectPosition >= catcherPosition - halfCatcherWidth && catchObjectPosition >= catcherPosition - halfCatcherWidth &&
catchObjectPosition <= catcherPosition + halfCatcherWidth; catchObjectPosition <= catcherPosition + halfCatcherWidth;
if (validCatch && fruit.HyperDash) if (validCatch && fruit.HyperDash)
{ {
HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED; HyperDashModifier = Math.Abs(fruit.HyperDashTarget.X - fruit.X) / Math.Abs(fruit.HyperDashTarget.StartTime - fruit.StartTime) / BASE_SPEED;
HyperDashDirection = fruit.HyperDashTarget.X - fruit.X; HyperDashDirection = fruit.HyperDashTarget.X - fruit.X;
} }
else else
HyperDashModifier = 1; HyperDashModifier = 1;
return validCatch; return validCatch;
} }
/// <summary> /// <summary>
/// Whether we are hypderdashing or not. /// Whether we are hypderdashing or not.
/// </summary> /// </summary>
public bool HyperDashing => hyperDashModifier != 1; public bool HyperDashing => hyperDashModifier != 1;
private double hyperDashModifier = 1; private double hyperDashModifier = 1;
/// <summary> /// <summary>
/// The direction in which hyperdash is allowed. 0 allows both directions. /// The direction in which hyperdash is allowed. 0 allows both directions.
/// </summary> /// </summary>
public double HyperDashDirection; public double HyperDashDirection;
/// <summary> /// <summary>
/// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1. /// The speed modifier resultant from hyperdash. Will trigger hyperdash when not equal to 1.
/// </summary> /// </summary>
public double HyperDashModifier public double HyperDashModifier
{ {
get { return hyperDashModifier; } get { return hyperDashModifier; }
set set
{ {
if (value == hyperDashModifier) return; if (value == hyperDashModifier) return;
hyperDashModifier = value; hyperDashModifier = value;
const float transition_length = 180; const float transition_length = 180;
if (HyperDashing) if (HyperDashing)
{ {
this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint); this.FadeColour(Color4.OrangeRed, transition_length, Easing.OutQuint);
this.FadeTo(0.2f, transition_length, Easing.OutQuint); this.FadeTo(0.2f, transition_length, Easing.OutQuint);
Trail = true; Trail = true;
} }
else else
{ {
HyperDashDirection = 0; HyperDashDirection = 0;
this.FadeColour(Color4.White, transition_length, Easing.OutQuint); this.FadeColour(Color4.White, transition_length, Easing.OutQuint);
this.FadeTo(1, transition_length, Easing.OutQuint); this.FadeTo(1, transition_length, Easing.OutQuint);
} }
} }
} }
public bool OnPressed(CatchAction action) public bool OnPressed(CatchAction action)
{ {
switch (action) switch (action)
{ {
case CatchAction.MoveLeft: case CatchAction.MoveLeft:
currentDirection--; currentDirection--;
return true; return true;
case CatchAction.MoveRight: case CatchAction.MoveRight:
currentDirection++; currentDirection++;
return true; return true;
case CatchAction.Dash: case CatchAction.Dash:
Dashing = true; Dashing = true;
return true; return true;
} }
return false; return false;
} }
public bool OnReleased(CatchAction action) public bool OnReleased(CatchAction action)
{ {
switch (action) switch (action)
{ {
case CatchAction.MoveLeft: case CatchAction.MoveLeft:
currentDirection++; currentDirection++;
return true; return true;
case CatchAction.MoveRight: case CatchAction.MoveRight:
currentDirection--; currentDirection--;
return true; return true;
case CatchAction.Dash: case CatchAction.Dash:
Dashing = false; Dashing = false;
return true; return true;
} }
return false; return false;
} }
/// <summary> /// <summary>
/// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable.
/// </summary> /// </summary>
public const double BASE_SPEED = 1.0 / 512; public const double BASE_SPEED = 1.0 / 512;
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();
if (currentDirection == 0) return; if (currentDirection == 0) return;
var direction = Math.Sign(currentDirection); var direction = Math.Sign(currentDirection);
double dashModifier = Dashing ? 1 : 0.5; double dashModifier = Dashing ? 1 : 0.5;
if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection))) if (hyperDashModifier != 1 && (HyperDashDirection == 0 || direction == Math.Sign(HyperDashDirection)))
dashModifier = hyperDashModifier; dashModifier = hyperDashModifier;
Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y); Scale = new Vector2(Math.Abs(Scale.X) * direction, Scale.Y);
X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1); X = (float)MathHelper.Clamp(X + direction * Clock.ElapsedFrameTime * BASE_SPEED * dashModifier, 0, 1);
} }
/// <summary> /// <summary>
/// Drop any fruit off the plate. /// Drop any fruit off the plate.
/// </summary> /// </summary>
public void Drop() public void Drop()
{ {
var fruit = caughtFruit.ToArray(); var fruit = caughtFruit.ToArray();
foreach (var f in fruit) foreach (var f in fruit)
{ {
if (ExplodingFruitTarget != null) if (ExplodingFruitTarget != null)
{ {
f.Anchor = Anchor.TopLeft; f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f); caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f); ExplodingFruitTarget.Add(f);
} }
f.MoveToY(f.Y + 75, 750, Easing.InSine); f.MoveToY(f.Y + 75, 750, Easing.InSine);
f.FadeOut(750); f.FadeOut(750);
f.Expire(); f.Expire();
} }
} }
/// <summary> /// <summary>
/// Explode any fruit off the plate. /// Explode any fruit off the plate.
/// </summary> /// </summary>
public void Explode() public void Explode()
{ {
var fruit = caughtFruit.ToArray(); var fruit = caughtFruit.ToArray();
foreach (var f in fruit) foreach (var f in fruit)
{ {
var originalX = f.X * Scale.X; var originalX = f.X * Scale.X;
if (ExplodingFruitTarget != null) if (ExplodingFruitTarget != null)
{ {
f.Anchor = Anchor.TopLeft; f.Anchor = Anchor.TopLeft;
f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget); f.Position = caughtFruit.ToSpaceOfOtherDrawable(f.DrawPosition, ExplodingFruitTarget);
caughtFruit.Remove(f); caughtFruit.Remove(f);
ExplodingFruitTarget.Add(f); ExplodingFruitTarget.Add(f);
} }
f.MoveToY(f.Y - 50, 250, Easing.OutSine) f.MoveToY(f.Y - 50, 250, Easing.OutSine)
.Then() .Then()
.MoveToY(f.Y + 50, 500, Easing.InSine); .MoveToY(f.Y + 50, 500, Easing.InSine);
f.MoveToX(f.X + originalX * 6, 1000); f.MoveToX(f.X + originalX * 6, 1000);
f.FadeOut(750); f.FadeOut(750);
f.Expire(); f.Expire();
} }
} }
} }
} }
} }

View File

@ -1,60 +1,60 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue> public class ManiaBeatmapConversionTest : BeatmapConversionTest<ConvertValue>
{ {
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
private bool isForCurrentRuleset; private bool isForCurrentRuleset;
[NonParallelizable] [NonParallelizable]
[TestCase("basic", false)] [TestCase("basic", false)]
public void Test(string name, bool isForCurrentRuleset) public void Test(string name, bool isForCurrentRuleset)
{ {
this.isForCurrentRuleset = isForCurrentRuleset; this.isForCurrentRuleset = isForCurrentRuleset;
base.Test(name); base.Test(name);
} }
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject) protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
{ {
yield return new ConvertValue yield return new ConvertValue
{ {
StartTime = hitObject.StartTime, StartTime = hitObject.StartTime,
EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime, EndTime = (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime,
Column = ((ManiaHitObject)hitObject).Column Column = ((ManiaHitObject)hitObject).Column
}; };
} }
protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap); protected override IBeatmapConverter CreateConverter(Beatmap beatmap) => new ManiaBeatmapConverter(isForCurrentRuleset, beatmap);
} }
public struct ConvertValue : IEquatable<ConvertValue> public struct ConvertValue : IEquatable<ConvertValue>
{ {
/// <summary> /// <summary>
/// A sane value to account for osu!stable using ints everwhere. /// A sane value to account for osu!stable using ints everwhere.
/// </summary> /// </summary>
private const float conversion_lenience = 2; private const float conversion_lenience = 2;
public double StartTime; public double StartTime;
public double EndTime; public double EndTime;
public int Column; public int Column;
public bool Equals(ConvertValue other) public bool Equals(ConvertValue other)
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
&& Column == other.Column; && Column == other.Column;
} }
} }

View File

@ -1,179 +1,179 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseAutoGeneration : OsuTestCase public class TestCaseAutoGeneration : OsuTestCase
{ {
[Test] [Test]
public void TestSingleNote() public void TestSingleNote()
{ {
// | | // | |
// | - | // | - |
// | | // | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 1000 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
} }
[Test] [Test]
public void TestSingleHoldNote() public void TestSingleHoldNote()
{ {
// | | // | |
// | * | // | * |
// | * | // | * |
// | * | // | * |
// | | // | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Special1), "Special1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Special1), "Special1 has not been released");
} }
[Test] [Test]
public void TestSingleNoteChord() public void TestSingleNoteChord()
{ {
// | | | // | | |
// | - | - | // | - | - |
// | | | // | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 }); beatmap.HitObjects.Add(new Note { StartTime = 1000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
} }
[Test] [Test]
public void TestHoldNoteChord() public void TestHoldNoteChord()
{ {
// | | | // | | |
// | * | * | // | * | * |
// | * | * | // | * | * |
// | * | * | // | * | * |
// | | | // | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames"); Assert.IsTrue(generated.Frames.Count == 3, "Replay must have 3 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been released");
} }
[Test] [Test]
public void TestSingleNoteStair() public void TestSingleNoteStair()
{ {
// | | | // | | |
// | | - | // | | - |
// | - | | // | - | |
// | | | // | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new Note { StartTime = 1000 }); beatmap.HitObjects.Add(new Note { StartTime = 1000 });
beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 }); beatmap.HitObjects.Add(new Note { StartTime = 2000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time"); Assert.AreEqual(1000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[2].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time"); Assert.AreEqual(2000, generated.Frames[3].Time, "Incorrect second note hit time");
Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); Assert.AreEqual(2000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
} }
[Test] [Test]
public void TestHoldNoteStair() public void TestHoldNoteStair()
{ {
// | | | // | | |
// | | * | // | | * |
// | * | * | // | * | * |
// | * | * | // | * | * |
// | * | | // | * | |
// | | | // | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 }); beatmap.HitObjects.Add(new HoldNote { StartTime = 2000, Duration = 2000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames"); Assert.IsTrue(generated.Frames.Count == 5, "Replay must have 5 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect first note release time");
Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time"); Assert.AreEqual(2000, generated.Frames[2].Time, "Incorrect second note hit time");
Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time"); Assert.AreEqual(4000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[4].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed"); Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key1, ManiaAction.Key2), "Key1 & Key2 have not been pressed");
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released"); Assert.IsTrue(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has been released");
Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released"); Assert.IsFalse(checkContains(generated.Frames[4], ManiaAction.Key2), "Key2 has not been released");
} }
[Test] [Test]
public void TestHoldNoteWithReleasePress() public void TestHoldNoteWithReleasePress()
{ {
// | | | // | | |
// | * | - | // | * | - |
// | * | | // | * | |
// | * | | // | * | |
// | | | // | | |
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 }); var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 2 });
beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY }); beatmap.HitObjects.Add(new HoldNote { StartTime = 1000, Duration = 2000 - ManiaAutoGenerator.RELEASE_DELAY });
beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 }); beatmap.HitObjects.Add(new Note { StartTime = 3000, Column = 1 });
var generated = new ManiaAutoGenerator(beatmap).Generate(); var generated = new ManiaAutoGenerator(beatmap).Generate();
Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames"); Assert.IsTrue(generated.Frames.Count == 4, "Replay must have 4 frames");
Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time"); Assert.AreEqual(1000, generated.Frames[1].Time, "Incorrect first note hit time");
Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time"); Assert.AreEqual(3000, generated.Frames[2].Time, "Incorrect second note press time + first note release time");
Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time"); Assert.AreEqual(3000 + ManiaAutoGenerator.RELEASE_DELAY, generated.Frames[3].Time, "Incorrect second note release time");
Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[1], ManiaAction.Key1), "Key1 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released"); Assert.IsFalse(checkContains(generated.Frames[2], ManiaAction.Key1), "Key1 has not been released");
Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed"); Assert.IsTrue(checkContains(generated.Frames[2], ManiaAction.Key2), "Key2 has not been pressed");
Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released"); Assert.IsFalse(checkContains(generated.Frames[3], ManiaAction.Key2), "Key2 has not been released");
} }
private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action)); private bool checkContains(ReplayFrame frame, params ManiaAction[] actions) => actions.All(action => ((ManiaReplayFrame)frame).Actions.Contains(action));
} }
} }

View File

@ -1,96 +1,96 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseManiaHitObjects : OsuTestCase public class TestCaseManiaHitObjects : OsuTestCase
{ {
public TestCaseManiaHitObjects() public TestCaseManiaHitObjects()
{ {
Add(new FillFlowContainer Add(new FillFlowContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal, Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0), Spacing = new Vector2(10, 0),
// Imagine that the containers containing the drawable notes are the "columns" // Imagine that the containers containing the drawable notes are the "columns"
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
{ {
Name = "Normal note column", Name = "Normal note column",
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = 50, Width = 50,
Children = new[] Children = new[]
{ {
new Container new Container
{ {
Name = "Timing section", Name = "Timing section",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
RelativeChildSize = new Vector2(1, 10000), RelativeChildSize = new Vector2(1, 10000),
Children = new[] Children = new[]
{ {
new DrawableNote(new Note(), ManiaAction.Key1) new DrawableNote(new Note(), ManiaAction.Key1)
{ {
Y = 5000, Y = 5000,
LifetimeStart = double.MinValue, LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue, LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red AccentColour = Color4.Red
}, },
new DrawableNote(new Note(), ManiaAction.Key1) new DrawableNote(new Note(), ManiaAction.Key1)
{ {
Y = 6000, Y = 6000,
LifetimeStart = double.MinValue, LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue, LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red AccentColour = Color4.Red
} }
} }
} }
} }
}, },
new Container new Container
{ {
Name = "Hold note column", Name = "Hold note column",
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = 50, Width = 50,
Children = new[] Children = new[]
{ {
new Container new Container
{ {
Name = "Timing section", Name = "Timing section",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
RelativeChildSize = new Vector2(1, 10000), RelativeChildSize = new Vector2(1, 10000),
Children = new[] Children = new[]
{ {
new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1) new DrawableHoldNote(new HoldNote { Duration = 1000 } , ManiaAction.Key1)
{ {
Y = 5000, Y = 5000,
Height = 1000, Height = 1000,
LifetimeStart = double.MinValue, LifetimeStart = double.MinValue,
LifetimeEnd = double.MaxValue, LifetimeEnd = double.MaxValue,
AccentColour = Color4.Red AccentColour = Color4.Red
} }
} }
} }
} }
} }
} }
}); });
} }
} }
} }

View File

@ -1,193 +1,193 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Tests.Visual; using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
public class TestCaseManiaPlayfield : OsuTestCase public class TestCaseManiaPlayfield : OsuTestCase
{ {
private const double start_time = 500; private const double start_time = 500;
private const double duration = 500; private const double duration = 500;
protected override double TimePerAction => 200; protected override double TimePerAction => 200;
private RulesetInfo maniaRuleset; private RulesetInfo maniaRuleset;
public TestCaseManiaPlayfield() public TestCaseManiaPlayfield()
{ {
var rng = new Random(1337); var rng = new Random(1337);
AddStep("1 column", () => createPlayfield(1)); AddStep("1 column", () => createPlayfield(1));
AddStep("4 columns", () => createPlayfield(4)); AddStep("4 columns", () => createPlayfield(4));
AddStep("5 columns", () => createPlayfield(5)); AddStep("5 columns", () => createPlayfield(5));
AddStep("8 columns", () => createPlayfield(8)); AddStep("8 columns", () => createPlayfield(8));
AddStep("4 + 4 columns", () => AddStep("4 + 4 columns", () =>
{ {
var stages = new List<StageDefinition> var stages = new List<StageDefinition>
{ {
new StageDefinition { Columns = 4 }, new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 4 }, new StageDefinition { Columns = 4 },
}; };
createPlayfield(stages); createPlayfield(stages);
}); });
AddStep("2 + 4 + 2 columns", () => AddStep("2 + 4 + 2 columns", () =>
{ {
var stages = new List<StageDefinition> var stages = new List<StageDefinition>
{ {
new StageDefinition { Columns = 2 }, new StageDefinition { Columns = 2 },
new StageDefinition { Columns = 4 }, new StageDefinition { Columns = 4 },
new StageDefinition { Columns = 2 }, new StageDefinition { Columns = 2 },
}; };
createPlayfield(stages); createPlayfield(stages);
}); });
AddStep("1 + 8 + 1 columns", () => AddStep("1 + 8 + 1 columns", () =>
{ {
var stages = new List<StageDefinition> var stages = new List<StageDefinition>
{ {
new StageDefinition { Columns = 1 }, new StageDefinition { Columns = 1 },
new StageDefinition { Columns = 8 }, new StageDefinition { Columns = 8 },
new StageDefinition { Columns = 1 }, new StageDefinition { Columns = 1 },
}; };
createPlayfield(stages); createPlayfield(stages);
}); });
AddStep("Reversed", () => createPlayfield(4, true)); AddStep("Reversed", () => createPlayfield(4, true));
AddStep("Notes with input", () => createPlayfieldWithNotes()); AddStep("Notes with input", () => createPlayfieldWithNotes());
AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true)); AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Notes with gravity", () => createPlayfieldWithNotes()); AddStep("Notes with gravity", () => createPlayfieldWithNotes());
AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true)); AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true));
AddStep("Hit explosion", () => AddStep("Hit explosion", () =>
{ {
var playfield = createPlayfield(4); var playfield = createPlayfield(4);
int col = rng.Next(0, 4); int col = rng.Next(0, 4);
var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1) var note = new DrawableNote(new Note { Column = col }, ManiaAction.Key1)
{ {
AccentColour = playfield.Columns.ElementAt(col).AccentColour AccentColour = playfield.Columns.ElementAt(col).AccentColour
}; };
playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); playfield.OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect }); playfield.Columns[col].OnJudgement(note, new ManiaJudgement { Result = HitResult.Perfect });
}); });
} }
private DependencyContainer dependencies; private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent)); => dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets, SettingsStore settings) private void load(RulesetStore rulesets, SettingsStore settings)
{ {
maniaRuleset = rulesets.GetRuleset(3); maniaRuleset = rulesets.GetRuleset(3);
dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4)); dependencies.Cache(new ManiaConfigManager(settings, maniaRuleset, 4));
} }
private ManiaPlayfield createPlayfield(int cols, bool inverted = false) private ManiaPlayfield createPlayfield(int cols, bool inverted = false)
{ {
var stages = new List<StageDefinition> var stages = new List<StageDefinition>
{ {
new StageDefinition { Columns = cols }, new StageDefinition { Columns = cols },
}; };
return createPlayfield(stages, inverted); return createPlayfield(stages, inverted);
} }
private ManiaPlayfield createPlayfield(List<StageDefinition> stages, bool inverted = false) private ManiaPlayfield createPlayfield(List<StageDefinition> stages, bool inverted = false)
{ {
Clear(); Clear();
var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both }; var inputManager = new ManiaInputManager(maniaRuleset, stages.Sum(g => g.Columns)) { RelativeSizeAxes = Axes.Both };
Add(inputManager); Add(inputManager);
ManiaPlayfield playfield; ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(stages) inputManager.Add(playfield = new ManiaPlayfield(stages)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}); });
playfield.Inverted.Value = inverted; playfield.Inverted.Value = inverted;
return playfield; return playfield;
} }
private void createPlayfieldWithNotes(bool inverted = false) private void createPlayfieldWithNotes(bool inverted = false)
{ {
Clear(); Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 }; var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both }; var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
Add(inputManager); Add(inputManager);
ManiaPlayfield playfield; ManiaPlayfield playfield;
var stages = new List<StageDefinition> var stages = new List<StageDefinition>
{ {
new StageDefinition { Columns = 4 }, new StageDefinition { Columns = 4 },
}; };
inputManager.Add(playfield = new ManiaPlayfield(stages) inputManager.Add(playfield = new ManiaPlayfield(stages)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Clock = new FramedClock(rateAdjustClock) Clock = new FramedClock(rateAdjustClock)
}); });
playfield.Inverted.Value = inverted; playfield.Inverted.Value = inverted;
for (double t = start_time; t <= start_time + duration; t += 100) for (double t = start_time; t <= start_time + duration; t += 100)
{ {
playfield.Add(new DrawableNote(new Note playfield.Add(new DrawableNote(new Note
{ {
StartTime = t, StartTime = t,
Column = 0 Column = 0
}, ManiaAction.Key1)); }, ManiaAction.Key1));
playfield.Add(new DrawableNote(new Note playfield.Add(new DrawableNote(new Note
{ {
StartTime = t, StartTime = t,
Column = 3 Column = 3
}, ManiaAction.Key4)); }, ManiaAction.Key4));
} }
playfield.Add(new DrawableHoldNote(new HoldNote playfield.Add(new DrawableHoldNote(new HoldNote
{ {
StartTime = start_time, StartTime = start_time,
Duration = duration, Duration = duration,
Column = 1 Column = 1
}, ManiaAction.Key2)); }, ManiaAction.Key2));
playfield.Add(new DrawableHoldNote(new HoldNote playfield.Add(new DrawableHoldNote(new HoldNote
{ {
StartTime = start_time, StartTime = start_time,
Duration = duration, Duration = duration,
Column = 2 Column = 2
}, ManiaAction.Key3)); }, ManiaAction.Key3));
} }
} }
} }

View File

@ -1,16 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework; using NUnit.Framework;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
[TestFixture] [TestFixture]
public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints public class TestCasePerformancePoints : Game.Tests.Visual.TestCasePerformancePoints
{ {
public TestCasePerformancePoints() public TestCasePerformancePoints()
: base(new ManiaRuleset()) : base(new ManiaRuleset())
{ {
} }
} }
} }

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.TestProject.props" /> <Import Project="..\osu.TestProject.props" />
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks> <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Project References"> <ItemGroup Label="Project References">
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" /> <ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -1,33 +1,33 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
public class ManiaBeatmap : Beatmap<ManiaHitObject> public class ManiaBeatmap : Beatmap<ManiaHitObject>
{ {
/// <summary> /// <summary>
/// The definitions for each stage in a <see cref="ManiaPlayfield"/>. /// The definitions for each stage in a <see cref="ManiaPlayfield"/>.
/// </summary> /// </summary>
public List<StageDefinition> Stages = new List<StageDefinition>(); public List<StageDefinition> Stages = new List<StageDefinition>();
/// <summary> /// <summary>
/// Total number of columns represented by all stages in this <see cref="ManiaBeatmap"/>. /// Total number of columns represented by all stages in this <see cref="ManiaBeatmap"/>.
/// </summary> /// </summary>
public int TotalColumns => Stages.Sum(g => g.Columns); public int TotalColumns => Stages.Sum(g => g.Columns);
/// <summary> /// <summary>
/// Creates a new <see cref="ManiaBeatmap"/>. /// Creates a new <see cref="ManiaBeatmap"/>.
/// </summary> /// </summary>
/// <param name="defaultStage">The initial stages.</param> /// <param name="defaultStage">The initial stages.</param>
public ManiaBeatmap(StageDefinition defaultStage) public ManiaBeatmap(StageDefinition defaultStage)
{ {
Stages.Add(defaultStage); Stages.Add(defaultStage);
} }
} }
} }

View File

@ -1,225 +1,225 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using System; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns; using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using OpenTK; using OpenTK;
using osu.Game.Audio; using osu.Game.Audio;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
public class ManiaBeatmapConverter : BeatmapConverter<ManiaHitObject> public class ManiaBeatmapConverter : BeatmapConverter<ManiaHitObject>
{ {
/// <summary> /// <summary>
/// Maximum number of previous notes to consider for density calculation. /// Maximum number of previous notes to consider for density calculation.
/// </summary> /// </summary>
private const int max_notes_for_density = 7; private const int max_notes_for_density = 7;
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) }; protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(IHasXPosition) };
public int TargetColumns; public int TargetColumns;
public readonly bool IsForCurrentRuleset; public readonly bool IsForCurrentRuleset;
private Pattern lastPattern = new Pattern(); private Pattern lastPattern = new Pattern();
private FastRandom random; private FastRandom random;
private ManiaBeatmap beatmap; private ManiaBeatmap beatmap;
public ManiaBeatmapConverter(bool isForCurrentRuleset, Beatmap original) public ManiaBeatmapConverter(bool isForCurrentRuleset, Beatmap original)
{ {
IsForCurrentRuleset = isForCurrentRuleset; IsForCurrentRuleset = isForCurrentRuleset;
var roundedCircleSize = Math.Round(original.BeatmapInfo.BaseDifficulty.CircleSize); var roundedCircleSize = Math.Round(original.BeatmapInfo.BaseDifficulty.CircleSize);
var roundedOverallDifficulty = Math.Round(original.BeatmapInfo.BaseDifficulty.OverallDifficulty); var roundedOverallDifficulty = Math.Round(original.BeatmapInfo.BaseDifficulty.OverallDifficulty);
if (isForCurrentRuleset) if (isForCurrentRuleset)
TargetColumns = (int)Math.Max(1, roundedCircleSize); TargetColumns = (int)Math.Max(1, roundedCircleSize);
else else
{ {
float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count; float percentSliderOrSpinner = (float)original.HitObjects.Count(h => h is IHasEndTime) / original.HitObjects.Count;
if (percentSliderOrSpinner < 0.2) if (percentSliderOrSpinner < 0.2)
TargetColumns = 7; TargetColumns = 7;
else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5)
TargetColumns = roundedOverallDifficulty > 5 ? 7 : 6; TargetColumns = roundedOverallDifficulty > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6) else if (percentSliderOrSpinner > 0.6)
TargetColumns = roundedOverallDifficulty > 4 ? 5 : 4; TargetColumns = roundedOverallDifficulty > 4 ? 5 : 4;
else else
TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7)); TargetColumns = Math.Max(4, Math.Min((int)roundedOverallDifficulty + 1, 7));
} }
} }
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original) protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original)
{ {
BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; 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); int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed); random = new FastRandom(seed);
return base.ConvertBeatmap(original); return base.ConvertBeatmap(original);
} }
protected override Beatmap<ManiaHitObject> CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns }); protected override Beatmap<ManiaHitObject> CreateBeatmap() => beatmap = new ManiaBeatmap(new StageDefinition { Columns = TargetColumns });
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap) protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
{ {
var maniaOriginal = original as ManiaHitObject; var maniaOriginal = original as ManiaHitObject;
if (maniaOriginal != null) if (maniaOriginal != null)
{ {
yield return maniaOriginal; yield return maniaOriginal;
yield break; yield break;
} }
var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap); var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap);
if (objects == null) if (objects == null)
yield break; yield break;
foreach (ManiaHitObject obj in objects) foreach (ManiaHitObject obj in objects)
yield return obj; yield return obj;
} }
private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density); private readonly List<double> prevNoteTimes = new List<double>(max_notes_for_density);
private double density = int.MaxValue; private double density = int.MaxValue;
private void computeDensity(double newNoteTime) private void computeDensity(double newNoteTime)
{ {
if (prevNoteTimes.Count == max_notes_for_density) if (prevNoteTimes.Count == max_notes_for_density)
prevNoteTimes.RemoveAt(0); prevNoteTimes.RemoveAt(0);
prevNoteTimes.Add(newNoteTime); prevNoteTimes.Add(newNoteTime);
density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count; density = (prevNoteTimes[prevNoteTimes.Count - 1] - prevNoteTimes[0]) / prevNoteTimes.Count;
} }
private double lastTime; private double lastTime;
private Vector2 lastPosition; private Vector2 lastPosition;
private PatternType lastStair; private PatternType lastStair;
private void recordNote(double time, Vector2 position) private void recordNote(double time, Vector2 position)
{ {
lastTime = time; lastTime = time;
lastPosition = position; lastPosition = position;
} }
/// <summary> /// <summary>
/// Method that generates hit objects for osu!mania specific beatmaps. /// Method that generates hit objects for osu!mania specific beatmaps.
/// </summary> /// </summary>
/// <param name="original">The original hit object.</param> /// <param name="original">The original hit object.</param>
/// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param> /// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, Beatmap originalBeatmap) private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, Beatmap originalBeatmap)
{ {
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
Pattern newPattern = generator.Generate(); Pattern newPattern = generator.Generate();
lastPattern = newPattern; lastPattern = newPattern;
return newPattern.HitObjects; return newPattern.HitObjects;
} }
/// <summary> /// <summary>
/// Method that generates hit objects for non-osu!mania beatmaps. /// Method that generates hit objects for non-osu!mania beatmaps.
/// </summary> /// </summary>
/// <param name="original">The original hit object.</param> /// <param name="original">The original hit object.</param>
/// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param> /// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateConverted(HitObject original, Beatmap originalBeatmap) private IEnumerable<ManiaHitObject> generateConverted(HitObject original, Beatmap originalBeatmap)
{ {
var endTimeData = original as IHasEndTime; var endTimeData = original as IHasEndTime;
var distanceData = original as IHasDistance; var distanceData = original as IHasDistance;
var positionData = original as IHasPosition; var positionData = original as IHasPosition;
Patterns.PatternGenerator conversion = null; Patterns.PatternGenerator conversion = null;
if (distanceData != null) if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap); conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
else if (endTimeData != null) else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap); conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap);
else if (positionData != null) else if (positionData != null)
{ {
computeDensity(original.StartTime); computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap); conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position); recordNote(original.StartTime, positionData.Position);
} }
if (conversion == null) if (conversion == null)
return null; return null;
Pattern newPattern = conversion.Generate(); Pattern newPattern = conversion.Generate();
lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
return newPattern.HitObjects; return newPattern.HitObjects;
} }
/// <summary> /// <summary>
/// A pattern generator for osu!mania-specific beatmaps. /// A pattern generator for osu!mania-specific beatmaps.
/// </summary> /// </summary>
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{ {
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{ {
} }
public override Pattern Generate() public override Pattern Generate()
{ {
var endTimeData = HitObject as IHasEndTime; var endTimeData = HitObject as IHasEndTime;
var positionData = HitObject as IHasXPosition; var positionData = HitObject as IHasXPosition;
int column = GetColumn(positionData?.X ?? 0); int column = GetColumn(positionData?.X ?? 0);
var pattern = new Pattern(); var pattern = new Pattern();
if (endTimeData != null) if (endTimeData != null)
{ {
pattern.Add(new HoldNote pattern.Add(new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Duration = endTimeData.Duration, Duration = endTimeData.Duration,
Column = column, Column = column,
Head = { Samples = sampleInfoListAt(HitObject.StartTime) }, Head = { Samples = sampleInfoListAt(HitObject.StartTime) },
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) }, Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) },
}); });
} }
else if (positionData != null) else if (positionData != null)
{ {
pattern.Add(new Note pattern.Add(new Note
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Samples = HitObject.Samples, Samples = HitObject.Samples,
Column = column Column = column
}); });
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Retrieves the sample info list at a point in time. /// Retrieves the sample info list at a point in time.
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param> /// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns> /// <returns></returns>
private List<SampleInfo> sampleInfoListAt(double time) private List<SampleInfo> sampleInfoListAt(double time)
{ {
var curveData = HitObject as IHasCurve; var curveData = HitObject as IHasCurve;
if (curveData == null) if (curveData == null)
return HitObject.Samples; return HitObject.Samples;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount(); double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index]; return curveData.RepeatSamples[index];
} }
} }
} }
} }

View File

@ -1,491 +1,491 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
/// <summary> /// <summary>
/// A pattern generator for IHasDistance hit objects. /// A pattern generator for IHasDistance hit objects.
/// </summary> /// </summary>
internal class DistanceObjectPatternGenerator : PatternGenerator internal class DistanceObjectPatternGenerator : PatternGenerator
{ {
/// <summary> /// <summary>
/// Base osu! slider scoring distance. /// Base osu! slider scoring distance.
/// </summary> /// </summary>
private const float osu_base_scoring_distance = 100; private const float osu_base_scoring_distance = 100;
private readonly double endTime; private readonly double endTime;
private readonly double segmentDuration; private readonly double segmentDuration;
private readonly int spanCount; private readonly int spanCount;
private PatternType convertType; private PatternType convertType;
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{ {
convertType = PatternType.None; convertType = PatternType.None;
if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
convertType = PatternType.LowProbability; convertType = PatternType.LowProbability;
var distanceData = hitObject as IHasDistance; var distanceData = hitObject as IHasDistance;
var repeatsData = hitObject as IHasRepeats; var repeatsData = hitObject as IHasRepeats;
spanCount = repeatsData?.SpanCount() ?? 1; spanCount = repeatsData?.SpanCount() ?? 1;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime); DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(hitObject.StartTime);
// The true distance, accounting for any repeats // The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * spanCount; double distance = (distanceData?.Distance ?? 0) * spanCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider // The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength; double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// The duration of the osu! hit object // The duration of the osu! hit object
double osuDuration = distance / osuVelocity; double osuDuration = distance / osuVelocity;
endTime = hitObject.StartTime + osuDuration; endTime = hitObject.StartTime + osuDuration;
segmentDuration = (endTime - HitObject.StartTime) / spanCount; segmentDuration = (endTime - HitObject.StartTime) / spanCount;
} }
public override Pattern Generate() public override Pattern Generate()
{ {
if (spanCount > 1) if (spanCount > 1)
{ {
if (segmentDuration <= 90) if (segmentDuration <= 90)
return generateRandomHoldNotes(HitObject.StartTime, 1); return generateRandomHoldNotes(HitObject.StartTime, 1);
if (segmentDuration <= 120) if (segmentDuration <= 120)
{ {
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, spanCount + 1); return generateRandomNotes(HitObject.StartTime, spanCount + 1);
} }
if (segmentDuration <= 160) if (segmentDuration <= 160)
return generateStair(HitObject.StartTime); return generateStair(HitObject.StartTime);
if (segmentDuration <= 200 && ConversionDifficulty > 3) if (segmentDuration <= 200 && ConversionDifficulty > 3)
return generateRandomMultipleNotes(HitObject.StartTime); return generateRandomMultipleNotes(HitObject.StartTime);
double duration = endTime - HitObject.StartTime; double duration = endTime - HitObject.StartTime;
if (duration >= 4000) if (duration >= 4000)
return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.23, 0, 0);
if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart) if (segmentDuration > 400 && spanCount < TotalColumns - 1 - RandomStart)
return generateTiledHoldNotes(HitObject.StartTime); return generateTiledHoldNotes(HitObject.StartTime);
return generateHoldAndNormalNotes(HitObject.StartTime); return generateHoldAndNormalNotes(HitObject.StartTime);
} }
if (segmentDuration <= 110) if (segmentDuration <= 110)
{ {
if (PreviousPattern.ColumnWithObjects < TotalColumns) if (PreviousPattern.ColumnWithObjects < TotalColumns)
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
else else
convertType &= ~PatternType.ForceNotStack; convertType &= ~PatternType.ForceNotStack;
return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2); return generateRandomNotes(HitObject.StartTime, segmentDuration < 80 ? 1 : 2);
} }
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if ((convertType & PatternType.LowProbability) > 0) if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0); return generateNRandomNotes(HitObject.StartTime, 0.78, 0.3, 0);
return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03); return generateNRandomNotes(HitObject.StartTime, 0.85, 0.36, 0.03);
} }
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if ((convertType & PatternType.LowProbability) > 0) if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0); return generateNRandomNotes(HitObject.StartTime, 0.43, 0.08, 0);
return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0); return generateNRandomNotes(HitObject.StartTime, 0.56, 0.18, 0);
} }
if (ConversionDifficulty > 2.5) if (ConversionDifficulty > 2.5)
{ {
if ((convertType & PatternType.LowProbability) > 0) if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.3, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0); return generateNRandomNotes(HitObject.StartTime, 0.37, 0.08, 0);
} }
if ((convertType & PatternType.LowProbability) > 0) if ((convertType & PatternType.LowProbability) > 0)
return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.17, 0, 0);
return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0); return generateNRandomNotes(HitObject.StartTime, 0.27, 0, 0);
} }
/// <summary> /// <summary>
/// Generates random hold notes that start at an span the same amount of rows. /// Generates random hold notes that start at an span the same amount of rows.
/// </summary> /// </summary>
/// <param name="startTime">Start time of each hold note.</param> /// <param name="startTime">Start time of each hold note.</param>
/// <param name="noteCount">Number of hold notes.</param> /// <param name="noteCount">Number of hold notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomHoldNotes(double startTime, int noteCount) private Pattern generateRandomHoldNotes(double startTime, int noteCount)
{ {
// - - - - // - - - -
// ■ - ■ ■ // ■ - ■ ■
// □ - □ □ // □ - □ □
// ■ - ■ ■ // ■ - ■ ■
var pattern = new Pattern(); var pattern = new Pattern();
int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects; int usableColumns = TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects;
int nextColumn = Random.Next(RandomStart, TotalColumns); int nextColumn = Random.Next(RandomStart, TotalColumns);
for (int i = 0; i < Math.Min(usableColumns, noteCount); i++) for (int i = 0; i < Math.Min(usableColumns, noteCount); i++)
{ {
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn)) //find available column
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, endTime);
} }
// This is can't be combined with the above loop due to RNG // This is can't be combined with the above loop due to RNG
for (int i = 0; i < noteCount - usableColumns; i++) for (int i = 0; i < noteCount - usableColumns; i++)
{ {
while (pattern.ColumnHasObject(nextColumn)) while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, endTime);
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Generates random notes, with one note per row and no stacking. /// Generates random notes, with one note per row and no stacking.
/// </summary> /// </summary>
/// <param name="startTime">The start time.</param> /// <param name="startTime">The start time.</param>
/// <param name="noteCount">The number of notes.</param> /// <param name="noteCount">The number of notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomNotes(double startTime, int noteCount) private Pattern generateRandomNotes(double startTime, int noteCount)
{ {
// - - - - // - - - -
// x - - - // x - - -
// - - x - // - - x -
// - - - x // - - - x
// x - - - // x - - -
var pattern = new Pattern(); var pattern = new Pattern();
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns)
{ {
while (PreviousPattern.ColumnHasObject(nextColumn)) while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
} }
int lastColumn = nextColumn; int lastColumn = nextColumn;
for (int i = 0; i < noteCount; i++) for (int i = 0; i < noteCount; i++)
{ {
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
while (nextColumn == lastColumn) while (nextColumn == lastColumn)
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
lastColumn = nextColumn; lastColumn = nextColumn;
startTime += segmentDuration; startTime += segmentDuration;
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Generates a stair of notes, with one note per row. /// Generates a stair of notes, with one note per row.
/// </summary> /// </summary>
/// <param name="startTime">The start time.</param> /// <param name="startTime">The start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateStair(double startTime) private Pattern generateStair(double startTime)
{ {
// - - - - // - - - -
// x - - - // x - - -
// - x - - // - x - -
// - - x - // - - x -
// - - - x // - - - x
// - - x - // - - x -
// - x - - // - x - -
// x - - - // x - - -
var pattern = new Pattern(); var pattern = new Pattern();
int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int column = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
bool increasing = Random.NextDouble() > 0.5; bool increasing = Random.NextDouble() > 0.5;
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
addToPattern(pattern, column, startTime, startTime); addToPattern(pattern, column, startTime, startTime);
startTime += segmentDuration; startTime += segmentDuration;
// Check if we're at the borders of the stage, and invert the pattern if so // Check if we're at the borders of the stage, and invert the pattern if so
if (increasing) if (increasing)
{ {
if (column >= TotalColumns - 1) if (column >= TotalColumns - 1)
{ {
increasing = false; increasing = false;
column--; column--;
} }
else else
column++; column++;
} }
else else
{ {
if (column <= RandomStart) if (column <= RandomStart)
{ {
increasing = true; increasing = true;
column++; column++;
} }
else else
column--; column--;
} }
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Generates random notes with 1-2 notes per row and no stacking. /// Generates random notes with 1-2 notes per row and no stacking.
/// </summary> /// </summary>
/// <param name="startTime">The start time.</param> /// <param name="startTime">The start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomMultipleNotes(double startTime) private Pattern generateRandomMultipleNotes(double startTime)
{ {
// - - - - // - - - -
// x - - - // x - - -
// - x x - // - x x -
// - - - x // - - - x
// x - x - // x - x -
var pattern = new Pattern(); var pattern = new Pattern();
bool legacy = TotalColumns >= 4 && TotalColumns <= 8; bool legacy = TotalColumns >= 4 && TotalColumns <= 8;
int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0)); int interval = Random.Next(1, TotalColumns - (legacy ? 1 : 0));
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn += interval; nextColumn += interval;
if (nextColumn >= TotalColumns - RandomStart) if (nextColumn >= TotalColumns - RandomStart)
nextColumn = nextColumn - TotalColumns - RandomStart + (legacy ? 1 : 0); nextColumn = nextColumn - TotalColumns - RandomStart + (legacy ? 1 : 0);
nextColumn += RandomStart; nextColumn += RandomStart;
// If we're in 2K, let's not add many consecutive doubles // If we're in 2K, let's not add many consecutive doubles
if (TotalColumns > 2) if (TotalColumns > 2)
addToPattern(pattern, nextColumn, startTime, startTime); addToPattern(pattern, nextColumn, startTime, startTime);
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
startTime += segmentDuration; startTime += segmentDuration;
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Generates random hold notes. The amount of hold notes generated is determined by probabilities. /// Generates random hold notes. The amount of hold notes generated is determined by probabilities.
/// </summary> /// </summary>
/// <param name="startTime">The hold note start time.</param> /// <param name="startTime">The hold note start time.</param>
/// <param name="p2">The probability required for 2 hold notes to be generated.</param> /// <param name="p2">The probability required for 2 hold notes to be generated.</param>
/// <param name="p3">The probability required for 3 hold notes to be generated.</param> /// <param name="p3">The probability required for 3 hold notes to be generated.</param>
/// <param name="p4">The probability required for 4 hold notes to be generated.</param> /// <param name="p4">The probability required for 4 hold notes to be generated.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4) private Pattern generateNRandomNotes(double startTime, double p2, double p3, double p4)
{ {
// - - - - // - - - -
// ■ - ■ ■ // ■ - ■ ■
// □ - □ □ // □ - □ □
// ■ - ■ ■ // ■ - ■ ■
switch (TotalColumns) switch (TotalColumns)
{ {
case 2: case 2:
p2 = 0; p2 = 0;
p3 = 0; p3 = 0;
p4 = 0; p4 = 0;
break; break;
case 3: case 3:
p2 = Math.Min(p2, 0.1); p2 = Math.Min(p2, 0.1);
p3 = 0; p3 = 0;
p4 = 0; p4 = 0;
break; break;
case 4: case 4:
p2 = Math.Min(p2, 0.3); p2 = Math.Min(p2, 0.3);
p3 = Math.Min(p3, 0.04); p3 = Math.Min(p3, 0.04);
p4 = 0; p4 = 0;
break; break;
case 5: case 5:
p2 = Math.Min(p2, 0.34); p2 = Math.Min(p2, 0.34);
p3 = Math.Min(p3, 0.1); p3 = Math.Min(p3, 0.1);
p4 = Math.Min(p4, 0.03); p4 = Math.Min(p4, 0.03);
break; break;
} }
bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH; bool isDoubleSample(SampleInfo sample) => sample.Name == SampleInfo.HIT_CLAP && sample.Name == SampleInfo.HIT_FINISH;
bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0; bool canGenerateTwoNotes = (convertType & PatternType.LowProbability) == 0;
canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample); canGenerateTwoNotes &= HitObject.Samples.Any(isDoubleSample) || sampleInfoListAt(HitObject.StartTime).Any(isDoubleSample);
if (canGenerateTwoNotes) if (canGenerateTwoNotes)
p2 = 1; p2 = 1;
return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4)); return generateRandomHoldNotes(startTime, GetRandomNoteCount(p2, p3, p4));
} }
/// <summary> /// <summary>
/// Generates tiled hold notes. You can think of this as a stair of hold notes. /// Generates tiled hold notes. You can think of this as a stair of hold notes.
/// </summary> /// </summary>
/// <param name="startTime">The first hold note start time.</param> /// <param name="startTime">The first hold note start time.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateTiledHoldNotes(double startTime) private Pattern generateTiledHoldNotes(double startTime)
{ {
// - - - - // - - - -
// ■ ■ ■ ■ // ■ ■ ■ ■
// □ □ □ □ // □ □ □ □
// □ □ □ □ // □ □ □ □
// □ □ □ ■ // □ □ □ ■
// □ □ ■ - // □ □ ■ -
// □ ■ - - // □ ■ - -
// ■ - - - // ■ - - -
var pattern = new Pattern(); var pattern = new Pattern();
int columnRepeat = Math.Min(spanCount, TotalColumns); int columnRepeat = Math.Min(spanCount, TotalColumns);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns)
{ {
while (PreviousPattern.ColumnHasObject(nextColumn)) while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
} }
for (int i = 0; i < columnRepeat; i++) for (int i = 0; i < columnRepeat; i++)
{ {
while (pattern.ColumnHasObject(nextColumn)) while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(pattern, nextColumn, startTime, endTime); addToPattern(pattern, nextColumn, startTime, endTime);
startTime += segmentDuration; startTime += segmentDuration;
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Generates a hold note alongside normal notes. /// Generates a hold note alongside normal notes.
/// </summary> /// </summary>
/// <param name="startTime">The start time of notes.</param> /// <param name="startTime">The start time of notes.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateHoldAndNormalNotes(double startTime) private Pattern generateHoldAndNormalNotes(double startTime)
{ {
// - - - - // - - - -
// ■ x x - // ■ x x -
// ■ - x x // ■ - x x
// ■ x - x // ■ x - x
// ■ - x x // ■ - x x
var pattern = new Pattern(); var pattern = new Pattern();
int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int holdColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns) if ((convertType & PatternType.ForceNotStack) > 0 && PreviousPattern.ColumnWithObjects < TotalColumns)
{ {
while (PreviousPattern.ColumnHasObject(holdColumn)) while (PreviousPattern.ColumnHasObject(holdColumn))
holdColumn = Random.Next(RandomStart, TotalColumns); holdColumn = Random.Next(RandomStart, TotalColumns);
} }
// Create the hold note // Create the hold note
addToPattern(pattern, holdColumn, startTime, endTime); addToPattern(pattern, holdColumn, startTime, endTime);
int nextColumn = Random.Next(RandomStart, TotalColumns); int nextColumn = Random.Next(RandomStart, TotalColumns);
int noteCount; int noteCount;
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
noteCount = GetRandomNoteCount(0.63, 0); noteCount = GetRandomNoteCount(0.63, 0);
else if (ConversionDifficulty > 4) else if (ConversionDifficulty > 4)
noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0); noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0);
else if (ConversionDifficulty > 2.5) else if (ConversionDifficulty > 2.5)
noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0); noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0);
else else
noteCount = 0; noteCount = 0;
noteCount = Math.Min(TotalColumns - 1, noteCount); noteCount = Math.Min(TotalColumns - 1, noteCount);
bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP);
var rowPattern = new Pattern(); var rowPattern = new Pattern();
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= spanCount; i++)
{ {
if (!(ignoreHead && startTime == HitObject.StartTime)) if (!(ignoreHead && startTime == HitObject.StartTime))
{ {
for (int j = 0; j < noteCount; j++) for (int j = 0; j < noteCount; j++)
{ {
while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn) while (rowPattern.ColumnHasObject(nextColumn) || nextColumn == holdColumn)
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
addToPattern(rowPattern, nextColumn, startTime, startTime); addToPattern(rowPattern, nextColumn, startTime, startTime);
} }
} }
pattern.Add(rowPattern); pattern.Add(rowPattern);
rowPattern.Clear(); rowPattern.Clear();
startTime += segmentDuration; startTime += segmentDuration;
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Retrieves the sample info list at a point in time. /// Retrieves the sample info list at a point in time.
/// </summary> /// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param> /// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns> /// <returns></returns>
private List<SampleInfo> sampleInfoListAt(double time) private List<SampleInfo> sampleInfoListAt(double time)
{ {
var curveData = HitObject as IHasCurve; var curveData = HitObject as IHasCurve;
if (curveData == null) if (curveData == null)
return HitObject.Samples; return HitObject.Samples;
double segmentTime = (endTime - HitObject.StartTime) / spanCount; double segmentTime = (endTime - HitObject.StartTime) / spanCount;
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.RepeatSamples[index]; return curveData.RepeatSamples[index];
} }
/// <summary> /// <summary>
/// Constructs and adds a note to a pattern. /// Constructs and adds a note to a pattern.
/// </summary> /// </summary>
/// <param name="pattern">The pattern to add to.</param> /// <param name="pattern">The pattern to add to.</param>
/// <param name="column">The column to add the note to.</param> /// <param name="column">The column to add the note to.</param>
/// <param name="startTime">The start time of the note.</param> /// <param name="startTime">The start time of the note.</param>
/// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param> /// <param name="endTime">The end time of the note (set to <paramref name="startTime"/> for a non-hold note).</param>
private void addToPattern(Pattern pattern, int column, double startTime, double endTime) private void addToPattern(Pattern pattern, int column, double startTime, double endTime)
{ {
ManiaHitObject newObject; ManiaHitObject newObject;
if (startTime == endTime) if (startTime == endTime)
{ {
newObject = new Note newObject = new Note
{ {
StartTime = startTime, StartTime = startTime,
Samples = sampleInfoListAt(startTime), Samples = sampleInfoListAt(startTime),
Column = column Column = column
}; };
} }
else else
{ {
var holdNote = new HoldNote var holdNote = new HoldNote
{ {
StartTime = startTime, StartTime = startTime,
Column = column, Column = column,
Duration = endTime - startTime, Duration = endTime - startTime,
Head = { Samples = sampleInfoListAt(startTime) }, Head = { Samples = sampleInfoListAt(startTime) },
Tail = { Samples = sampleInfoListAt(endTime) } Tail = { Samples = sampleInfoListAt(endTime) }
}; };
newObject = holdNote; newObject = holdNote;
} }
pattern.Add(newObject); pattern.Add(newObject);
} }
} }
} }

View File

@ -1,102 +1,102 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
internal class EndTimeObjectPatternGenerator : PatternGenerator internal class EndTimeObjectPatternGenerator : PatternGenerator
{ {
private readonly double endTime; private readonly double endTime;
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap) public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern(), originalBeatmap) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{ {
var endtimeData = HitObject as IHasEndTime; var endtimeData = HitObject as IHasEndTime;
endTime = endtimeData?.EndTime ?? 0; endTime = endtimeData?.EndTime ?? 0;
} }
public override Pattern Generate() public override Pattern Generate()
{ {
var pattern = new Pattern(); var pattern = new Pattern();
bool generateHold = endTime - HitObject.StartTime >= 100; bool generateHold = endTime - HitObject.StartTime >= 100;
if (TotalColumns == 8) if (TotalColumns == 8)
{ {
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000) if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH) && endTime - HitObject.StartTime < 1000)
addToPattern(pattern, 0, generateHold); addToPattern(pattern, 0, generateHold);
else else
addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold); addToPattern(pattern, getNextRandomColumn(RandomStart), generateHold);
} }
else if (TotalColumns > 0) else if (TotalColumns > 0)
addToPattern(pattern, getNextRandomColumn(0), generateHold); addToPattern(pattern, getNextRandomColumn(0), generateHold);
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Picks a random column after a column. /// Picks a random column after a column.
/// </summary> /// </summary>
/// <param name="start">The starting column.</param> /// <param name="start">The starting column.</param>
/// <returns>A random column after <paramref name="start"/>.</returns> /// <returns>A random column after <paramref name="start"/>.</returns>
private int getNextRandomColumn(int start) private int getNextRandomColumn(int start)
{ {
int nextColumn = Random.Next(start, TotalColumns); int nextColumn = Random.Next(start, TotalColumns);
while (PreviousPattern.ColumnHasObject(nextColumn)) while (PreviousPattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(start, TotalColumns); nextColumn = Random.Next(start, TotalColumns);
return nextColumn; return nextColumn;
} }
/// <summary> /// <summary>
/// Constructs and adds a note to a pattern. /// Constructs and adds a note to a pattern.
/// </summary> /// </summary>
/// <param name="pattern">The pattern to add to.</param> /// <param name="pattern">The pattern to add to.</param>
/// <param name="column">The column to add the note to.</param> /// <param name="column">The column to add the note to.</param>
/// <param name="holdNote">Whether to add a hold note.</param> /// <param name="holdNote">Whether to add a hold note.</param>
private void addToPattern(Pattern pattern, int column, bool holdNote) private void addToPattern(Pattern pattern, int column, bool holdNote)
{ {
ManiaHitObject newObject; ManiaHitObject newObject;
if (holdNote) if (holdNote)
{ {
var hold = new HoldNote var hold = new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Column = column, Column = column,
Duration = endTime - HitObject.StartTime Duration = endTime - HitObject.StartTime
}; };
if (hold.Head.Samples == null) if (hold.Head.Samples == null)
hold.Head.Samples = new List<SampleInfo>(); hold.Head.Samples = new List<SampleInfo>();
hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL }); hold.Head.Samples.Add(new SampleInfo { Name = SampleInfo.HIT_NORMAL });
hold.Tail.Samples = HitObject.Samples; hold.Tail.Samples = HitObject.Samples;
newObject = hold; newObject = hold;
} }
else else
{ {
newObject = new Note newObject = new Note
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Samples = HitObject.Samples, Samples = HitObject.Samples,
Column = column Column = column
}; };
} }
pattern.Add(newObject); pattern.Add(newObject);
} }
} }
} }

View File

@ -1,401 +1,401 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq; using System.Linq;
using OpenTK; using OpenTK;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
internal class HitObjectPatternGenerator : PatternGenerator internal class HitObjectPatternGenerator : PatternGenerator
{ {
public PatternType StairType { get; private set; } public PatternType StairType { get; private set; }
private readonly PatternType convertType; private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap) public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern, originalBeatmap) : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{ {
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime)); if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
if (density < 0) throw new ArgumentOutOfRangeException(nameof(density)); if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
StairType = lastStair; StairType = lastStair;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime); EffectControlPoint effectPoint = beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime);
var positionData = hitObject as IHasPosition; var positionData = hitObject as IHasPosition;
float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length; float positionSeparation = ((positionData?.Position ?? Vector2.Zero) - previousPosition).Length;
double timeSeparation = hitObject.StartTime - previousTime; double timeSeparation = hitObject.StartTime - previousTime;
if (timeSeparation <= 80) if (timeSeparation <= 80)
{ {
// More than 187 BPM // More than 187 BPM
convertType |= PatternType.ForceNotStack | PatternType.KeepSingle; convertType |= PatternType.ForceNotStack | PatternType.KeepSingle;
} }
else if (timeSeparation <= 95) else if (timeSeparation <= 95)
{ {
// More than 157 BPM // More than 157 BPM
convertType |= PatternType.ForceNotStack | PatternType.KeepSingle | lastStair; convertType |= PatternType.ForceNotStack | PatternType.KeepSingle | lastStair;
} }
else if (timeSeparation <= 105) else if (timeSeparation <= 105)
{ {
// More than 140 BPM // More than 140 BPM
convertType |= PatternType.ForceNotStack | PatternType.LowProbability; convertType |= PatternType.ForceNotStack | PatternType.LowProbability;
} }
else if (timeSeparation <= 125) else if (timeSeparation <= 125)
{ {
// More than 120 BPM // More than 120 BPM
convertType |= PatternType.ForceNotStack; convertType |= PatternType.ForceNotStack;
} }
else if (timeSeparation <= 135 && positionSeparation < 20) else if (timeSeparation <= 135 && positionSeparation < 20)
{ {
// More than 111 BPM stream // More than 111 BPM stream
convertType |= PatternType.Cycle | PatternType.KeepSingle; convertType |= PatternType.Cycle | PatternType.KeepSingle;
} }
else if (timeSeparation <= 150 && positionSeparation < 20) else if (timeSeparation <= 150 && positionSeparation < 20)
{ {
// More than 100 BPM stream // More than 100 BPM stream
convertType |= PatternType.ForceStack | PatternType.LowProbability; convertType |= PatternType.ForceStack | PatternType.LowProbability;
} }
else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5) else if (positionSeparation < 20 && density >= timingPoint.BeatLength / 2.5)
{ {
// Low density stream // Low density stream
convertType |= PatternType.Reverse | PatternType.LowProbability; convertType |= PatternType.Reverse | PatternType.LowProbability;
} }
else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode) else if (density < timingPoint.BeatLength / 2.5 || effectPoint.KiaiMode)
{ {
// High density // High density
} }
else else
convertType |= PatternType.LowProbability; convertType |= PatternType.LowProbability;
} }
public override Pattern Generate() public override Pattern Generate()
{ {
int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0; int lastColumn = PreviousPattern.HitObjects.FirstOrDefault()?.Column ?? 0;
if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any()) if ((convertType & PatternType.Reverse) > 0 && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by copying the last hit objects in reverse-column order // Generate a new pattern by copying the last hit objects in reverse-column order
var pattern = new Pattern(); var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i)) if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, RandomStart + TotalColumns - i - 1); addToPattern(pattern, RandomStart + TotalColumns - i - 1);
return pattern; return pattern;
} }
if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1 if ((convertType & PatternType.Cycle) > 0 && PreviousPattern.HitObjects.Count() == 1
// If we convert to 7K + 1, let's not overload the special key // If we convert to 7K + 1, let's not overload the special key
&& (TotalColumns != 8 || lastColumn != 0) && (TotalColumns != 8 || lastColumn != 0)
// Make sure the last column was not the centre column // Make sure the last column was not the centre column
&& (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2))
{ {
// Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object) // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object)
var pattern = new Pattern(); var pattern = new Pattern();
int column = RandomStart + TotalColumns - lastColumn - 1; int column = RandomStart + TotalColumns - lastColumn - 1;
addToPattern(pattern, column); addToPattern(pattern, column);
return pattern; return pattern;
} }
if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any()) if ((convertType & PatternType.ForceStack) > 0 && PreviousPattern.HitObjects.Any())
{ {
// Generate a new pattern by placing on the already filled columns // Generate a new pattern by placing on the already filled columns
var pattern = new Pattern(); var pattern = new Pattern();
for (int i = RandomStart; i < TotalColumns; i++) for (int i = RandomStart; i < TotalColumns; i++)
if (PreviousPattern.ColumnHasObject(i)) if (PreviousPattern.ColumnHasObject(i))
addToPattern(pattern, i); addToPattern(pattern, i);
return pattern; return pattern;
} }
if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1) if ((convertType & PatternType.Stair) > 0 && PreviousPattern.HitObjects.Count() == 1)
{ {
// Generate a new pattern by placing on the next column, cycling back to the start if there is no "next" // Generate a new pattern by placing on the next column, cycling back to the start if there is no "next"
var pattern = new Pattern(); var pattern = new Pattern();
int targetColumn = lastColumn + 1; int targetColumn = lastColumn + 1;
if (targetColumn == TotalColumns) if (targetColumn == TotalColumns)
{ {
targetColumn = RandomStart; targetColumn = RandomStart;
StairType = PatternType.ReverseStair; StairType = PatternType.ReverseStair;
} }
addToPattern(pattern, targetColumn); addToPattern(pattern, targetColumn);
return pattern; return pattern;
} }
if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1) if ((convertType & PatternType.ReverseStair) > 0 && PreviousPattern.HitObjects.Count() == 1)
{ {
// Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous" // Generate a new pattern by placing on the previous column, cycling back to the end if there is no "previous"
var pattern = new Pattern(); var pattern = new Pattern();
int targetColumn = lastColumn - 1; int targetColumn = lastColumn - 1;
if (targetColumn == RandomStart - 1) if (targetColumn == RandomStart - 1)
{ {
targetColumn = TotalColumns - 1; targetColumn = TotalColumns - 1;
StairType = PatternType.Stair; StairType = PatternType.Stair;
} }
addToPattern(pattern, targetColumn); addToPattern(pattern, targetColumn);
return pattern; return pattern;
} }
if ((convertType & PatternType.KeepSingle) > 0) if ((convertType & PatternType.KeepSingle) > 0)
return generateRandomNotes(1); return generateRandomNotes(1);
if ((convertType & PatternType.Mirror) > 0) if ((convertType & PatternType.Mirror) > 0)
{ {
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
return generateRandomPatternWithMirrored(0.12, 0.38, 0.12); return generateRandomPatternWithMirrored(0.12, 0.38, 0.12);
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
return generateRandomPatternWithMirrored(0.12, 0.17, 0); return generateRandomPatternWithMirrored(0.12, 0.17, 0);
return generateRandomPatternWithMirrored(0.12, 0, 0); return generateRandomPatternWithMirrored(0.12, 0, 0);
} }
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
{ {
if ((convertType & PatternType.LowProbability) > 0) if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.78, 0.42, 0, 0); return generateRandomPattern(0.78, 0.42, 0, 0);
return generateRandomPattern(1, 0.62, 0, 0); return generateRandomPattern(1, 0.62, 0, 0);
} }
if (ConversionDifficulty > 4) if (ConversionDifficulty > 4)
{ {
if ((convertType & PatternType.LowProbability) > 0) if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.35, 0.08, 0, 0); return generateRandomPattern(0.35, 0.08, 0, 0);
return generateRandomPattern(0.52, 0.15, 0, 0); return generateRandomPattern(0.52, 0.15, 0, 0);
} }
if (ConversionDifficulty > 2) if (ConversionDifficulty > 2)
{ {
if ((convertType & PatternType.LowProbability) > 0) if ((convertType & PatternType.LowProbability) > 0)
return generateRandomPattern(0.18, 0, 0, 0); return generateRandomPattern(0.18, 0, 0, 0);
return generateRandomPattern(0.45, 0, 0, 0); return generateRandomPattern(0.45, 0, 0, 0);
} }
return generateRandomPattern(0, 0, 0, 0); return generateRandomPattern(0, 0, 0, 0);
} }
/// <summary> /// <summary>
/// Generates random notes. /// Generates random notes.
/// <para> /// <para>
/// This will generate as many as it can up to <paramref name="noteCount"/>, accounting for /// This will generate as many as it can up to <paramref name="noteCount"/>, accounting for
/// any stacks if <see cref="convertType"/> is forcing no stacks. /// any stacks if <see cref="convertType"/> is forcing no stacks.
/// </para> /// </para>
/// </summary> /// </summary>
/// <param name="noteCount">The amount of notes to generate.</param> /// <param name="noteCount">The amount of notes to generate.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomNotes(int noteCount) private Pattern generateRandomNotes(int noteCount)
{ {
var pattern = new Pattern(); var pattern = new Pattern();
bool allowStacking = (convertType & PatternType.ForceNotStack) == 0; bool allowStacking = (convertType & PatternType.ForceNotStack) == 0;
if (!allowStacking) if (!allowStacking)
noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects); noteCount = Math.Min(noteCount, TotalColumns - RandomStart - PreviousPattern.ColumnWithObjects);
int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true); int nextColumn = GetColumn((HitObject as IHasXPosition)?.X ?? 0, true);
for (int i = 0; i < noteCount; i++) for (int i = 0; i < noteCount; i++)
{ {
while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking) while (pattern.ColumnHasObject(nextColumn) || PreviousPattern.ColumnHasObject(nextColumn) && !allowStacking)
{ {
if ((convertType & PatternType.Gathered) > 0) if ((convertType & PatternType.Gathered) > 0)
{ {
nextColumn++; nextColumn++;
if (nextColumn == TotalColumns) if (nextColumn == TotalColumns)
nextColumn = RandomStart; nextColumn = RandomStart;
} }
else else
nextColumn = Random.Next(RandomStart, TotalColumns); nextColumn = Random.Next(RandomStart, TotalColumns);
} }
addToPattern(pattern, nextColumn); addToPattern(pattern, nextColumn);
} }
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Whether this hit object can generate a note in the special column. /// Whether this hit object can generate a note in the special column.
/// </summary> /// </summary>
private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH); private bool hasSpecialColumn => HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP) && HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_FINISH);
/// <summary> /// <summary>
/// Generates a random pattern. /// Generates a random pattern.
/// </summary> /// </summary>
/// <param name="p2">Probability for 2 notes to be generated.</param> /// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param> /// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="p4">Probability for 4 notes to be generated.</param> /// <param name="p4">Probability for 4 notes to be generated.</param>
/// <param name="p5">Probability for 5 notes to be generated.</param> /// <param name="p5">Probability for 5 notes to be generated.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomPattern(double p2, double p3, double p4, double p5) private Pattern generateRandomPattern(double p2, double p3, double p4, double p5)
{ {
var pattern = new Pattern(); var pattern = new Pattern();
pattern.Add(generateRandomNotes(getRandomNoteCount(p2, p3, p4, p5))); pattern.Add(generateRandomNotes(getRandomNoteCount(p2, p3, p4, p5)));
if (RandomStart > 0 && hasSpecialColumn) if (RandomStart > 0 && hasSpecialColumn)
addToPattern(pattern, 0); addToPattern(pattern, 0);
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Generates a random pattern which has both normal and mirrored notes. /// Generates a random pattern which has both normal and mirrored notes.
/// </summary> /// </summary>
/// <param name="centreProbability">The probability for a note to be added to the centre column.</param> /// <param name="centreProbability">The probability for a note to be added to the centre column.</param>
/// <param name="p2">Probability for 2 notes to be generated.</param> /// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param> /// <param name="p3">Probability for 3 notes to be generated.</param>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3) private Pattern generateRandomPatternWithMirrored(double centreProbability, double p2, double p3)
{ {
var pattern = new Pattern(); var pattern = new Pattern();
bool addToCentre; bool addToCentre;
int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre); int noteCount = getRandomNoteCountMirrored(centreProbability, p2, p3, out addToCentre);
int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2; int columnLimit = (TotalColumns % 2 == 0 ? TotalColumns : TotalColumns - 1) / 2;
int nextColumn = Random.Next(RandomStart, columnLimit); int nextColumn = Random.Next(RandomStart, columnLimit);
for (int i = 0; i < noteCount; i++) for (int i = 0; i < noteCount; i++)
{ {
while (pattern.ColumnHasObject(nextColumn)) while (pattern.ColumnHasObject(nextColumn))
nextColumn = Random.Next(RandomStart, columnLimit); nextColumn = Random.Next(RandomStart, columnLimit);
// Add normal note // Add normal note
addToPattern(pattern, nextColumn); addToPattern(pattern, nextColumn);
// Add mirrored note // Add mirrored note
addToPattern(pattern, RandomStart + TotalColumns - nextColumn - 1); addToPattern(pattern, RandomStart + TotalColumns - nextColumn - 1);
} }
if (addToCentre) if (addToCentre)
addToPattern(pattern, TotalColumns / 2); addToPattern(pattern, TotalColumns / 2);
if (RandomStart > 0 && hasSpecialColumn) if (RandomStart > 0 && hasSpecialColumn)
addToPattern(pattern, 0); addToPattern(pattern, 0);
return pattern; return pattern;
} }
/// <summary> /// <summary>
/// Generates a count of notes to be generated from a list of probabilities. /// Generates a count of notes to be generated from a list of probabilities.
/// </summary> /// </summary>
/// <param name="p2">Probability for 2 notes to be generated.</param> /// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param> /// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="p4">Probability for 4 notes to be generated.</param> /// <param name="p4">Probability for 4 notes to be generated.</param>
/// <param name="p5">Probability for 5 notes to be generated.</param> /// <param name="p5">Probability for 5 notes to be generated.</param>
/// <returns>The amount of notes to be generated.</returns> /// <returns>The amount of notes to be generated.</returns>
private int getRandomNoteCount(double p2, double p3, double p4, double p5) private int getRandomNoteCount(double p2, double p3, double p4, double p5)
{ {
switch (TotalColumns) switch (TotalColumns)
{ {
case 2: case 2:
p2 = 0; p2 = 0;
p3 = 0; p3 = 0;
p4 = 0; p4 = 0;
p5 = 0; p5 = 0;
break; break;
case 3: case 3:
p2 = Math.Min(p2, 0.1); p2 = Math.Min(p2, 0.1);
p3 = 0; p3 = 0;
p4 = 0; p4 = 0;
p5 = 0; p5 = 0;
break; break;
case 4: case 4:
p2 = Math.Min(p2, 0.23); p2 = Math.Min(p2, 0.23);
p3 = Math.Min(p3, 0.04); p3 = Math.Min(p3, 0.04);
p4 = 0; p4 = 0;
p5 = 0; p5 = 0;
break; break;
case 5: case 5:
p3 = Math.Min(p3, 0.15); p3 = Math.Min(p3, 0.15);
p4 = Math.Min(p4, 0.03); p4 = Math.Min(p4, 0.03);
p5 = 0; p5 = 0;
break; break;
} }
if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP)) if (HitObject.Samples.Any(s => s.Name == SampleInfo.HIT_CLAP))
p2 = 1; p2 = 1;
return GetRandomNoteCount(p2, p3, p4, p5); return GetRandomNoteCount(p2, p3, p4, p5);
} }
/// <summary> /// <summary>
/// Generates a count of notes to be generated from a list of probabilities. /// Generates a count of notes to be generated from a list of probabilities.
/// </summary> /// </summary>
/// <param name="centreProbability">The probability for a note to be added to the centre column.</param> /// <param name="centreProbability">The probability for a note to be added to the centre column.</param>
/// <param name="p2">Probability for 2 notes to be generated.</param> /// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param> /// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="addToCentre">Whether to add a note to the centre column.</param> /// <param name="addToCentre">Whether to add a note to the centre column.</param>
/// <returns>The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count.</returns> /// <returns>The amount of notes to be generated. The note to be added to the centre column will NOT be part of this count.</returns>
private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre) private int getRandomNoteCountMirrored(double centreProbability, double p2, double p3, out bool addToCentre)
{ {
addToCentre = false; addToCentre = false;
if ((convertType & PatternType.ForceNotStack) > 0) if ((convertType & PatternType.ForceNotStack) > 0)
return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3); return getRandomNoteCount(p2 / 2, p2, (p2 + p3) / 2, p3);
switch (TotalColumns) switch (TotalColumns)
{ {
case 2: case 2:
centreProbability = 0; centreProbability = 0;
p2 = 0; p2 = 0;
p3 = 0; p3 = 0;
break; break;
case 3: case 3:
centreProbability = Math.Min(centreProbability, 0.03); centreProbability = Math.Min(centreProbability, 0.03);
p2 = 0; p2 = 0;
p3 = 0; p3 = 0;
break; break;
case 4: case 4:
centreProbability = 0; centreProbability = 0;
p2 = Math.Min(p2 * 2, 0.2); p2 = Math.Min(p2 * 2, 0.2);
p3 = 0; p3 = 0;
break; break;
case 5: case 5:
centreProbability = Math.Min(centreProbability, 0.03); centreProbability = Math.Min(centreProbability, 0.03);
p3 = 0; p3 = 0;
break; break;
case 6: case 6:
centreProbability = 0; centreProbability = 0;
p2 = Math.Min(p2 * 2, 0.5); p2 = Math.Min(p2 * 2, 0.5);
p3 = Math.Min(p3 * 2, 0.15); p3 = Math.Min(p3 * 2, 0.15);
break; break;
} }
double centreVal = Random.NextDouble(); double centreVal = Random.NextDouble();
int noteCount = GetRandomNoteCount(p2, p3); int noteCount = GetRandomNoteCount(p2, p3);
addToCentre = TotalColumns % 2 != 0 && noteCount != 3 && centreVal > 1 - centreProbability; addToCentre = TotalColumns % 2 != 0 && noteCount != 3 && centreVal > 1 - centreProbability;
return noteCount; return noteCount;
} }
/// <summary> /// <summary>
/// Constructs and adds a note to a pattern. /// Constructs and adds a note to a pattern.
/// </summary> /// </summary>
/// <param name="pattern">The pattern to add to.</param> /// <param name="pattern">The pattern to add to.</param>
/// <param name="column">The column to add the note to.</param> /// <param name="column">The column to add the note to.</param>
private void addToPattern(Pattern pattern, int column) private void addToPattern(Pattern pattern, int column)
{ {
pattern.Add(new Note pattern.Add(new Note
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Samples = HitObject.Samples, Samples = HitObject.Samples,
Column = column Column = column
}); });
} }
} }
} }

View File

@ -1,123 +1,123 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Linq; using System.Linq;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using OpenTK; using OpenTK;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
/// <summary> /// <summary>
/// A pattern generator for legacy hit objects. /// A pattern generator for legacy hit objects.
/// </summary> /// </summary>
internal abstract class PatternGenerator : Patterns.PatternGenerator internal abstract class PatternGenerator : Patterns.PatternGenerator
{ {
/// <summary> /// <summary>
/// The column index at which to start generating random notes. /// The column index at which to start generating random notes.
/// </summary> /// </summary>
protected readonly int RandomStart; protected readonly int RandomStart;
/// <summary> /// <summary>
/// The random number generator to use. /// The random number generator to use.
/// </summary> /// </summary>
protected readonly FastRandom Random; protected readonly FastRandom Random;
/// <summary> /// <summary>
/// The beatmap which <see cref="HitObject"/> is being converted from. /// The beatmap which <see cref="HitObject"/> is being converted from.
/// </summary> /// </summary>
protected readonly Beatmap OriginalBeatmap; protected readonly Beatmap OriginalBeatmap;
protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap) protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
: base(hitObject, beatmap, previousPattern) : base(hitObject, beatmap, previousPattern)
{ {
if (random == null) throw new ArgumentNullException(nameof(random)); if (random == null) throw new ArgumentNullException(nameof(random));
if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap)); if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap));
Random = random; Random = random;
OriginalBeatmap = originalBeatmap; OriginalBeatmap = originalBeatmap;
RandomStart = TotalColumns == 8 ? 1 : 0; RandomStart = TotalColumns == 8 ? 1 : 0;
} }
/// <summary> /// <summary>
/// Converts an x-position into a column. /// Converts an x-position into a column.
/// </summary> /// </summary>
/// <param name="position">The x-position.</param> /// <param name="position">The x-position.</param>
/// <param name="allowSpecial">Whether to treat as 7K + 1.</param> /// <param name="allowSpecial">Whether to treat as 7K + 1.</param>
/// <returns>The column.</returns> /// <returns>The column.</returns>
protected int GetColumn(float position, bool allowSpecial = false) protected int GetColumn(float position, bool allowSpecial = false)
{ {
if (allowSpecial && TotalColumns == 8) if (allowSpecial && TotalColumns == 8)
{ {
const float local_x_divisor = 512f / 7; const float local_x_divisor = 512f / 7;
return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1; return MathHelper.Clamp((int)Math.Floor(position / local_x_divisor), 0, 6) + 1;
} }
float localXDivisor = 512f / TotalColumns; float localXDivisor = 512f / TotalColumns;
return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1); return MathHelper.Clamp((int)Math.Floor(position / localXDivisor), 0, TotalColumns - 1);
} }
/// <summary> /// <summary>
/// Generates a count of notes to be generated from probabilities. /// Generates a count of notes to be generated from probabilities.
/// </summary> /// </summary>
/// <param name="p2">Probability for 2 notes to be generated.</param> /// <param name="p2">Probability for 2 notes to be generated.</param>
/// <param name="p3">Probability for 3 notes to be generated.</param> /// <param name="p3">Probability for 3 notes to be generated.</param>
/// <param name="p4">Probability for 4 notes to be generated.</param> /// <param name="p4">Probability for 4 notes to be generated.</param>
/// <param name="p5">Probability for 5 notes to be generated.</param> /// <param name="p5">Probability for 5 notes to be generated.</param>
/// <param name="p6">Probability for 6 notes to be generated.</param> /// <param name="p6">Probability for 6 notes to be generated.</param>
/// <returns>The amount of notes to be generated.</returns> /// <returns>The amount of notes to be generated.</returns>
protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0) protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0)
{ {
if (p2 < 0 || p2 > 1) throw new ArgumentOutOfRangeException(nameof(p2)); if (p2 < 0 || p2 > 1) throw new ArgumentOutOfRangeException(nameof(p2));
if (p3 < 0 || p3 > 1) throw new ArgumentOutOfRangeException(nameof(p3)); if (p3 < 0 || p3 > 1) throw new ArgumentOutOfRangeException(nameof(p3));
if (p4 < 0 || p4 > 1) throw new ArgumentOutOfRangeException(nameof(p4)); if (p4 < 0 || p4 > 1) throw new ArgumentOutOfRangeException(nameof(p4));
if (p5 < 0 || p5 > 1) throw new ArgumentOutOfRangeException(nameof(p5)); if (p5 < 0 || p5 > 1) throw new ArgumentOutOfRangeException(nameof(p5));
if (p6 < 0 || p6 > 1) throw new ArgumentOutOfRangeException(nameof(p6)); if (p6 < 0 || p6 > 1) throw new ArgumentOutOfRangeException(nameof(p6));
double val = Random.NextDouble(); double val = Random.NextDouble();
if (val >= 1 - p6) if (val >= 1 - p6)
return 6; return 6;
if (val >= 1 - p5) if (val >= 1 - p5)
return 5; return 5;
if (val >= 1 - p4) if (val >= 1 - p4)
return 4; return 4;
if (val >= 1 - p3) if (val >= 1 - p3)
return 3; return 3;
return val >= 1 - p2 ? 2 : 1; return val >= 1 - p2 ? 2 : 1;
} }
private double? conversionDifficulty; private double? conversionDifficulty;
/// <summary> /// <summary>
/// A difficulty factor used for various conversion methods from osu!stable. /// A difficulty factor used for various conversion methods from osu!stable.
/// </summary> /// </summary>
protected double ConversionDifficulty protected double ConversionDifficulty
{ {
get get
{ {
if (conversionDifficulty != null) if (conversionDifficulty != null)
return conversionDifficulty.Value; return conversionDifficulty.Value;
HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault(); HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault();
HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault(); HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
drainTime -= OriginalBeatmap.TotalBreakTime; drainTime -= OriginalBeatmap.TotalBreakTime;
if (drainTime == 0) if (drainTime == 0)
drainTime = 10000000; drainTime = 10000000;
// We need this in seconds // We need this in seconds
drainTime /= 1000; drainTime /= 1000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value; return conversionDifficulty.Value;
} }
} }
} }
} }

View File

@ -1,65 +1,65 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
/// <summary> /// <summary>
/// The type of pattern to generate. Used for legacy patterns. /// The type of pattern to generate. Used for legacy patterns.
/// </summary> /// </summary>
[Flags] [Flags]
internal enum PatternType internal enum PatternType
{ {
None = 0, None = 0,
/// <summary> /// <summary>
/// Keep the same as last row. /// Keep the same as last row.
/// </summary> /// </summary>
ForceStack = 1 << 0, ForceStack = 1 << 0,
/// <summary> /// <summary>
/// Keep different from last row. /// Keep different from last row.
/// </summary> /// </summary>
ForceNotStack = 1 << 1, ForceNotStack = 1 << 1,
/// <summary> /// <summary>
/// Keep as single note at its original position. /// Keep as single note at its original position.
/// </summary> /// </summary>
KeepSingle = 1 << 2, KeepSingle = 1 << 2,
/// <summary> /// <summary>
/// Use a lower random value. /// Use a lower random value.
/// </summary> /// </summary>
LowProbability = 1 << 3, LowProbability = 1 << 3,
/// <summary> /// <summary>
/// Reserved. /// Reserved.
/// </summary> /// </summary>
Alternate = 1 << 4, Alternate = 1 << 4,
/// <summary> /// <summary>
/// Ignore the repeat count. /// Ignore the repeat count.
/// </summary> /// </summary>
ForceSigSlider = 1 << 5, ForceSigSlider = 1 << 5,
/// <summary> /// <summary>
/// Convert slider to circle. /// Convert slider to circle.
/// </summary> /// </summary>
ForceNotSlider = 1 << 6, ForceNotSlider = 1 << 6,
/// <summary> /// <summary>
/// Notes gathered together. /// Notes gathered together.
/// </summary> /// </summary>
Gathered = 1 << 7, Gathered = 1 << 7,
Mirror = 1 << 8, Mirror = 1 << 8,
/// <summary> /// <summary>
/// Change 0 -> 6. /// Change 0 -> 6.
/// </summary> /// </summary>
Reverse = 1 << 9, Reverse = 1 << 9,
/// <summary> /// <summary>
/// 1 -> 5 -> 1 -> 5 like reverse. /// 1 -> 5 -> 1 -> 5 like reverse.
/// </summary> /// </summary>
Cycle = 1 << 10, Cycle = 1 << 10,
/// <summary> /// <summary>
/// Next note will be at column + 1. /// Next note will be at column + 1.
/// </summary> /// </summary>
Stair = 1 << 11, Stair = 1 << 11,
/// <summary> /// <summary>
/// Next note will be at column - 1. /// Next note will be at column - 1.
/// </summary> /// </summary>
ReverseStair = 1 << 12 ReverseStair = 1 << 12
} }
} }

View File

@ -1,57 +1,57 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
{ {
/// <summary> /// <summary>
/// Creates a pattern containing hit objects. /// Creates a pattern containing hit objects.
/// </summary> /// </summary>
internal class Pattern internal class Pattern
{ {
private readonly List<ManiaHitObject> hitObjects = new List<ManiaHitObject>(); private readonly List<ManiaHitObject> hitObjects = new List<ManiaHitObject>();
/// <summary> /// <summary>
/// All the hit objects contained in this pattern. /// All the hit objects contained in this pattern.
/// </summary> /// </summary>
public IEnumerable<ManiaHitObject> HitObjects => hitObjects; public IEnumerable<ManiaHitObject> HitObjects => hitObjects;
/// <summary> /// <summary>
/// Check whether a column of this patterns contains a hit object. /// Check whether a column of this patterns contains a hit object.
/// </summary> /// </summary>
/// <param name="column">The column index.</param> /// <param name="column">The column index.</param>
/// <returns>Whether the column with index <paramref name="column"/> contains a hit object.</returns> /// <returns>Whether the column with index <paramref name="column"/> contains a hit object.</returns>
public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column); public bool ColumnHasObject(int column) => hitObjects.Exists(h => h.Column == column);
/// <summary> /// <summary>
/// Amount of columns taken up by hit objects in this pattern. /// Amount of columns taken up by hit objects in this pattern.
/// </summary> /// </summary>
public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count(); public int ColumnWithObjects => HitObjects.GroupBy(h => h.Column).Count();
/// <summary> /// <summary>
/// Adds a hit object to this pattern. /// Adds a hit object to this pattern.
/// </summary> /// </summary>
/// <param name="hitObject">The hit object to add.</param> /// <param name="hitObject">The hit object to add.</param>
public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject); public void Add(ManiaHitObject hitObject) => hitObjects.Add(hitObject);
/// <summary> /// <summary>
/// Copies hit object from another pattern to this one. /// Copies hit object from another pattern to this one.
/// </summary> /// </summary>
/// <param name="other">The other pattern.</param> /// <param name="other">The other pattern.</param>
public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects); public void Add(Pattern other) => hitObjects.AddRange(other.HitObjects);
/// <summary> /// <summary>
/// Clears this pattern, removing all hit objects. /// Clears this pattern, removing all hit objects.
/// </summary> /// </summary>
public void Clear() => hitObjects.Clear(); public void Clear() => hitObjects.Clear();
/// <summary> /// <summary>
/// Removes a hit object from this pattern. /// Removes a hit object from this pattern.
/// </summary> /// </summary>
/// <param name="hitObject">The hit object to remove.</param> /// <param name="hitObject">The hit object to remove.</param>
public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject); public bool Remove(ManiaHitObject hitObject) => hitObjects.Remove(hitObject);
} }
} }

View File

@ -1,50 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
{ {
/// <summary> /// <summary>
/// Generator to create a pattern <see cref="Pattern"/> from a hit object. /// Generator to create a pattern <see cref="Pattern"/> from a hit object.
/// </summary> /// </summary>
internal abstract class PatternGenerator internal abstract class PatternGenerator
{ {
/// <summary> /// <summary>
/// The last pattern. /// The last pattern.
/// </summary> /// </summary>
protected readonly Pattern PreviousPattern; protected readonly Pattern PreviousPattern;
/// <summary> /// <summary>
/// The hit object to create the pattern for. /// The hit object to create the pattern for.
/// </summary> /// </summary>
protected readonly HitObject HitObject; protected readonly HitObject HitObject;
/// <summary> /// <summary>
/// The beatmap which <see cref="HitObject"/> is a part of. /// The beatmap which <see cref="HitObject"/> is a part of.
/// </summary> /// </summary>
protected readonly ManiaBeatmap Beatmap; protected readonly ManiaBeatmap Beatmap;
protected readonly int TotalColumns; protected readonly int TotalColumns;
protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern)
{ {
if (hitObject == null) throw new ArgumentNullException(nameof(hitObject)); if (hitObject == null) throw new ArgumentNullException(nameof(hitObject));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern)); if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
HitObject = hitObject; HitObject = hitObject;
Beatmap = beatmap; Beatmap = beatmap;
PreviousPattern = previousPattern; PreviousPattern = previousPattern;
TotalColumns = Beatmap.TotalColumns; TotalColumns = Beatmap.TotalColumns;
} }
/// <summary> /// <summary>
/// Generates the pattern for <see cref="HitObject"/>, filled with hit objects. /// Generates the pattern for <see cref="HitObject"/>, filled with hit objects.
/// </summary> /// </summary>
/// <returns>The <see cref="Pattern"/> containing the hit objects.</returns> /// <returns>The <see cref="Pattern"/> containing the hit objects.</returns>
public abstract Pattern Generate(); public abstract Pattern Generate();
} }
} }

View File

@ -1,25 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
/// <summary> /// <summary>
/// Defines properties for each stage in a <see cref="ManiaPlayfield"/>. /// Defines properties for each stage in a <see cref="ManiaPlayfield"/>.
/// </summary> /// </summary>
public struct StageDefinition public struct StageDefinition
{ {
/// <summary> /// <summary>
/// The number of <see cref="Column"/>s which this stage contains. /// The number of <see cref="Column"/>s which this stage contains.
/// </summary> /// </summary>
public int Columns; public int Columns;
/// <summary> /// <summary>
/// Whether the column index is a special column for this stage. /// Whether the column index is a special column for this stage.
/// </summary> /// </summary>
/// <param name="column">The 0-based column index.</param> /// <param name="column">The 0-based column index.</param>
/// <returns>Whether the column is a special column.</returns> /// <returns>Whether the column is a special column.</returns>
public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2; public bool IsSpecialColumn(int column) => Columns % 2 == 1 && column == Columns / 2;
} }
} }

View File

@ -1,34 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration.Tracking; using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
namespace osu.Game.Rulesets.Mania.Configuration namespace osu.Game.Rulesets.Mania.Configuration
{ {
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting> public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
{ {
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant)
: base(settings, ruleset, variant) : base(settings, ruleset, variant)
{ {
} }
protected override void InitialiseDefaults() protected override void InitialiseDefaults()
{ {
base.InitialiseDefaults(); base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0); Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
} }
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{ {
new TrackedSetting<double>(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms")) new TrackedSetting<double>(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
}; };
} }
public enum ManiaSetting public enum ManiaSetting
{ {
ScrollTime ScrollTime
} }
} }

View File

@ -1,27 +1,27 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {
public class HoldNoteTailJudgement : ManiaJudgement public class HoldNoteTailJudgement : ManiaJudgement
{ {
/// <summary> /// <summary>
/// Whether the hold note has been released too early and shouldn't give full score for the release. /// Whether the hold note has been released too early and shouldn't give full score for the release.
/// </summary> /// </summary>
public bool HasBroken; public bool HasBroken;
protected override int NumericResultFor(HitResult result) protected override int NumericResultFor(HitResult result)
{ {
switch (result) switch (result)
{ {
default: default:
return base.NumericResultFor(result); return base.NumericResultFor(result);
case HitResult.Great: case HitResult.Great:
case HitResult.Perfect: case HitResult.Perfect:
return base.NumericResultFor(HasBroken ? HitResult.Good : result); return base.NumericResultFor(HasBroken ? HitResult.Good : result);
} }
} }
} }
} }

View File

@ -1,14 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {
public class HoldNoteTickJudgement : ManiaJudgement public class HoldNoteTickJudgement : ManiaJudgement
{ {
public override bool AffectsCombo => false; public override bool AffectsCombo => false;
protected override int NumericResultFor(HitResult result) => 20; protected override int NumericResultFor(HitResult result) => 20;
} }
} }

View File

@ -1,29 +1,29 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Judgements namespace osu.Game.Rulesets.Mania.Judgements
{ {
public class ManiaJudgement : Judgement public class ManiaJudgement : Judgement
{ {
protected override int NumericResultFor(HitResult result) protected override int NumericResultFor(HitResult result)
{ {
switch (result) switch (result)
{ {
default: default:
return 0; return 0;
case HitResult.Meh: case HitResult.Meh:
return 50; return 50;
case HitResult.Ok: case HitResult.Ok:
return 100; return 100;
case HitResult.Good: case HitResult.Good:
return 200; return 200;
case HitResult.Great: case HitResult.Great:
case HitResult.Perfect: case HitResult.Perfect:
return 300; return 300;
} }
} }
} }
} }

View File

@ -1,146 +1,146 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
{ {
internal class ManiaDifficultyCalculator : DifficultyCalculator<ManiaHitObject> internal class ManiaDifficultyCalculator : DifficultyCalculator<ManiaHitObject>
{ {
private const double star_scaling_factor = 0.018; private const double star_scaling_factor = 0.018;
/// <summary> /// <summary>
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step. /// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size strain_step.
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain. /// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage. /// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
/// </summary> /// </summary>
private const double strain_step = 400; private const double strain_step = 400;
/// <summary> /// <summary>
/// The weighting of each strain value decays to this number * it's previous value /// The weighting of each strain value decays to this number * it's previous value
/// </summary> /// </summary>
private const double decay_weight = 0.9; private const double decay_weight = 0.9;
/// <summary> /// <summary>
/// HitObjects are stored as a member variable. /// HitObjects are stored as a member variable.
/// </summary> /// </summary>
private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>(); private readonly List<ManiaHitObjectDifficulty> difficultyHitObjects = new List<ManiaHitObjectDifficulty>();
public ManiaDifficultyCalculator(Beatmap beatmap) public ManiaDifficultyCalculator(Beatmap beatmap)
: base(beatmap) : base(beatmap)
{ {
} }
public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods) public ManiaDifficultyCalculator(Beatmap beatmap, Mod[] mods)
: base(beatmap, mods) : base(beatmap, mods)
{ {
} }
public override double Calculate(Dictionary<string, double> categoryDifficulty = null) public override double Calculate(Dictionary<string, double> categoryDifficulty = null)
{ {
// Fill our custom DifficultyHitObject class, that carries additional information // Fill our custom DifficultyHitObject class, that carries additional information
difficultyHitObjects.Clear(); difficultyHitObjects.Clear();
int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7; int columnCount = (Beatmap as ManiaBeatmap)?.TotalColumns ?? 7;
foreach (var hitObject in Beatmap.HitObjects) foreach (var hitObject in Beatmap.HitObjects)
difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount)); difficultyHitObjects.Add(new ManiaHitObjectDifficulty(hitObject, columnCount));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure. // Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime)); difficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!calculateStrainValues()) if (!calculateStrainValues())
return 0; return 0;
double starRating = calculateDifficulty() * star_scaling_factor; double starRating = calculateDifficulty() * star_scaling_factor;
categoryDifficulty?.Add("Strain", starRating); categoryDifficulty?.Add("Strain", starRating);
return starRating; return starRating;
} }
private bool calculateStrainValues() private bool calculateStrainValues()
{ {
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment. // Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
using (List<ManiaHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator()) using (List<ManiaHitObjectDifficulty>.Enumerator hitObjectsEnumerator = difficultyHitObjects.GetEnumerator())
{ {
if (!hitObjectsEnumerator.MoveNext()) if (!hitObjectsEnumerator.MoveNext())
return false; return false;
ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current; ManiaHitObjectDifficulty current = hitObjectsEnumerator.Current;
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject. // First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
while (hitObjectsEnumerator.MoveNext()) while (hitObjectsEnumerator.MoveNext())
{ {
var next = hitObjectsEnumerator.Current; var next = hitObjectsEnumerator.Current;
next?.CalculateStrains(current, TimeRate); next?.CalculateStrains(current, TimeRate);
current = next; current = next;
} }
return true; return true;
} }
} }
private double calculateDifficulty() private double calculateDifficulty()
{ {
double actualStrainStep = strain_step * TimeRate; double actualStrainStep = strain_step * TimeRate;
// Find the highest strain value within each strain step // Find the highest strain value within each strain step
List<double> highestStrains = new List<double>(); List<double> highestStrains = new List<double>();
double intervalEndTime = actualStrainStep; double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
ManiaHitObjectDifficulty previousHitObject = null; ManiaHitObjectDifficulty previousHitObject = null;
foreach (var hitObject in difficultyHitObjects) foreach (var hitObject in difficultyHitObjects)
{ {
// While we are beyond the current interval push the currently available maximum to our strain list // While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime) while (hitObject.BaseHitObject.StartTime > intervalEndTime)
{ {
highestStrains.Add(maximumStrain); highestStrains.Add(maximumStrain);
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay // The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
// until the beginning of the next interval. // until the beginning of the next interval.
if (previousHitObject == null) if (previousHitObject == null)
{ {
maximumStrain = 0; maximumStrain = 0;
} }
else else
{ {
double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); double individualDecay = Math.Pow(ManiaHitObjectDifficulty.INDIVIDUAL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000); double overallDecay = Math.Pow(ManiaHitObjectDifficulty.OVERALL_DECAY_BASE, (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay; maximumStrain = previousHitObject.IndividualStrain * individualDecay + previousHitObject.OverallStrain * overallDecay;
} }
// Go to the next time interval // Go to the next time interval
intervalEndTime += actualStrainStep; intervalEndTime += actualStrainStep;
} }
// Obtain maximum strain // Obtain maximum strain
double strain = hitObject.IndividualStrain + hitObject.OverallStrain; double strain = hitObject.IndividualStrain + hitObject.OverallStrain;
maximumStrain = Math.Max(strain, maximumStrain); maximumStrain = Math.Max(strain, maximumStrain);
previousHitObject = hitObject; previousHitObject = hitObject;
} }
// Build the weighted sum over the highest strains for each interval // Build the weighted sum over the highest strains for each interval
double difficulty = 0; double difficulty = 0;
double weight = 1; double weight = 1;
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain. highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
foreach (double strain in highestStrains) foreach (double strain in highestStrains)
{ {
difficulty += weight * strain; difficulty += weight * strain;
weight *= decay_weight; weight *= decay_weight;
} }
return difficulty; return difficulty;
} }
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter(Beatmap beatmap) => new ManiaBeatmapConverter(true, beatmap);
} }
} }

View File

@ -1,64 +1,64 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
{ {
public class ManiaInputManager : RulesetInputManager<ManiaAction> public class ManiaInputManager : RulesetInputManager<ManiaAction>
{ {
public ManiaInputManager(RulesetInfo ruleset, int variant) public ManiaInputManager(RulesetInfo ruleset, int variant)
: base(ruleset, variant, SimultaneousBindingMode.Unique) : base(ruleset, variant, SimultaneousBindingMode.Unique)
{ {
} }
} }
public enum ManiaAction public enum ManiaAction
{ {
[Description("Special 1")] [Description("Special 1")]
Special1 = 1, Special1 = 1,
[Description("Special 2")] [Description("Special 2")]
Special2, Special2,
// This offsets the start value of normal keys in-case we add more special keys // This offsets the start value of normal keys in-case we add more special keys
// above at a later time, without breaking replays/configs. // above at a later time, without breaking replays/configs.
[Description("Key 1")] [Description("Key 1")]
Key1 = 10, Key1 = 10,
[Description("Key 2")] [Description("Key 2")]
Key2, Key2,
[Description("Key 3")] [Description("Key 3")]
Key3, Key3,
[Description("Key 4")] [Description("Key 4")]
Key4, Key4,
[Description("Key 5")] [Description("Key 5")]
Key5, Key5,
[Description("Key 6")] [Description("Key 6")]
Key6, Key6,
[Description("Key 7")] [Description("Key 7")]
Key7, Key7,
[Description("Key 8")] [Description("Key 8")]
Key8, Key8,
[Description("Key 9")] [Description("Key 9")]
Key9, Key9,
[Description("Key 10")] [Description("Key 10")]
Key10, Key10,
[Description("Key 11")] [Description("Key 11")]
Key11, Key11,
[Description("Key 12")] [Description("Key 12")]
Key12, Key12,
[Description("Key 13")] [Description("Key 13")]
Key13, Key13,
[Description("Key 14")] [Description("Key 14")]
Key14, Key14,
[Description("Key 15")] [Description("Key 15")]
Key15, Key15,
[Description("Key 16")] [Description("Key 16")]
Key16, Key16,
[Description("Key 17")] [Description("Key 17")]
Key17, Key17,
[Description("Key 18")] [Description("Key 18")]
Key18, Key18,
} }
} }

View File

@ -1,312 +1,381 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Rulesets.Mania
{ namespace osu.Game.Rulesets.Mania
public class ManiaRuleset : Ruleset {
{ public class ManiaRuleset : Ruleset
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset); {
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new ManiaRulesetContainer(this, beatmap, isForCurrentRuleset);
public override IEnumerable<Mod> GetModsFor(ModType type)
{ public override IEnumerable<Mod> ConvertLegacyMods(LegacyMods mods)
switch (type) {
{ if (mods.HasFlag(LegacyMods.Nightcore))
case ModType.DifficultyReduction: yield return new ManiaModNightcore();
return new Mod[] else if (mods.HasFlag(LegacyMods.DoubleTime))
{ yield return new ManiaModDoubleTime();
new ManiaModEasy(),
new ManiaModNoFail(), if (mods.HasFlag(LegacyMods.Autoplay))
new MultiMod yield return new ManiaModAutoplay();
{
Mods = new Mod[] if (mods.HasFlag(LegacyMods.Easy))
{ yield return new ManiaModEasy();
new ManiaModHalfTime(),
new ManiaModDaycore(), if (mods.HasFlag(LegacyMods.FadeIn))
}, yield return new ManiaModFadeIn();
},
}; if (mods.HasFlag(LegacyMods.Flashlight))
yield return new ManiaModFlashlight();
case ModType.DifficultyIncrease:
return new Mod[] if (mods.HasFlag(LegacyMods.HalfTime))
{ yield return new ManiaModHalfTime();
new ManiaModHardRock(),
new MultiMod if (mods.HasFlag(LegacyMods.HardRock))
{ yield return new ManiaModHardRock();
Mods = new Mod[]
{ if (mods.HasFlag(LegacyMods.Hidden))
new ManiaModSuddenDeath(), yield return new ManiaModHidden();
new ManiaModPerfect(),
}, if (mods.HasFlag(LegacyMods.Key1))
}, yield return new ManiaModKey1();
new MultiMod
{ if (mods.HasFlag(LegacyMods.Key2))
Mods = new Mod[] yield return new ManiaModKey2();
{
new ManiaModDoubleTime(), if (mods.HasFlag(LegacyMods.Key3))
new ManiaModNightcore(), yield return new ManiaModKey3();
},
}, if (mods.HasFlag(LegacyMods.Key4))
new MultiMod yield return new ManiaModKey4();
{
Mods = new Mod[] if (mods.HasFlag(LegacyMods.Key5))
{ yield return new ManiaModKey5();
new ManiaModFadeIn(),
new ManiaModHidden(), if (mods.HasFlag(LegacyMods.Key6))
} yield return new ManiaModKey6();
},
new ManiaModFlashlight(), if (mods.HasFlag(LegacyMods.Key7))
}; yield return new ManiaModKey7();
case ModType.Special: if (mods.HasFlag(LegacyMods.Key8))
return new Mod[] yield return new ManiaModKey8();
{
new MultiMod if (mods.HasFlag(LegacyMods.Key9))
{ yield return new ManiaModKey9();
Mods = new Mod[]
{ if (mods.HasFlag(LegacyMods.NoFail))
new ManiaModKey4(), yield return new ManiaModNoFail();
new ManiaModKey5(),
new ManiaModKey6(), if (mods.HasFlag(LegacyMods.Perfect))
new ManiaModKey7(), yield return new ManiaModPerfect();
new ManiaModKey8(),
new ManiaModKey9(), if (mods.HasFlag(LegacyMods.Random))
new ManiaModKey1(), yield return new ManiaModRandom();
new ManiaModKey2(),
new ManiaModKey3(), if (mods.HasFlag(LegacyMods.SuddenDeath))
}, yield return new ManiaModSuddenDeath();
}, }
new ManiaModRandom(),
new ManiaModDualStages(), public override IEnumerable<Mod> GetModsFor(ModType type)
new ManiaModMirror(), {
new MultiMod switch (type)
{ {
Mods = new Mod[] case ModType.DifficultyReduction:
{ return new Mod[]
new ManiaModAutoplay(), {
new ModCinema(), new ManiaModEasy(),
}, new ManiaModNoFail(),
}, new MultiMod
}; {
Mods = new Mod[]
default: {
return new Mod[] { }; new ManiaModHalfTime(),
} new ManiaModDaycore(),
} },
},
public override string Description => "osu!mania"; };
public override string ShortName => "mania"; case ModType.DifficultyIncrease:
return new Mod[]
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o }; {
new ManiaModHardRock(),
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods); new MultiMod
{
public override int? LegacyID => 3; Mods = new Mod[]
{
public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame(); new ManiaModSuddenDeath(),
new ManiaModPerfect(),
public ManiaRuleset(RulesetInfo rulesetInfo = null) },
: base(rulesetInfo) },
{ new MultiMod
} {
Mods = new Mod[]
public override IEnumerable<int> AvailableVariants {
{ new ManiaModDoubleTime(),
get new ManiaModNightcore(),
{ },
for (int i = 1; i <= 9; i++) },
yield return (int)PlayfieldType.Single + i; new MultiMod
for (int i = 2; i <= 18; i += 2) {
yield return (int)PlayfieldType.Dual + i; Mods = new Mod[]
} {
} new ManiaModFadeIn(),
new ManiaModHidden(),
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) }
{ },
switch (getPlayfieldType(variant)) new ManiaModFlashlight(),
{ };
case PlayfieldType.Single:
return new VariantMappingGenerator case ModType.Special:
{ return new Mod[]
LeftKeys = new[] {
{ new MultiMod
InputKey.A, {
InputKey.S, Mods = new Mod[]
InputKey.D, {
InputKey.F new ManiaModKey4(),
}, new ManiaModKey5(),
RightKeys = new[] new ManiaModKey6(),
{ new ManiaModKey7(),
InputKey.J, new ManiaModKey8(),
InputKey.K, new ManiaModKey9(),
InputKey.L, new ManiaModKey1(),
InputKey.Semicolon new ManiaModKey2(),
}, new ManiaModKey3(),
SpecialKey = InputKey.Space, },
SpecialAction = ManiaAction.Special1, },
NormalActionStart = ManiaAction.Key1, new ManiaModRandom(),
}.GenerateKeyBindingsFor(variant, out _); new ManiaModDualStages(),
case PlayfieldType.Dual: new ManiaModMirror(),
int keys = getDualStageKeyCount(variant); new MultiMod
{
var stage1Bindings = new VariantMappingGenerator Mods = new Mod[]
{ {
LeftKeys = new[] new ManiaModAutoplay(),
{ new ModCinema(),
InputKey.Number1, },
InputKey.Number2, },
InputKey.Number3, };
InputKey.Number4,
}, default:
RightKeys = new[] return new Mod[] { };
{ }
InputKey.Z, }
InputKey.X,
InputKey.C, public override string Description => "osu!mania";
InputKey.V
}, public override string ShortName => "mania";
SpecialKey = InputKey.Tilde,
SpecialAction = ManiaAction.Special1, public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
NormalActionStart = ManiaAction.Key1
}.GenerateKeyBindingsFor(keys, out var nextNormal); public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap, Mod[] mods = null) => new ManiaDifficultyCalculator(beatmap, mods);
var stage2Bindings = new VariantMappingGenerator public override int? LegacyID => 3;
{
LeftKeys = new[] public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new ManiaReplayFrame();
{
InputKey.Number7, public ManiaRuleset(RulesetInfo rulesetInfo = null)
InputKey.Number8, : base(rulesetInfo)
InputKey.Number9, {
InputKey.Number0 }
},
RightKeys = new[] public override IEnumerable<int> AvailableVariants
{ {
InputKey.O, get
InputKey.P, {
InputKey.BracketLeft, for (int i = 1; i <= 9; i++)
InputKey.BracketRight yield return (int)PlayfieldType.Single + i;
}, for (int i = 2; i <= 18; i += 2)
SpecialKey = InputKey.BackSlash, yield return (int)PlayfieldType.Dual + i;
SpecialAction = ManiaAction.Special2, }
NormalActionStart = nextNormal }
}.GenerateKeyBindingsFor(keys, out _);
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
return stage1Bindings.Concat(stage2Bindings); {
} switch (getPlayfieldType(variant))
{
return new KeyBinding[0]; case PlayfieldType.Single:
} return new VariantMappingGenerator
{
public override string GetVariantName(int variant) LeftKeys = new[]
{ {
switch (getPlayfieldType(variant)) InputKey.A,
{ InputKey.S,
default: InputKey.D,
return $"{variant}K"; InputKey.F
case PlayfieldType.Dual: },
{ RightKeys = new[]
var keys = getDualStageKeyCount(variant); {
return $"{keys}K + {keys}K"; InputKey.J,
} InputKey.K,
} InputKey.L,
} InputKey.Semicolon
},
/// <summary> SpecialKey = InputKey.Space,
/// Finds the number of keys for each stage in a <see cref="PlayfieldType.Dual"/> variant. SpecialAction = ManiaAction.Special1,
/// </summary> NormalActionStart = ManiaAction.Key1,
/// <param name="variant">The variant.</param> }.GenerateKeyBindingsFor(variant, out _);
private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2; case PlayfieldType.Dual:
int keys = getDualStageKeyCount(variant);
/// <summary>
/// Finds the <see cref="PlayfieldType"/> that corresponds to a variant value. var stage1Bindings = new VariantMappingGenerator
/// </summary> {
/// <param name="variant">The variant value.</param> LeftKeys = new[]
/// <returns>The <see cref="PlayfieldType"/> that corresponds to <paramref name="variant"/>.</returns> {
private PlayfieldType getPlayfieldType(int variant) InputKey.Number1,
{ InputKey.Number2,
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v); InputKey.Number3,
} InputKey.Number4,
},
private class VariantMappingGenerator RightKeys = new[]
{ {
/// <summary> InputKey.Z,
/// All the <see cref="InputKey"/>s available to the left hand. InputKey.X,
/// </summary> InputKey.C,
public InputKey[] LeftKeys; InputKey.V
},
/// <summary> SpecialKey = InputKey.Tilde,
/// All the <see cref="InputKey"/>s available to the right hand. SpecialAction = ManiaAction.Special1,
/// </summary> NormalActionStart = ManiaAction.Key1
public InputKey[] RightKeys; }.GenerateKeyBindingsFor(keys, out var nextNormal);
/// <summary> var stage2Bindings = new VariantMappingGenerator
/// The <see cref="InputKey"/> for the special key. {
/// </summary> LeftKeys = new[]
public InputKey SpecialKey; {
InputKey.Number7,
/// <summary> InputKey.Number8,
/// The <see cref="ManiaAction"/> at which the normal columns should begin. InputKey.Number9,
/// </summary> InputKey.Number0
public ManiaAction NormalActionStart; },
RightKeys = new[]
/// <summary> {
/// The <see cref="ManiaAction"/> for the special column. InputKey.O,
/// </summary> InputKey.P,
public ManiaAction SpecialAction; InputKey.BracketLeft,
InputKey.BracketRight
/// <summary> },
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns. SpecialKey = InputKey.BackSlash,
/// </summary> SpecialAction = ManiaAction.Special2,
/// <param name="columns">The number of columns that need to be bound.</param> NormalActionStart = nextNormal
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param> }.GenerateKeyBindingsFor(keys, out _);
/// <returns>The keybindings.</returns>
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction) return stage1Bindings.Concat(stage2Bindings);
{ }
ManiaAction currentNormalAction = NormalActionStart;
return new KeyBinding[0];
var bindings = new List<KeyBinding>(); }
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++) public override string GetVariantName(int variant)
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++)); {
switch (getPlayfieldType(variant))
for (int i = 0; i < columns / 2; i++) {
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++)); default:
return $"{variant}K";
if (columns % 2 == 1) case PlayfieldType.Dual:
bindings.Add(new KeyBinding(SpecialKey, SpecialAction)); {
var keys = getDualStageKeyCount(variant);
nextNormalAction = currentNormalAction; return $"{keys}K + {keys}K";
return bindings; }
} }
} }
}
/// <summary>
public enum PlayfieldType /// Finds the number of keys for each stage in a <see cref="PlayfieldType.Dual"/> variant.
{ /// </summary>
/// <summary> /// <param name="variant">The variant.</param>
/// Columns are grouped into a single stage. private int getDualStageKeyCount(int variant) => (variant - (int)PlayfieldType.Dual) / 2;
/// Number of columns in this stage lies at (item - Single).
/// </summary> /// <summary>
Single = 0, /// Finds the <see cref="PlayfieldType"/> that corresponds to a variant value.
/// <summary> /// </summary>
/// Columns are grouped into two stages. /// <param name="variant">The variant value.</param>
/// Overall number of columns lies at (item - Dual), further computation is required for /// <returns>The <see cref="PlayfieldType"/> that corresponds to <paramref name="variant"/>.</returns>
/// number of columns in each individual stage. private PlayfieldType getPlayfieldType(int variant)
/// </summary> {
Dual = 1000, return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderByDescending(i => i).First(v => variant >= v);
} }
}
private class VariantMappingGenerator
{
/// <summary>
/// All the <see cref="InputKey"/>s available to the left hand.
/// </summary>
public InputKey[] LeftKeys;
/// <summary>
/// All the <see cref="InputKey"/>s available to the right hand.
/// </summary>
public InputKey[] RightKeys;
/// <summary>
/// The <see cref="InputKey"/> for the special key.
/// </summary>
public InputKey SpecialKey;
/// <summary>
/// The <see cref="ManiaAction"/> at which the normal columns should begin.
/// </summary>
public ManiaAction NormalActionStart;
/// <summary>
/// The <see cref="ManiaAction"/> for the special column.
/// </summary>
public ManiaAction SpecialAction;
/// <summary>
/// Generates a list of <see cref="KeyBinding"/>s for a specific number of columns.
/// </summary>
/// <param name="columns">The number of columns that need to be bound.</param>
/// <param name="nextNormalAction">The next <see cref="ManiaAction"/> to use for normal columns.</param>
/// <returns>The keybindings.</returns>
public IEnumerable<KeyBinding> GenerateKeyBindingsFor(int columns, out ManiaAction nextNormalAction)
{
ManiaAction currentNormalAction = NormalActionStart;
var bindings = new List<KeyBinding>();
for (int i = LeftKeys.Length - columns / 2; i < LeftKeys.Length; i++)
bindings.Add(new KeyBinding(LeftKeys[i], currentNormalAction++));
for (int i = 0; i < columns / 2; i++)
bindings.Add(new KeyBinding(RightKeys[i], currentNormalAction++));
if (columns % 2 == 1)
bindings.Add(new KeyBinding(SpecialKey, SpecialAction));
nextNormalAction = currentNormalAction;
return bindings;
}
}
}
public enum PlayfieldType
{
/// <summary>
/// Columns are grouped into a single stage.
/// Number of columns in this stage lies at (item - Single).
/// </summary>
Single = 0,
/// <summary>
/// Columns are grouped into two stages.
/// Overall number of columns lies at (item - Dual), further computation is required for
/// number of columns in each individual stage.
/// </summary>
Dual = 1000,
}
}

View File

@ -1,91 +1,91 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
namespace osu.Game.Rulesets.Mania.MathUtils namespace osu.Game.Rulesets.Mania.MathUtils
{ {
/// <summary> /// <summary>
/// A PRNG specified in http://heliosphan.org/fastrandom.html. /// A PRNG specified in http://heliosphan.org/fastrandom.html.
/// </summary> /// </summary>
internal class FastRandom internal class FastRandom
{ {
private const double int_to_real = 1.0 / (int.MaxValue + 1.0); private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF; private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087; private const uint y = 842502087;
private const uint z = 3579807591; private const uint z = 3579807591;
private const uint w = 273326509; private const uint w = 273326509;
private uint _x, _y = y, _z = z, _w = w; private uint _x, _y = y, _z = z, _w = w;
public FastRandom(int seed) public FastRandom(int seed)
{ {
_x = (uint)seed; _x = (uint)seed;
} }
public FastRandom() public FastRandom()
: this(Environment.TickCount) : this(Environment.TickCount)
{ {
} }
/// <summary> /// <summary>
/// Generates a random unsigned integer within the range [<see cref="uint.MinValue"/>, <see cref="uint.MaxValue"/>). /// Generates a random unsigned integer within the range [<see cref="uint.MinValue"/>, <see cref="uint.MaxValue"/>).
/// </summary> /// </summary>
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public uint NextUInt() public uint NextUInt()
{ {
uint t = _x ^ _x << 11; uint t = _x ^ _x << 11;
_x = _y; _x = _y;
_y = _z; _y = _z;
_z = _w; _z = _w;
return _w = _w ^ _w >> 19 ^ t ^ t >> 8; return _w = _w ^ _w >> 19 ^ t ^ t >> 8;
} }
/// <summary> /// <summary>
/// Generates a random integer value within the range [0, <see cref="int.MaxValue"/>). /// Generates a random integer value within the range [0, <see cref="int.MaxValue"/>).
/// </summary> /// </summary>
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public int Next() => (int)(int_mask & NextUInt()); public int Next() => (int)(int_mask & NextUInt());
/// <summary> /// <summary>
/// Generates a random integer value within the range [0, <paramref name="upperBound"/>). /// Generates a random integer value within the range [0, <paramref name="upperBound"/>).
/// </summary> /// </summary>
/// <param name="upperBound">The upper bound.</param> /// <param name="upperBound">The upper bound.</param>
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public int Next(int upperBound) => (int)(NextDouble() * upperBound); public int Next(int upperBound) => (int)(NextDouble() * upperBound);
/// <summary> /// <summary>
/// Generates a random integer value within the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>). /// Generates a random integer value within the range [<paramref name="lowerBound"/>, <paramref name="upperBound"/>).
/// </summary> /// </summary>
/// <param name="lowerBound">The lower bound of the range.</param> /// <param name="lowerBound">The lower bound of the range.</param>
/// <param name="upperBound">The upper bound of the range.</param> /// <param name="upperBound">The upper bound of the range.</param>
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound)); public int Next(int lowerBound, int upperBound) => (int)(lowerBound + NextDouble() * (upperBound - lowerBound));
/// <summary> /// <summary>
/// Generates a random double value within the range [0, 1). /// Generates a random double value within the range [0, 1).
/// </summary> /// </summary>
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public double NextDouble() => int_to_real * Next(); public double NextDouble() => int_to_real * Next();
private uint bitBuffer; private uint bitBuffer;
private int bitIndex = 32; private int bitIndex = 32;
/// <summary> /// <summary>
/// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls. /// Generates a reandom boolean value. Cached such that a random value is only generated once in every 32 calls.
/// </summary> /// </summary>
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public bool NextBool() public bool NextBool()
{ {
if (bitIndex == 32) if (bitIndex == 32)
{ {
bitBuffer = NextUInt(); bitBuffer = NextUInt();
bitIndex = 1; bitIndex = 1;
return (bitBuffer & 1) == 1; return (bitBuffer & 1) == 1;
} }
bitIndex++; bitIndex++;
return ((bitBuffer >>= 1) & 1) == 1; return ((bitBuffer >>= 1) & 1) == 1;
} }
} }
} }

View File

@ -1,15 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public interface IPlayfieldTypeMod : IApplicableMod public interface IPlayfieldTypeMod : IApplicableMod
{ {
/// <summary> /// <summary>
/// The <see cref="PlayfieldType"/> which this <see cref="IPlayfieldTypeMod"/> requires. /// The <see cref="PlayfieldType"/> which this <see cref="IPlayfieldTypeMod"/> requires.
/// </summary> /// </summary>
PlayfieldType PlayfieldType { get; } PlayfieldType PlayfieldType { get; }
} }
} }

View File

@ -1,29 +1,29 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter<ManiaHitObject> public abstract class ManiaKeyMod : Mod, IApplicableToBeatmapConverter<ManiaHitObject>
{ {
public override string ShortenedName => Name; public override string ShortenedName => Name;
public abstract int KeyCount { get; } public abstract int KeyCount { get; }
public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier public override double ScoreMultiplier => 1; // TODO: Implement the mania key mod score multiplier
public override bool Ranked => true; public override bool Ranked => true;
public void ApplyToBeatmapConverter(BeatmapConverter<ManiaHitObject> beatmapConverter) public void ApplyToBeatmapConverter(BeatmapConverter<ManiaHitObject> beatmapConverter)
{ {
var mbc = (ManiaBeatmapConverter)beatmapConverter; var mbc = (ManiaBeatmapConverter)beatmapConverter;
// Although this can work, for now let's not allow keymods for mania-specific beatmaps // Although this can work, for now let's not allow keymods for mania-specific beatmaps
if (mbc.IsForCurrentRuleset) if (mbc.IsForCurrentRuleset)
return; return;
mbc.TargetColumns = KeyCount; mbc.TargetColumns = KeyCount;
} }
} }
} }

View File

@ -1,25 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModAutoplay : ModAutoplay<ManiaHitObject> public class ManiaModAutoplay : ModAutoplay<ManiaHitObject>
{ {
protected override Score CreateReplayScore(Beatmap<ManiaHitObject> beatmap) protected override Score CreateReplayScore(Beatmap<ManiaHitObject> beatmap)
{ {
return new Score return new Score
{ {
User = new User { Username = "osu!topus!" }, User = new User { Username = "osu!topus!" },
Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(),
}; };
} }
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModDaycore : ModDaycore public class ManiaModDaycore : ModDaycore
{ {
public override double ScoreMultiplier => 0.5; public override double ScoreMultiplier => 0.5;
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModDoubleTime : ModDoubleTime public class ManiaModDoubleTime : ModDoubleTime
{ {
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
} }
} }

View File

@ -1,52 +1,52 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter<ManiaHitObject>, IApplicableToRulesetContainer<ManiaHitObject> public class ManiaModDualStages : Mod, IPlayfieldTypeMod, IApplicableToBeatmapConverter<ManiaHitObject>, IApplicableToRulesetContainer<ManiaHitObject>
{ {
public override string Name => "Dual Stages"; public override string Name => "Dual Stages";
public override string ShortenedName => "DS"; public override string ShortenedName => "DS";
public override string Description => @"Double the stages, double the fun!"; public override string Description => @"Double the stages, double the fun!";
public override double ScoreMultiplier => 0; public override double ScoreMultiplier => 0;
public void ApplyToBeatmapConverter(BeatmapConverter<ManiaHitObject> beatmapConverter) public void ApplyToBeatmapConverter(BeatmapConverter<ManiaHitObject> beatmapConverter)
{ {
var mbc = (ManiaBeatmapConverter)beatmapConverter; var mbc = (ManiaBeatmapConverter)beatmapConverter;
// Although this can work, for now let's not allow keymods for mania-specific beatmaps // Although this can work, for now let's not allow keymods for mania-specific beatmaps
if (mbc.IsForCurrentRuleset) if (mbc.IsForCurrentRuleset)
return; return;
mbc.TargetColumns *= 2; mbc.TargetColumns *= 2;
} }
public void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer) public void ApplyToRulesetContainer(RulesetContainer<ManiaHitObject> rulesetContainer)
{ {
var mrc = (ManiaRulesetContainer)rulesetContainer; var mrc = (ManiaRulesetContainer)rulesetContainer;
// Although this can work, for now let's not allow keymods for mania-specific beatmaps // Although this can work, for now let's not allow keymods for mania-specific beatmaps
if (mrc.IsForCurrentRuleset) if (mrc.IsForCurrentRuleset)
return; return;
var newDefinitions = new List<StageDefinition>(); var newDefinitions = new List<StageDefinition>();
foreach (var existing in mrc.Beatmap.Stages) foreach (var existing in mrc.Beatmap.Stages)
{ {
newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 });
newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 }); newDefinitions.Add(new StageDefinition { Columns = existing.Columns / 2 });
} }
mrc.Beatmap.Stages = newDefinitions; mrc.Beatmap.Stages = newDefinitions;
} }
public PlayfieldType PlayfieldType => PlayfieldType.Dual; public PlayfieldType PlayfieldType => PlayfieldType.Dual;
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModEasy : ModEasy public class ManiaModEasy : ModEasy
{ {
public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!"; public override string Description => @"More forgiving HP drain, less accuracy required, and three lives!";
} }
} }

View File

@ -1,21 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModFadeIn : Mod public class ManiaModFadeIn : Mod
{ {
public override string Name => "Fade In"; public override string Name => "Fade In";
public override string ShortenedName => "FI"; public override string ShortenedName => "FI";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden; public override FontAwesome Icon => FontAwesome.fa_osu_mod_hidden;
public override ModType Type => ModType.DifficultyIncrease; public override ModType Type => ModType.DifficultyIncrease;
public override string Description => @"Keys appear out of nowhere!"; public override string Description => @"Keys appear out of nowhere!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override bool Ranked => true; public override bool Ranked => true;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) };
} }
} }

View File

@ -1,14 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModFlashlight : ModFlashlight public class ManiaModFlashlight : ModFlashlight
{ {
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModHidden) }; public override Type[] IncompatibleMods => new[] { typeof(ModHidden) };
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModHalfTime : ModHalfTime public class ManiaModHalfTime : ModHalfTime
{ {
public override double ScoreMultiplier => 0.5; public override double ScoreMultiplier => 0.5;
} }
} }

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModHardRock : ModHardRock public class ManiaModHardRock : ModHardRock
{ {
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
} }
} }

View File

@ -1,15 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModHidden : ModHidden public class ManiaModHidden : ModHidden
{ {
public override string Description => @"Keys fade out before you hit them!"; public override string Description => @"Keys fade out before you hit them!";
public override double ScoreMultiplier => 1; public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) };
} }
} }

View File

@ -1,13 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Mania.Mods namespace osu.Game.Rulesets.Mania.Mods
{ {
public class ManiaModKey1 : ManiaKeyMod public class ManiaModKey1 : ManiaKeyMod
{ {
public override int KeyCount => 1; public override int KeyCount => 1;
public override string Name => "One Key"; public override string Name => "One Key";
public override string ShortenedName => "1K"; public override string ShortenedName => "1K";
public override string Description => @"Play with one key."; public override string Description => @"Play with one key.";
} }
} }

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