Merge branch 'master' into always-combo-break
@ -2,7 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.osu.Desktop/.idea/.idea.osu.Desktop.iml" filepath="$PROJECT_DIR$/.idea/.idea.osu.Desktop/.idea/.idea.osu.Desktop.iml" />
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.osu.Desktop/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.osu.Desktop/riderModule.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/.idea.osu.Desktop/riderModule.iml" filepath="$PROJECT_DIR$/.idea/.idea.osu.Desktop/riderModule.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
|
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Catch.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
|
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Mania.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
|
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Osu.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset">
|
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.1/osu.Game.Rulesets.Taiko.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
|
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament">
|
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tournament.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -15,7 +15,7 @@
|
|||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
|
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu! SDL" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
|
<configuration default="false" name="osu! SDL" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--sdl" />
|
<option name="PROGRAM_PARAMETERS" value="--sdl" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,9 +12,9 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!">
|
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.1/osu.Game.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.1" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.1" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
137
CONTRIBUTING.md
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
# Contributing Guidelines
|
||||||
|
|
||||||
|
Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience.
|
||||||
|
|
||||||
|
These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
1. [I would like to submit an issue!](#i-would-like-to-submit-an-issue)
|
||||||
|
2. [I would like to submit a pull request!](#i-would-like-to-submit-a-pull-request)
|
||||||
|
|
||||||
|
## I would like to submit an issue!
|
||||||
|
|
||||||
|
Issues, bug reports and feature suggestions are welcomed, though please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most.
|
||||||
|
|
||||||
|
* **Before submitting an issue, try searching existing issues first.**
|
||||||
|
|
||||||
|
For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not to have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before.
|
||||||
|
|
||||||
|
* **When submitting a bug report, please try to include as much detail as possible.**
|
||||||
|
|
||||||
|
Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information consists of:
|
||||||
|
|
||||||
|
* the in-game logs, which are located at:
|
||||||
|
* `%AppData%/osu/logs` (on Windows),
|
||||||
|
* `~/.local/share/osu/logs` (on Linux and macOS),
|
||||||
|
* `Android/Data/sh.ppy.osulazer/logs` (on Android),
|
||||||
|
* on iOS they can be obtained by connecting your device to your desktop and [copying the `logs` directory from the app's own document storage using iTunes](https://support.apple.com/en-us/HT201301#copy-to-computer),
|
||||||
|
* your system specifications (including the operating system and platform you are playing on),
|
||||||
|
* a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug),
|
||||||
|
* a video or picture of the bug, if at all possible.
|
||||||
|
|
||||||
|
* **Provide more information when asked to do so.**
|
||||||
|
|
||||||
|
Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is!
|
||||||
|
|
||||||
|
* **When submitting a feature proposal, please describe it in the most understandable way you can.**
|
||||||
|
|
||||||
|
Communicating your idea for a feature can often be hard, and we would like to avoid any misunderstandings. As such, please try to explain your idea in a short, but understandable manner - it's best to avoid jargon or terms and references that could be considered obscure. A mock-up picture (doesn't have to be good!) of the feature can also go a long way in explaining.
|
||||||
|
|
||||||
|
* **Refrain from posting "+1" comments.**
|
||||||
|
|
||||||
|
If an issue has already been created, saying that you also experience it without providing any additional details doesn't really help us in any way. To express support for a proposal or indicate that you are also affected by a particular bug, you can use comment reactions instead.
|
||||||
|
|
||||||
|
* **Refrain from asking if an issue has been resolved yet.**
|
||||||
|
|
||||||
|
As mentioned above, the issue tracker has hundreds of issues open at any given time. Currently the game is being worked on by two members of the core team, and a handful of outside contributors who offer their free time to help out. As such, it can happen that an issue gets placed on the backburner due to being less important; generally posting a comment demanding its resolution some months or years after it is reported is not very likely to increase its priority.
|
||||||
|
|
||||||
|
* **Avoid long discussions about non-development topics.**
|
||||||
|
|
||||||
|
GitHub is mostly a developer space, and as such isn't really fit for lengthened discussions about gameplay mechanics (which might not even be in any way confirmed for the final release) and similar non-technical matters. Such matters are probably best addressed at the osu! forums.
|
||||||
|
|
||||||
|
## I would like to submit a pull request!
|
||||||
|
|
||||||
|
We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label.
|
||||||
|
|
||||||
|
However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management).
|
||||||
|
|
||||||
|
Here are some key things to note before jumping in:
|
||||||
|
|
||||||
|
* **Make sure you are comfortable with C\# and your development environment.**
|
||||||
|
|
||||||
|
While we are accepting of all kinds of contributions, we also have a certain quality standard we'd like to uphold and limited time to review your code. Therefore, we would like to avoid providing entry-level advice, and as such if you're not very familiar with C\# as a programming language, we'd recommend that you start off with a few personal projects to get acquainted with the language's syntax, toolchain and principles of object-oriented programming first.
|
||||||
|
|
||||||
|
In addition, please take the time to take a look at and get acquainted with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up.
|
||||||
|
|
||||||
|
* **Make sure you are familiar with git and the pull request workflow.**
|
||||||
|
|
||||||
|
[git](https://git-scm.com/) is a distributed version control system that might not be very intuitive at the beginning if you're not familiar with version control. In particular, projects using git have a particular workflow for submitting code changes, which is called the pull request workflow.
|
||||||
|
|
||||||
|
To make things run more smoothly, we recommend that you look up some online resources to familiarise yourself with the git vocabulary and commands, and practice working with forks and submitting pull requests at your own pace. A high-level overview of the process can be found in [this article by GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests).
|
||||||
|
|
||||||
|
* **Double-check designs before starting work on new functionality.**
|
||||||
|
|
||||||
|
When implementing new features, keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
|
||||||
|
|
||||||
|
* **Make sure to submit pull requests off of a topic branch.**
|
||||||
|
|
||||||
|
As described in the article linked in the previous point, topic branches help you parallelise your work and separate it from the main `master` branch, and additionally are easier for maintainers to work with. Working with multiple `master` branches across many remotes is difficult to keep track of, and it's easy to make a mistake and push to the wrong `master` branch by accident.
|
||||||
|
|
||||||
|
* **Refrain from making changes through the GitHub web interface.**
|
||||||
|
|
||||||
|
Even though GitHub provides an option to edit code or replace files in the repository using the web interface, we strongly discourage using it in most scenarios. Editing files this way is inefficient and likely to introduce whitespace or file encoding changes that make it more difficult to review the code.
|
||||||
|
|
||||||
|
Code written through the web interface will also very likely be questioned outright by the reviewers, as it is likely that it has not been properly tested or that it will fail continuous integration checks. We strongly encourage using an IDE like [Visual Studio](https://visualstudio.microsoft.com/), [Visual Studio Code](https://code.visualstudio.com/) or [JetBrains Rider](https://www.jetbrains.com/rider/) instead.
|
||||||
|
|
||||||
|
* **Add tests for your code whenever possible.**
|
||||||
|
|
||||||
|
Automated tests are an essential part of a quality and reliable codebase. They help to make the code more maintainable by ensuring it is safe to reorganise (or refactor) the code in various ways, and also prevent regressions - bugs that resurface after having been fixed at some point in the past. If it is viable, please put in the time to add tests, so that the changes you make can last for a (hopefully) very long time.
|
||||||
|
|
||||||
|
* **Run tests before opening a pull request.**
|
||||||
|
|
||||||
|
Tying into the previous point, sometimes changes in one part of the codebase can result in unpredictable changes in behaviour in other pieces of the code. This is why it is best to always try to run tests before opening a PR.
|
||||||
|
|
||||||
|
Continuous integration will always run the tests for you (and us), too, but it is best not to rely on it, as there might be many builds queued at any time. Running tests on your own will help you be more certain that at the point of clicking the "Create pull request" button, your changes are as ready as can be.
|
||||||
|
|
||||||
|
* **Run code style analysis before opening a pull request.**
|
||||||
|
|
||||||
|
As part of continuous integration, we also run code style analysis, which is supposed to make sure that your code is formatted the same way as all the pre-existing code in the repository. The reason we enforce a particular code style everywhere is to make sure the codebase is consistent in that regard - having one whitespace convention in one place and another one elsewhere causes disorganisation.
|
||||||
|
|
||||||
|
* **Make sure that the pull request is complete before opening it.**
|
||||||
|
|
||||||
|
Whether it's fixing a bug or implementing new functionality, it's best that you make sure that the change you want to submit as a pull request is as complete as it can be before clicking the *Create pull request* button. Having to track if a pull request is ready for review or not places additional burden on reviewers.
|
||||||
|
|
||||||
|
Draft pull requests are an option, but use them sparingly and within reason. They are best suited to discuss code changes that cannot be easily described in natural language or have a potential large impact on the future direction of the project. When in doubt, don't open drafts unless a maintainer asks you to do so.
|
||||||
|
|
||||||
|
* **Only push code when it's ready.**
|
||||||
|
|
||||||
|
As an extension of the above, when making changes to an already-open PR, please try to only push changes you are reasonably certain of. Pushing after every commit causes the continuous integration build queue to grow in size, slowing down work and taking up time that could be spent verifying other changes.
|
||||||
|
|
||||||
|
* **Make sure to keep the *Allow edits from maintainers* check box checked.**
|
||||||
|
|
||||||
|
To speed up the merging process, collaborators and team members will sometimes want to push changes to your branch themselves, to make minor code style adjustments or to otherwise refactor the code without having to describe how they'd like the code to look like in painstaking detail. Having the *Allow edits from maintainers* check box checked lets them do that; without it they are forced to report issues back to you and wait for you to address them.
|
||||||
|
|
||||||
|
* **Refrain from continually merging the master branch back to the PR.**
|
||||||
|
|
||||||
|
Unless there are merge conflicts that need resolution, there is no need to keep merging `master` back to a branch over and over again. One of the maintainers will merge `master` themselves before merging the PR itself anyway, and continual merge commits can cause CI to get overwhelmed due to queueing up too many builds.
|
||||||
|
|
||||||
|
* **Refrain from force-pushing to the PR branch.**
|
||||||
|
|
||||||
|
Force-pushing should be avoided, as it can lead to accidentally overwriting a maintainer's changes or CI building wrong commits. We value all history in the project, so there is no need to squash or amend commits in most cases.
|
||||||
|
|
||||||
|
The cases in which force-pushing is warranted are very rare (such as accidentally leaking sensitive info in one of the files committed, adding unrelated files, or mis-merging a dependent PR).
|
||||||
|
|
||||||
|
* **Be patient when waiting for the code to be reviewed and merged.**
|
||||||
|
|
||||||
|
As much as we'd like to review all contributions as fast as possible, our time is limited, as team members have to work on their own tasks in addition to reviewing code. As such, work needs to be prioritised, and it can unfortunately take weeks or months for your PR to be merged, depending on how important it is deemed to be.
|
||||||
|
|
||||||
|
* **Don't mistake criticism of code for criticism of your person.**
|
||||||
|
|
||||||
|
As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack.
|
||||||
|
|
||||||
|
* **Feel free to reach out for help.**
|
||||||
|
|
||||||
|
If you're uncertain about some part of the codebase or some inner workings of the game and framework, please reach out either by leaving a comment in the relevant issue or PR thread, or by posting a message in the [development Discord server](https://discord.gg/ppy). We will try to help you as much as we can.
|
||||||
|
|
||||||
|
When it comes to which form of communication is best, GitHub generally lends better to longer-form discussions, while Discord is better for snappy call-and-response answers. Use your best discretion when deciding, and try to keep a single discussion in one place instead of moving back and forth.
|
@ -4,3 +4,5 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals(
|
|||||||
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
|
M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead.
|
||||||
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
|
T:System.IComparable;Don't use non-generic IComparable. Use generic version instead.
|
||||||
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText.
|
||||||
|
T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods.
|
||||||
|
T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods.
|
||||||
|
@ -16,9 +16,9 @@
|
|||||||
<EmbeddedResource Include="Resources\**\*.*" />
|
<EmbeddedResource Include="Resources\**\*.*" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Code Analysis">
|
<ItemGroup Label="Code Analysis">
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
||||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0" PrivateAssets="All" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Code Analysis">
|
<PropertyGroup Label="Code Analysis">
|
||||||
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset</CodeAnalysisRuleSet>
|
||||||
|
72
Gemfile.lock
@ -5,6 +5,22 @@ GEM
|
|||||||
addressable (2.7.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
|
aws-eventstream (1.1.0)
|
||||||
|
aws-partitions (1.329.0)
|
||||||
|
aws-sdk-core (3.99.2)
|
||||||
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
|
aws-partitions (~> 1, >= 1.239.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
jmespath (~> 1.0)
|
||||||
|
aws-sdk-kms (1.34.1)
|
||||||
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sdk-s3 (1.68.1)
|
||||||
|
aws-sdk-core (~> 3, >= 3.99.0)
|
||||||
|
aws-sdk-kms (~> 1)
|
||||||
|
aws-sigv4 (~> 1.1)
|
||||||
|
aws-sigv4 (1.1.4)
|
||||||
|
aws-eventstream (~> 1.0, >= 1.0.2)
|
||||||
babosa (1.0.3)
|
babosa (1.0.3)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
@ -13,23 +29,24 @@ GEM
|
|||||||
highline (~> 1.7.2)
|
highline (~> 1.7.2)
|
||||||
declarative (0.0.10)
|
declarative (0.0.10)
|
||||||
declarative-option (0.1.0)
|
declarative-option (0.1.0)
|
||||||
digest-crc (0.4.1)
|
digest-crc (0.5.1)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.5)
|
||||||
emoji_regex (1.0.1)
|
emoji_regex (1.0.1)
|
||||||
excon (0.71.1)
|
excon (0.74.0)
|
||||||
faraday (0.17.3)
|
faraday (1.0.1)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-cookie_jar (0.0.6)
|
||||||
faraday (>= 0.7.4)
|
faraday (>= 0.7.4)
|
||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (0.13.1)
|
faraday_middleware (1.0.0)
|
||||||
faraday (>= 0.7.4, < 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.1.7)
|
fastimage (2.1.7)
|
||||||
fastlane (2.140.0)
|
fastlane (2.149.1)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.2, < 2.0.0)
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
colored
|
colored
|
||||||
@ -37,12 +54,12 @@ GEM
|
|||||||
dotenv (>= 2.1.1, < 3.0.0)
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
emoji_regex (>= 0.1, < 2.0)
|
emoji_regex (>= 0.1, < 2.0)
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
faraday (~> 0.17)
|
faraday (>= 0.17, < 2.0)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 0.13.1)
|
faraday_middleware (>= 0.13.1, < 2.0)
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
google-api-client (>= 0.29.2, < 0.37.0)
|
google-api-client (>= 0.37.0, < 0.39.0)
|
||||||
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
google-cloud-storage (>= 1.15.0, < 2.0.0)
|
||||||
highline (>= 1.7.2, < 2.0.0)
|
highline (>= 1.7.2, < 2.0.0)
|
||||||
json (< 3.0.0)
|
json (< 3.0.0)
|
||||||
@ -69,7 +86,7 @@ GEM
|
|||||||
souyuz (= 0.9.1)
|
souyuz (= 0.9.1)
|
||||||
fastlane-plugin-xamarin (0.6.3)
|
fastlane-plugin-xamarin (0.6.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-api-client (0.36.4)
|
google-api-client (0.38.0)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
httpclient (>= 2.8.1, < 3.0)
|
httpclient (>= 2.8.1, < 3.0)
|
||||||
@ -80,27 +97,28 @@ GEM
|
|||||||
google-cloud-core (1.5.0)
|
google-cloud-core (1.5.0)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-errors (~> 1.0)
|
google-cloud-errors (~> 1.0)
|
||||||
google-cloud-env (1.3.0)
|
google-cloud-env (1.3.2)
|
||||||
faraday (~> 0.11)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
google-cloud-errors (1.0.0)
|
google-cloud-errors (1.0.1)
|
||||||
google-cloud-storage (1.25.1)
|
google-cloud-storage (1.26.2)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
google-api-client (~> 0.33)
|
google-api-client (~> 0.33)
|
||||||
google-cloud-core (~> 1.2)
|
google-cloud-core (~> 1.2)
|
||||||
googleauth (~> 0.9)
|
googleauth (~> 0.9)
|
||||||
mini_mime (~> 1.0)
|
mini_mime (~> 1.0)
|
||||||
googleauth (0.10.0)
|
googleauth (0.12.0)
|
||||||
faraday (~> 0.12)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.4, < 3.0)
|
jwt (>= 1.4, < 3.0)
|
||||||
memoist (~> 0.16)
|
memoist (~> 0.16)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (~> 0.12)
|
signet (~> 0.14)
|
||||||
highline (1.7.10)
|
highline (1.7.10)
|
||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
|
jmespath (1.4.0)
|
||||||
json (2.3.0)
|
json (2.3.0)
|
||||||
jwt (2.1.0)
|
jwt (2.1.0)
|
||||||
memoist (0.16.2)
|
memoist (0.16.2)
|
||||||
@ -114,7 +132,7 @@ GEM
|
|||||||
naturally (2.2.0)
|
naturally (2.2.0)
|
||||||
nokogiri (1.10.7)
|
nokogiri (1.10.7)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.4.0)
|
||||||
os (1.0.1)
|
os (1.1.0)
|
||||||
plist (3.5.0)
|
plist (3.5.0)
|
||||||
public_suffix (2.0.5)
|
public_suffix (2.0.5)
|
||||||
representable (3.0.4)
|
representable (3.0.4)
|
||||||
@ -125,12 +143,12 @@ GEM
|
|||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
rubyzip (1.3.0)
|
rubyzip (1.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.12.0)
|
signet (0.14.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (~> 0.9)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.7)
|
simctl (1.6.8)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
slack-notifier (2.3.2)
|
||||||
@ -141,17 +159,17 @@ GEM
|
|||||||
terminal-notifier (2.0.0)
|
terminal-notifier (2.0.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
tty-cursor (0.7.0)
|
tty-cursor (0.7.1)
|
||||||
tty-screen (0.7.0)
|
tty-screen (0.8.0)
|
||||||
tty-spinner (0.9.2)
|
tty-spinner (0.9.3)
|
||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.6)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.6.1)
|
unicode-display_width (1.7.0)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.14.0)
|
xcodeproj (1.16.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
|
@ -15,6 +15,8 @@ Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the
|
|||||||
|
|
||||||
This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
|
This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
|
||||||
|
|
||||||
|
**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passses come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet.
|
||||||
|
|
||||||
We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
|
We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
|
||||||
|
|
||||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||||
@ -91,11 +93,7 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted.
|
When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Based on past experiences, we have prepared a [list of contributing guidelines](CONTRIBUTING.md) that should hopefully ease you into our collaboration process and answer the most frequently-asked questions.
|
||||||
|
|
||||||
If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aopen+label%3Agood-first-issue+sort%3Aupdated-desc) label).
|
|
||||||
|
|
||||||
Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**.
|
|
||||||
|
|
||||||
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible.
|
Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible.
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
|
#addin "nuget:?package=CodeFileSanity&version=0.0.36"
|
||||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.2"
|
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3"
|
||||||
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
#tool "nuget:?package=NVika.MSBuild&version=1.0.1"
|
||||||
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First();
|
||||||
|
|
||||||
|
@ -5,6 +5,6 @@
|
|||||||
"version": "3.1.100"
|
"version": "3.1.100"
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "2.0.34"
|
"Microsoft.Build.Traversal": "2.0.50"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -51,7 +51,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.412.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2020.714.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.417.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.714.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -9,7 +9,7 @@ using osu.Framework.Android;
|
|||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
{
|
{
|
||||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)]
|
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)]
|
||||||
public class OsuGameActivity : AndroidGameActivity
|
public class OsuGameActivity : AndroidGameActivity
|
||||||
{
|
{
|
||||||
protected override Framework.Game CreateGame() => new OsuGameAndroid();
|
protected override Framework.Game CreateGame() => new OsuGameAndroid();
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
|
using Android.OS;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
|
|
||||||
@ -18,8 +19,32 @@ namespace osu.Android
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string versionName = packageInfo.VersionCode.ToString();
|
// We store the osu! build number in the "VersionCode" field to better support google play releases.
|
||||||
// undo play store version garbling
|
// If we were to use the main build number, it would require a new submission each time (similar to TestFlight).
|
||||||
|
// In order to do this, we should split it up and pad the numbers to still ensure sequential increase over time.
|
||||||
|
//
|
||||||
|
// We also need to be aware that older SDK versions store this as a 32bit int.
|
||||||
|
//
|
||||||
|
// Basic conversion format (as done in Fastfile): 2020.606.0 -> 202006060
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated
|
||||||
|
string versionName = string.Empty;
|
||||||
|
|
||||||
|
if (Build.VERSION.SdkInt >= BuildVersionCodes.P)
|
||||||
|
{
|
||||||
|
versionName = packageInfo.LongVersionCode.ToString();
|
||||||
|
// ensure we only read the trailing portion of long (the part we are interested in).
|
||||||
|
versionName = versionName.Substring(versionName.Length - 9);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
#pragma warning disable CS0618 // Type or member is obsolete
|
||||||
|
// this is required else older SDKs will report missing method exception.
|
||||||
|
versionName = packageInfo.VersionCode.ToString();
|
||||||
|
#pragma warning restore CS0618 // Type or member is obsolete
|
||||||
|
}
|
||||||
|
|
||||||
|
// undo play store version garbling (as mentioned above).
|
||||||
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
|
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
|
@ -6,15 +6,13 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.Win32;
|
||||||
using osu.Desktop.Overlays;
|
using osu.Desktop.Overlays;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osuTK.Input;
|
|
||||||
using Microsoft.Win32;
|
|
||||||
using osu.Desktop.Updater;
|
using osu.Desktop.Updater;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform.Windows;
|
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Screens.Menu;
|
using osu.Game.Screens.Menu;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
@ -37,7 +35,11 @@ namespace osu.Desktop
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Host is DesktopGameHost desktopHost)
|
if (Host is DesktopGameHost desktopHost)
|
||||||
return new StableStorage(desktopHost);
|
{
|
||||||
|
string stablePath = getStableInstallPath();
|
||||||
|
if (!string.IsNullOrEmpty(stablePath))
|
||||||
|
return new DesktopStorage(stablePath, desktopHost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
{
|
{
|
||||||
@ -47,6 +49,35 @@ namespace osu.Desktop
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string getStableInstallPath()
|
||||||
|
{
|
||||||
|
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
|
||||||
|
|
||||||
|
string stableInstallPath;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
||||||
|
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||||
|
|
||||||
|
if (checkExists(stableInstallPath))
|
||||||
|
return stableInstallPath;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
|
||||||
|
if (checkExists(stableInstallPath))
|
||||||
|
return stableInstallPath;
|
||||||
|
|
||||||
|
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
|
||||||
|
if (checkExists(stableInstallPath))
|
||||||
|
return stableInstallPath;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager()
|
protected override UpdateManager CreateUpdateManager()
|
||||||
{
|
{
|
||||||
switch (RuntimeInfo.OS)
|
switch (RuntimeInfo.OS)
|
||||||
@ -90,66 +121,32 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
base.SetHost(host);
|
base.SetHost(host);
|
||||||
|
|
||||||
if (host.Window is DesktopGameWindow desktopWindow)
|
switch (host.Window)
|
||||||
{
|
{
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
// Legacy osuTK DesktopGameWindow
|
||||||
|
case DesktopGameWindow desktopGameWindow:
|
||||||
|
desktopGameWindow.CursorState |= CursorState.Hidden;
|
||||||
|
desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
|
||||||
|
desktopGameWindow.Title = Name;
|
||||||
|
desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames);
|
||||||
|
break;
|
||||||
|
|
||||||
desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico"));
|
// SDL2 DesktopWindow
|
||||||
desktopWindow.Title = Name;
|
case DesktopWindow desktopWindow:
|
||||||
|
desktopWindow.CursorState.Value |= CursorState.Hidden;
|
||||||
desktopWindow.FileDrop += fileDrop;
|
desktopWindow.Title = Name;
|
||||||
|
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fileDrop(object sender, FileDropEventArgs e)
|
private void fileDrop(string[] filePaths)
|
||||||
{
|
{
|
||||||
var filePaths = e.FileNames;
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A method of accessing an osu-stable install in a controlled fashion.
|
|
||||||
/// </summary>
|
|
||||||
private class StableStorage : WindowsStorage
|
|
||||||
{
|
|
||||||
protected override string LocateBasePath()
|
|
||||||
{
|
|
||||||
static bool checkExists(string p) => Directory.Exists(Path.Combine(p, "Songs"));
|
|
||||||
|
|
||||||
string stableInstallPath;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu"))
|
|
||||||
stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", "");
|
|
||||||
|
|
||||||
if (checkExists(stableInstallPath))
|
|
||||||
return stableInstallPath;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!");
|
|
||||||
if (checkExists(stableInstallPath))
|
|
||||||
return stableInstallPath;
|
|
||||||
|
|
||||||
stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu");
|
|
||||||
if (checkExists(stableInstallPath))
|
|
||||||
return stableInstallPath;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StableStorage(DesktopGameHost host)
|
|
||||||
: base(string.Empty, host)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,13 +33,11 @@ namespace osu.Desktop
|
|||||||
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
|
if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args
|
||||||
{
|
{
|
||||||
var importer = new ArchiveImportIPCChannel(host);
|
var importer = new ArchiveImportIPCChannel(host);
|
||||||
// Restore the cwd so relative paths given at the command line work correctly
|
|
||||||
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, cwd)).Wait(3000))
|
||||||
throw new TimeoutException(@"IPC took too long to send");
|
throw new TimeoutException(@"IPC took too long to send");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,29 +30,27 @@ namespace osu.Desktop.Updater
|
|||||||
private static readonly Logger logger = Logger.GetLogger("updater");
|
private static readonly Logger logger = Logger.GetLogger("updater");
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(NotificationOverlay notification, OsuGameBase game)
|
private void load(NotificationOverlay notification)
|
||||||
{
|
{
|
||||||
notificationOverlay = notification;
|
notificationOverlay = notification;
|
||||||
|
|
||||||
if (game.IsDeployedBuild)
|
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
|
||||||
{
|
|
||||||
Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger));
|
|
||||||
Schedule(() => Task.Run(() => checkForUpdateAsync()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
|
protected override async Task PerformUpdateCheck() => await checkForUpdateAsync();
|
||||||
|
|
||||||
|
private async Task 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 scheduleRecheck = true;
|
bool scheduleRecheck = true;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
|
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)
|
||||||
@ -81,9 +79,9 @@ namespace osu.Desktop.Updater
|
|||||||
{
|
{
|
||||||
logger.Add(@"delta patching failed; will attempt full download!");
|
logger.Add(@"delta patching failed; will attempt full download!");
|
||||||
|
|
||||||
//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);
|
await checkForUpdateAsync(false, notification);
|
||||||
scheduleRecheck = false;
|
scheduleRecheck = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -101,8 +99,8 @@ namespace osu.Desktop.Updater
|
|||||||
{
|
{
|
||||||
if (scheduleRecheck)
|
if (scheduleRecheck)
|
||||||
{
|
{
|
||||||
//check again in 30 minutes.
|
// check again in 30 minutes.
|
||||||
Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30);
|
Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,10 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
<PackageReference Include="DiscordRichPresence" Version="1.0.150" />
|
||||||
|
<!-- .NET 3.1 SDK seems to cause issues with a runtime specification. This will likely be resolved in .NET 5. -->
|
||||||
|
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Runtime.Handles" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Runtime.InteropServices" Version="4.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
|
@ -8,7 +8,7 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Archives;
|
using osu.Game.IO.Archives;
|
||||||
using osu.Game.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Benchmarks
|
namespace osu.Game.Benchmarks
|
||||||
{
|
{
|
||||||
@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks
|
|||||||
|
|
||||||
public override void SetUp()
|
public override void SetUp()
|
||||||
{
|
{
|
||||||
using (var resources = new DllResourceStore(OsuResources.ResourceAssembly))
|
using (var resources = new DllResourceStore(typeof(TestResources).Assembly))
|
||||||
using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz"))
|
using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz"))
|
||||||
using (var reader = new ZipArchiveReader(archive))
|
using (var reader = new ZipArchiveReader(archive))
|
||||||
reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
|
reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream);
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,11 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
|
||||||
<PackageReference Include="nunit" Version="3.12.0" />
|
<PackageReference Include="nunit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\osu.Game.Tests\osu.Game.Tests.csproj" />
|
||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
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;
|
||||||
|
|
||||||
@ -83,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
public float Position
|
public float Position
|
||||||
{
|
{
|
||||||
get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position;
|
get => HitObject?.X ?? position;
|
||||||
set => position = value;
|
set => position = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
|
||||||
|
|
||||||
[TestCase(4.2058561036909863d, "diffcalc-test")]
|
[TestCase(4.050601681491468d, "diffcalc-test")]
|
||||||
public void Test(double expected, string name)
|
public void Test(double expected, string name)
|
||||||
=> base.Test(expected, name);
|
=> base.Test(expected, name);
|
||||||
|
|
||||||
|
37
osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CatchSkinColourDecodingTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestCatchSkinColourDecoding()
|
||||||
|
{
|
||||||
|
var store = new NamespacedResourceStore<byte[]>(new DllResourceStore(GetType().Assembly), "Resources/special-skin");
|
||||||
|
var rawSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, store);
|
||||||
|
var skinSource = new SkinProvidingContainer(rawSkin);
|
||||||
|
var skin = new CatchLegacySkinTransformer(skinSource);
|
||||||
|
|
||||||
|
Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value);
|
||||||
|
Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value);
|
||||||
|
Assert.AreEqual(new Color4(0, 255, 255, 255), skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestLegacySkin : LegacySkin
|
||||||
|
{
|
||||||
|
public TestLegacySkin(SkinInfo skin, IResourceStore<byte[]> storage)
|
||||||
|
// Bypass LegacySkinResourceStore to avoid returning null for retrieving files due to bad skin info (SkinInfo.Files = null).
|
||||||
|
: base(skin, storage, null, "skin.ini")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
public abstract class CatchSkinnableTestScene : SkinnableTestScene
|
public abstract class CatchSkinnableTestScene : SkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(CatchRuleset),
|
|
||||||
typeof(CatchLegacySkinTransformer),
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset();
|
protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,10 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods
|
|||||||
{
|
{
|
||||||
public class TestSceneCatchModPerfect : ModPerfectTestScene
|
public class TestSceneCatchModPerfect : ModPerfectTestScene
|
||||||
{
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
|
|
||||||
public TestSceneCatchModPerfect()
|
public TestSceneCatchModPerfect()
|
||||||
: base(new CatchRuleset(), new CatchModPerfect())
|
: base(new CatchModPerfect())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
[CatchTheBeat]
|
||||||
|
HyperDash: 232,185,35
|
||||||
|
HyperDashFruit: 0,255,255
|
||||||
|
HyperDashAfterImage: 232,74,35
|
@ -12,13 +12,8 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneAutoJuiceStream : PlayerTestScene
|
public class TestSceneAutoJuiceStream : TestSceneCatchPlayer
|
||||||
{
|
{
|
||||||
public TestSceneAutoJuiceStream()
|
|
||||||
: base(new CatchRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap
|
var beatmap = new Beatmap
|
||||||
@ -32,15 +27,15 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
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 * CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
beatmap.HitObjects.Add(new JuiceStream
|
beatmap.HitObjects.Add(new JuiceStream
|
||||||
{
|
{
|
||||||
X = 0.5f - width / 2,
|
X = CatchPlayfield.CENTER_X - width / 2,
|
||||||
Path = new SliderPath(PathType.Linear, new[]
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
|
new Vector2(width, 0)
|
||||||
}),
|
}),
|
||||||
StartTime = i * 2000,
|
StartTime = i * 2000,
|
||||||
NewCombo = i % 8 == 0
|
NewCombo = i % 8 == 0
|
||||||
|
@ -1,36 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
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.Drawables;
|
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneBananaShower : PlayerTestScene
|
public class TestSceneBananaShower : TestSceneCatchPlayer
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(BananaShower),
|
|
||||||
typeof(Banana),
|
|
||||||
typeof(DrawableBananaShower),
|
|
||||||
typeof(DrawableBanana),
|
|
||||||
|
|
||||||
typeof(CatchRuleset),
|
|
||||||
typeof(DrawableCatchRuleset),
|
|
||||||
};
|
|
||||||
|
|
||||||
public TestSceneBananaShower()
|
|
||||||
: base(new CatchRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBananaShower()
|
public void TestBananaShower()
|
||||||
{
|
{
|
||||||
|
@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatchPlayer : PlayerTestScene
|
public class TestSceneCatchPlayer : PlayerTestScene
|
||||||
{
|
{
|
||||||
public TestSceneCatchPlayer()
|
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||||
: base(new CatchRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,18 +4,13 @@
|
|||||||
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.Tests.Visual;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatchStacker : PlayerTestScene
|
public class TestSceneCatchStacker : TestSceneCatchPlayer
|
||||||
{
|
{
|
||||||
public TestSceneCatchStacker()
|
|
||||||
: base(new CatchRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||||
{
|
{
|
||||||
var beatmap = new Beatmap
|
var beatmap = new Beatmap
|
||||||
@ -28,7 +23,14 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
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)) * CatchPlayfield.WIDTH,
|
||||||
|
StartTime = i * 100,
|
||||||
|
NewCombo = i % 8 == 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
@ -4,26 +4,18 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneCatcher : CatchSkinnableTestScene
|
public class TestSceneCatcher : CatchSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
|
||||||
{
|
|
||||||
typeof(CatcherArea),
|
|
||||||
typeof(CatcherSprite)
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
SetContents(() => new Catcher
|
SetContents(() => new Catcher(new Container())
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.None,
|
RelativePositionAxes = Axes.None,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
|
Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size })
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopCentre,
|
||||||
CreateDrawableRepresentation = ((DrawableRuleset<CatchHitObject>)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
|
CreateDrawableRepresentation = ((DrawableRuleset<CatchHitObject>)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -22,15 +21,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneDrawableHitObjects : OsuTestScene
|
public class TestSceneDrawableHitObjects : OsuTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(Catcher),
|
|
||||||
typeof(DrawableCatchRuleset),
|
|
||||||
typeof(DrawableFruit),
|
|
||||||
typeof(DrawableJuiceStream),
|
|
||||||
typeof(DrawableBanana)
|
|
||||||
};
|
|
||||||
|
|
||||||
private DrawableCatchRuleset drawableRuleset;
|
private DrawableCatchRuleset drawableRuleset;
|
||||||
private double playfieldTime => drawableRuleset.Playfield.Time.Current;
|
private double playfieldTime => drawableRuleset.Playfield.Time.Current;
|
||||||
|
|
||||||
@ -168,8 +158,8 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
private float getXCoords(bool hit)
|
private float getXCoords(bool hit)
|
||||||
{
|
{
|
||||||
const float x_offset = 0.2f;
|
const float x_offset = 0.2f * CatchPlayfield.WIDTH;
|
||||||
float xCoords = drawableRuleset.Playfield.Width / 2;
|
float xCoords = CatchPlayfield.CENTER_X;
|
||||||
|
|
||||||
if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield)
|
if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield)
|
||||||
catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset;
|
catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset;
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Rulesets.Catch.Mods;
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
|
||||||
@ -11,8 +8,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects
|
public class TestSceneDrawableHitObjectsHidden : TestSceneDrawableHitObjects
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] { typeof(CatchModHidden) }).ToList();
|
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
|
@ -2,13 +2,10 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables.Pieces;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
@ -16,22 +13,6 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneFruitObjects : CatchSkinnableTestScene
|
public class TestSceneFruitObjects : CatchSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
|
||||||
{
|
|
||||||
typeof(CatchHitObject),
|
|
||||||
typeof(Fruit),
|
|
||||||
typeof(FruitPiece),
|
|
||||||
typeof(Droplet),
|
|
||||||
typeof(Banana),
|
|
||||||
typeof(BananaShower),
|
|
||||||
typeof(DrawableCatchHitObject),
|
|
||||||
typeof(DrawableFruit),
|
|
||||||
typeof(DrawableDroplet),
|
|
||||||
typeof(DrawableBanana),
|
|
||||||
typeof(DrawableBananaShower),
|
|
||||||
typeof(Pulp),
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
@ -10,24 +9,13 @@ 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;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneHyperDash : PlayerTestScene
|
public class TestSceneHyperDash : TestSceneCatchPlayer
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(CatcherArea),
|
|
||||||
};
|
|
||||||
|
|
||||||
public TestSceneHyperDash()
|
|
||||||
: base(new CatchRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override bool Autoplay => true;
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -59,13 +47,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Should produce a hyper-dash (edge case test)
|
// Should produce a hyper-dash (edge case test)
|
||||||
beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56 / 512f, NewCombo = true });
|
beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true });
|
||||||
beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308 / 512f, NewCombo = true });
|
beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true });
|
||||||
|
|
||||||
double startTime = 3000;
|
double startTime = 3000;
|
||||||
|
|
||||||
const float left_x = 0.02f;
|
const float left_x = 0.02f * CatchPlayfield.WIDTH;
|
||||||
const float right_x = 0.98f;
|
const float right_x = 0.98f * CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
createObjects(() => new Fruit { X = left_x });
|
createObjects(() => new Fruit { X = left_x });
|
||||||
createObjects(() => new TestJuiceStream(right_x), 1);
|
createObjects(() => new TestJuiceStream(right_x), 1);
|
||||||
|
206
osu.Game.Rulesets.Catch.Tests/TestSceneHyperDashColouring.cs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneHyperDashColouring : OsuTestScene
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skins { get; set; }
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDefaultCatcherColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin();
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomCatcherColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashColour = Color4.Goldenrod
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, skin.HyperDashColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomEndGlowColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashAfterImageColour = Color4.Lime
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR, skin.HyperDashAfterImageColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomEndGlowColourPriority()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashColour = Color4.Goldenrod,
|
||||||
|
HyperDashAfterImageColour = Color4.Lime
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashCatcherColour(skin, skin.HyperDashColour, skin.HyperDashAfterImageColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDefaultFruitColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin();
|
||||||
|
|
||||||
|
checkHyperDashFruitColour(skin, Catcher.DEFAULT_HYPER_DASH_COLOUR);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomFruitColour()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashFruitColour = Color4.Cyan
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCustomFruitColourPriority()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashColour = Color4.Goldenrod,
|
||||||
|
HyperDashFruitColour = Color4.Cyan
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashFruitColour(skin, skin.HyperDashFruitColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFruitColourFallback()
|
||||||
|
{
|
||||||
|
var skin = new TestSkin
|
||||||
|
{
|
||||||
|
HyperDashColour = Color4.Goldenrod
|
||||||
|
};
|
||||||
|
|
||||||
|
checkHyperDashFruitColour(skin, skin.HyperDashColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkHyperDashCatcherColour(ISkin skin, Color4 expectedCatcherColour, Color4? expectedEndGlowColour = null)
|
||||||
|
{
|
||||||
|
CatcherArea catcherArea = null;
|
||||||
|
CatcherTrailDisplay trails = null;
|
||||||
|
|
||||||
|
AddStep("create hyper-dashing catcher", () =>
|
||||||
|
{
|
||||||
|
Child = setupSkinHierarchy(catcherArea = new CatcherArea
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(4f),
|
||||||
|
}, skin);
|
||||||
|
|
||||||
|
trails = catcherArea.OfType<CatcherTrailDisplay>().Single();
|
||||||
|
catcherArea.MovableCatcher.SetHyperDashState(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("catcher colour is correct", () => catcherArea.MovableCatcher.Colour == expectedCatcherColour);
|
||||||
|
|
||||||
|
AddAssert("catcher trails colours are correct", () => trails.HyperDashTrailsColour == expectedCatcherColour);
|
||||||
|
AddAssert("catcher end-glow colours are correct", () => trails.EndGlowSpritesColour == (expectedEndGlowColour ?? expectedCatcherColour));
|
||||||
|
|
||||||
|
AddStep("finish hyper-dashing", () =>
|
||||||
|
{
|
||||||
|
catcherArea.MovableCatcher.SetHyperDashState(1);
|
||||||
|
catcherArea.MovableCatcher.FinishTransforms();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("catcher colour returned to white", () => catcherArea.MovableCatcher.Colour == Color4.White);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkHyperDashFruitColour(ISkin skin, Color4 expectedColour)
|
||||||
|
{
|
||||||
|
DrawableFruit drawableFruit = null;
|
||||||
|
|
||||||
|
AddStep("create hyper-dash fruit", () =>
|
||||||
|
{
|
||||||
|
var fruit = new Fruit { HyperDashTarget = new Banana() };
|
||||||
|
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
Child = setupSkinHierarchy(drawableFruit = new DrawableFruit(fruit)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(4f),
|
||||||
|
}, skin);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("hyper-dash colour is correct", () => checkLegacyFruitHyperDashColour(drawableFruit, expectedColour));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Drawable setupSkinHierarchy(Drawable child, ISkin skin)
|
||||||
|
{
|
||||||
|
var legacySkinProvider = new SkinProvidingContainer(skins.GetSkin(DefaultLegacySkin.Info));
|
||||||
|
var testSkinProvider = new SkinProvidingContainer(skin);
|
||||||
|
var legacySkinTransformer = new SkinProvidingContainer(new CatchLegacySkinTransformer(testSkinProvider));
|
||||||
|
|
||||||
|
return legacySkinProvider
|
||||||
|
.WithChild(testSkinProvider
|
||||||
|
.WithChild(legacySkinTransformer
|
||||||
|
.WithChild(child)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkLegacyFruitHyperDashColour(DrawableFruit fruit, Color4 expectedColour) =>
|
||||||
|
fruit.ChildrenOfType<SkinnableDrawable>().First().Drawable.ChildrenOfType<Sprite>().Any(c => c.Colour == expectedColour);
|
||||||
|
|
||||||
|
private class TestSkin : LegacySkin
|
||||||
|
{
|
||||||
|
public Color4 HyperDashColour
|
||||||
|
{
|
||||||
|
get => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()];
|
||||||
|
set => Configuration.CustomColours[CatchSkinColour.HyperDash.ToString()] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color4 HyperDashAfterImageColour
|
||||||
|
{
|
||||||
|
get => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()];
|
||||||
|
set => Configuration.CustomColours[CatchSkinColour.HyperDashAfterImage.ToString()] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Color4 HyperDashFruitColour
|
||||||
|
{
|
||||||
|
get => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()];
|
||||||
|
set => Configuration.CustomColours[CatchSkinColour.HyperDashFruit.ToString()] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestSkin()
|
||||||
|
: base(new SkinInfo(), null, null, string.Empty)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,20 +5,15 @@ 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.UI;
|
||||||
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.Visual;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
{
|
{
|
||||||
public class TestSceneJuiceStream : PlayerTestScene
|
public class TestSceneJuiceStream : TestSceneCatchPlayer
|
||||||
{
|
{
|
||||||
public TestSceneJuiceStream()
|
|
||||||
: base(new CatchRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestJuiceStreamEndingCombo()
|
public void TestJuiceStreamEndingCombo()
|
||||||
{
|
{
|
||||||
@ -36,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
new JuiceStream
|
new JuiceStream
|
||||||
{
|
{
|
||||||
X = 0.5f,
|
X = CatchPlayfield.CENTER_X,
|
||||||
Path = new SliderPath(PathType.Linear, new[]
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
{
|
{
|
||||||
Vector2.Zero,
|
Vector2.Zero,
|
||||||
@ -46,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
},
|
},
|
||||||
new Banana
|
new Banana
|
||||||
{
|
{
|
||||||
X = 0.5f,
|
X = CatchPlayfield.CENTER_X,
|
||||||
StartTime = 1000,
|
StartTime = 1000,
|
||||||
NewCombo = true
|
NewCombo = true
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="3.15.1" />
|
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
|
@ -5,7 +5,6 @@ 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.Linq;
|
using System.Linq;
|
||||||
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;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
@ -28,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
switch (obj)
|
switch (obj)
|
||||||
{
|
{
|
||||||
case IHasCurve curveData:
|
case IHasPathWithRepeats curveData:
|
||||||
return new JuiceStream
|
return new JuiceStream
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
@ -36,13 +35,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
Path = curveData.Path,
|
Path = curveData.Path,
|
||||||
NodeSamples = curveData.NodeSamples,
|
NodeSamples = curveData.NodeSamples,
|
||||||
RepeatCount = curveData.RepeatCount,
|
RepeatCount = curveData.RepeatCount,
|
||||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,
|
X = positionData?.X ?? 0,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
|
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0
|
||||||
}.Yield();
|
}.Yield();
|
||||||
|
|
||||||
case IHasEndTime endTime:
|
case IHasDuration endTime:
|
||||||
return new BananaShower
|
return new BananaShower
|
||||||
{
|
{
|
||||||
StartTime = obj.StartTime,
|
StartTime = obj.StartTime,
|
||||||
@ -59,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
Samples = obj.Samples,
|
Samples = obj.Samples,
|
||||||
NewCombo = comboData?.NewCombo ?? false,
|
NewCombo = comboData?.NewCombo ?? false,
|
||||||
ComboOffset = comboData?.ComboOffset ?? 0,
|
ComboOffset = comboData?.ComboOffset ?? 0,
|
||||||
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH
|
X = positionData?.X ?? 0
|
||||||
}.Yield();
|
}.Yield();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
case BananaShower bananaShower:
|
case BananaShower bananaShower:
|
||||||
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
|
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
|
||||||
{
|
{
|
||||||
banana.XOffset = (float)rng.NextDouble();
|
banana.XOffset = (float)(rng.NextDouble() * CatchPlayfield.WIDTH);
|
||||||
rng.Next(); // osu!stable retrieved a random banana type
|
rng.Next(); // osu!stable retrieved a random banana type
|
||||||
rng.Next(); // osu!stable retrieved a random banana rotation
|
rng.Next(); // osu!stable retrieved a random banana rotation
|
||||||
rng.Next(); // osu!stable retrieved a random banana colour
|
rng.Next(); // osu!stable retrieved a random banana colour
|
||||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
case JuiceStream juiceStream:
|
case JuiceStream juiceStream:
|
||||||
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
|
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
|
||||||
lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X / CatchPlayfield.BASE_WIDTH;
|
lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X;
|
||||||
|
|
||||||
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
|
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
|
||||||
lastStartTime = juiceStream.StartTime;
|
lastStartTime = juiceStream.StartTime;
|
||||||
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
catchObject.XOffset = 0;
|
catchObject.XOffset = 0;
|
||||||
|
|
||||||
if (catchObject is TinyDroplet)
|
if (catchObject is TinyDroplet)
|
||||||
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X);
|
catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.X, CatchPlayfield.WIDTH - catchObject.X);
|
||||||
else if (catchObject is Droplet)
|
else if (catchObject is Droplet)
|
||||||
rng.Next(); // osu!stable retrieved a random droplet rotation
|
rng.Next(); // osu!stable retrieved a random droplet rotation
|
||||||
}
|
}
|
||||||
@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ReSharper disable once PossibleLossOfFraction
|
// ReSharper disable once PossibleLossOfFraction
|
||||||
if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3)
|
if (Math.Abs(positionDiff) < timeDiff / 3)
|
||||||
applyOffset(ref offsetPosition, positionDiff);
|
applyOffset(ref offsetPosition, positionDiff);
|
||||||
|
|
||||||
hitObject.XOffset = offsetPosition - hitObject.X;
|
hitObject.XOffset = offsetPosition - hitObject.X;
|
||||||
@ -149,12 +149,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
|
private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng)
|
||||||
{
|
{
|
||||||
bool right = rng.NextBool();
|
bool right = rng.NextBool();
|
||||||
float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH;
|
float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset)));
|
||||||
|
|
||||||
if (right)
|
if (right)
|
||||||
{
|
{
|
||||||
// Clamp to the right bound
|
// Clamp to the right bound
|
||||||
if (position + rand <= 1)
|
if (position + rand <= CatchPlayfield.WIDTH)
|
||||||
position += rand;
|
position += rand;
|
||||||
else
|
else
|
||||||
position -= rand;
|
position -= rand;
|
||||||
@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime));
|
||||||
|
|
||||||
double halfCatcherWidth = CatcherArea.GetCatcherSize(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2;
|
||||||
int lastDirection = 0;
|
int lastDirection = 0;
|
||||||
double lastExcess = halfCatcherWidth;
|
double lastExcess = halfCatcherWidth;
|
||||||
|
|
||||||
|
@ -21,11 +21,13 @@ using osu.Game.Rulesets.Difficulty;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using System;
|
using System;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch
|
namespace osu.Game.Rulesets.Catch
|
||||||
{
|
{
|
||||||
|
[ExcludeFromDynamicCompile]
|
||||||
public class CatchRuleset : Ruleset, ILegacyRuleset
|
public class CatchRuleset : Ruleset, ILegacyRuleset
|
||||||
{
|
{
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => new DrawableCatchRuleset(this, beatmap, mods);
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
public class CatchDifficultyCalculator : DifficultyCalculator
|
public class CatchDifficultyCalculator : DifficultyCalculator
|
||||||
{
|
{
|
||||||
private const double star_scaling_factor = 0.145;
|
private const double star_scaling_factor = 0.153;
|
||||||
|
|
||||||
protected override int SectionLength => 750;
|
protected override int SectionLength => 750;
|
||||||
|
|
||||||
@ -71,8 +71,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
protected override Skill[] CreateSkills(IBeatmap beatmap)
|
protected override Skill[] CreateSkills(IBeatmap beatmap)
|
||||||
{
|
{
|
||||||
using (var catcher = new Catcher(beatmap.BeatmapInfo.BaseDifficulty))
|
halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f;
|
||||||
halfCatcherWidth = catcher.CatchWidth * 0.5f;
|
|
||||||
|
// For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay.
|
||||||
|
halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f);
|
||||||
|
|
||||||
return new Skill[]
|
return new Skill[]
|
||||||
{
|
{
|
||||||
|
@ -4,12 +4,12 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Scoring.Legacy;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty
|
namespace osu.Game.Rulesets.Catch.Difficulty
|
||||||
{
|
{
|
||||||
@ -34,11 +34,11 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
{
|
{
|
||||||
mods = Score.Mods;
|
mods = Score.Mods;
|
||||||
|
|
||||||
fruitsHit = Score?.GetCount300() ?? Score.Statistics[HitResult.Perfect];
|
fruitsHit = Score.Statistics.GetOrDefault(HitResult.Perfect);
|
||||||
ticksHit = Score?.GetCount100() ?? 0;
|
ticksHit = Score.Statistics.GetOrDefault(HitResult.LargeTickHit);
|
||||||
tinyTicksHit = Score?.GetCount50() ?? 0;
|
tinyTicksHit = Score.Statistics.GetOrDefault(HitResult.SmallTickHit);
|
||||||
tinyTicksMissed = Score?.GetCountKatu() ?? 0;
|
tinyTicksMissed = Score.Statistics.GetOrDefault(HitResult.SmallTickMiss);
|
||||||
misses = Score.Statistics[HitResult.Miss];
|
misses = Score.Statistics.GetOrDefault(HitResult.Miss);
|
||||||
|
|
||||||
// Don't count scores made with supposedly unranked mods
|
// Don't count scores made with supposedly unranked mods
|
||||||
if (mods.Any(m => !m.Ranked))
|
if (mods.Any(m => !m.Ranked))
|
||||||
@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
double lengthBonus =
|
double lengthBonus =
|
||||||
0.95 + 0.4 * Math.Min(1.0, numTotalHits / 3000.0) +
|
0.95 + 0.3 * Math.Min(1.0, numTotalHits / 2500.0) +
|
||||||
(numTotalHits > 3000 ? Math.Log10(numTotalHits / 3000.0) * 0.5 : 0.0);
|
(numTotalHits > 2500 ? Math.Log10(numTotalHits / 2500.0) * 0.475 : 0.0);
|
||||||
|
|
||||||
// Longer maps are worth more
|
// Longer maps are worth more
|
||||||
value *= lengthBonus;
|
value *= lengthBonus;
|
||||||
@ -63,19 +63,28 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
|
|
||||||
// Combo scaling
|
// Combo scaling
|
||||||
if (Attributes.MaxCombo > 0)
|
if (Attributes.MaxCombo > 0)
|
||||||
value *= Math.Min(Math.Pow(Attributes.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
value *= Math.Min(Math.Pow(Score.MaxCombo, 0.8) / Math.Pow(Attributes.MaxCombo, 0.8), 1.0);
|
||||||
|
|
||||||
|
double approachRate = Attributes.ApproachRate;
|
||||||
double approachRateFactor = 1.0;
|
double approachRateFactor = 1.0;
|
||||||
if (Attributes.ApproachRate > 9.0)
|
if (approachRate > 9.0)
|
||||||
approachRateFactor += 0.1 * (Attributes.ApproachRate - 9.0); // 10% for each AR above 9
|
approachRateFactor += 0.1 * (approachRate - 9.0); // 10% for each AR above 9
|
||||||
else if (Attributes.ApproachRate < 8.0)
|
if (approachRate > 10.0)
|
||||||
approachRateFactor += 0.025 * (8.0 - Attributes.ApproachRate); // 2.5% for each AR below 8
|
approachRateFactor += 0.1 * (approachRate - 10.0); // Additional 10% at AR 11, 30% total
|
||||||
|
else if (approachRate < 8.0)
|
||||||
|
approachRateFactor += 0.025 * (8.0 - approachRate); // 2.5% for each AR below 8
|
||||||
|
|
||||||
value *= approachRateFactor;
|
value *= approachRateFactor;
|
||||||
|
|
||||||
if (mods.Any(m => m is ModHidden))
|
if (mods.Any(m => m is ModHidden))
|
||||||
// Hiddens gives nothing on max approach rate, and more the lower it is
|
{
|
||||||
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
|
value *= 1.05 + 0.075 * (10.0 - Math.Min(10.0, Attributes.ApproachRate)); // 7.5% for each AR below 10
|
||||||
|
// Hiddens gives almost nothing on max approach rate, and more the lower it is
|
||||||
|
if (approachRate <= 10.0)
|
||||||
|
value *= 1.05 + 0.075 * (10.0 - approachRate); // 7.5% for each AR below 10
|
||||||
|
else if (approachRate > 10.0)
|
||||||
|
value *= 1.01 + 0.04 * (11.0 - Math.Min(11.0, approachRate)); // 5% at AR 10, 1% at AR 11
|
||||||
|
}
|
||||||
|
|
||||||
if (mods.Any(m => m is ModFlashlight))
|
if (mods.Any(m => m is ModFlashlight))
|
||||||
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps.
|
||||||
@ -91,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private float accuracy() => totalHits() == 0 ? 0 : Math.Clamp((float)totalSuccessfulHits() / totalHits(), 0, 1);
|
private double accuracy() => totalHits() == 0 ? 0 : Math.Clamp((double)totalSuccessfulHits() / totalHits(), 0, 1);
|
||||||
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
|
private int totalHits() => tinyTicksHit + ticksHit + fruitsHit + misses + tinyTicksMissed;
|
||||||
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
|
private int totalSuccessfulHits() => tinyTicksHit + ticksHit + fruitsHit;
|
||||||
private int totalComboHits() => misses + ticksHit + fruitsHit;
|
private int totalComboHits() => misses + ticksHit + fruitsHit;
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
@ -21,21 +20,24 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing
|
|||||||
public readonly float LastNormalizedPosition;
|
public readonly float LastNormalizedPosition;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 25ms.
|
/// Milliseconds elapsed since the start time of the previous <see cref="CatchDifficultyHitObject"/>, with a minimum of 40ms.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly double StrainTime;
|
public readonly double StrainTime;
|
||||||
|
|
||||||
|
public readonly double ClockRate;
|
||||||
|
|
||||||
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
public CatchDifficultyHitObject(HitObject hitObject, HitObject lastObject, double clockRate, float halfCatcherWidth)
|
||||||
: base(hitObject, lastObject, clockRate)
|
: base(hitObject, lastObject, clockRate)
|
||||||
{
|
{
|
||||||
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
|
||||||
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
var scalingFactor = normalized_hitobject_radius / halfCatcherWidth;
|
||||||
|
|
||||||
NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
NormalizedPosition = BaseObject.X * scalingFactor;
|
||||||
LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor;
|
LastNormalizedPosition = LastObject.X * scalingFactor;
|
||||||
|
|
||||||
// Every strain interval is hard capped at the equivalent of 600 BPM streaming speed as a safety measure
|
// Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure
|
||||||
StrainTime = Math.Max(25, DeltaTime);
|
StrainTime = Math.Max(40, DeltaTime);
|
||||||
|
ClockRate = clockRate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Catch.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
|
|
||||||
@ -13,9 +12,9 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
private const float absolute_player_positioning_error = 16f;
|
private const float absolute_player_positioning_error = 16f;
|
||||||
private const float normalized_hitobject_radius = 41.0f;
|
private const float normalized_hitobject_radius = 41.0f;
|
||||||
private const double direction_change_bonus = 12.5;
|
private const double direction_change_bonus = 21.0;
|
||||||
|
|
||||||
protected override double SkillMultiplier => 850;
|
protected override double SkillMultiplier => 900;
|
||||||
protected override double StrainDecayBase => 0.2;
|
protected override double StrainDecayBase => 0.2;
|
||||||
|
|
||||||
protected override double DecayWeight => 0.94;
|
protected override double DecayWeight => 0.94;
|
||||||
@ -24,6 +23,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
|
|
||||||
private float? lastPlayerPosition;
|
private float? lastPlayerPosition;
|
||||||
private float lastDistanceMoved;
|
private float lastDistanceMoved;
|
||||||
|
private double lastStrainTime;
|
||||||
|
|
||||||
public Movement(float halfCatcherWidth)
|
public Movement(float halfCatcherWidth)
|
||||||
{
|
{
|
||||||
@ -34,8 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
{
|
{
|
||||||
var catchCurrent = (CatchDifficultyHitObject)current;
|
var catchCurrent = (CatchDifficultyHitObject)current;
|
||||||
|
|
||||||
if (lastPlayerPosition == null)
|
lastPlayerPosition ??= catchCurrent.LastNormalizedPosition;
|
||||||
lastPlayerPosition = catchCurrent.LastNormalizedPosition;
|
|
||||||
|
|
||||||
float playerPosition = Math.Clamp(
|
float playerPosition = Math.Clamp(
|
||||||
lastPlayerPosition.Value,
|
lastPlayerPosition.Value,
|
||||||
@ -45,47 +44,47 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
|||||||
|
|
||||||
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
float distanceMoved = playerPosition - lastPlayerPosition.Value;
|
||||||
|
|
||||||
double distanceAddition = Math.Pow(Math.Abs(distanceMoved), 1.3) / 500;
|
double weightedStrainTime = catchCurrent.StrainTime + 13 + (3 / catchCurrent.ClockRate);
|
||||||
double sqrtStrain = Math.Sqrt(catchCurrent.StrainTime);
|
|
||||||
|
|
||||||
double bonus = 0;
|
double distanceAddition = (Math.Pow(Math.Abs(distanceMoved), 1.3) / 510);
|
||||||
|
double sqrtStrain = Math.Sqrt(weightedStrainTime);
|
||||||
|
|
||||||
// Direction changes give an extra point!
|
double edgeDashBonus = 0;
|
||||||
|
|
||||||
|
// Direction change bonus.
|
||||||
if (Math.Abs(distanceMoved) > 0.1)
|
if (Math.Abs(distanceMoved) > 0.1)
|
||||||
{
|
{
|
||||||
if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
|
if (Math.Abs(lastDistanceMoved) > 0.1 && Math.Sign(distanceMoved) != Math.Sign(lastDistanceMoved))
|
||||||
{
|
{
|
||||||
double bonusFactor = Math.Min(absolute_player_positioning_error, Math.Abs(distanceMoved)) / absolute_player_positioning_error;
|
double bonusFactor = Math.Min(50, Math.Abs(distanceMoved)) / 50;
|
||||||
|
double antiflowFactor = Math.Max(Math.Min(70, Math.Abs(lastDistanceMoved)) / 70, 0.38);
|
||||||
|
|
||||||
distanceAddition += direction_change_bonus / sqrtStrain * bonusFactor;
|
distanceAddition += direction_change_bonus / Math.Sqrt(lastStrainTime + 16) * bonusFactor * antiflowFactor * Math.Max(1 - Math.Pow(weightedStrainTime / 1000, 3), 0);
|
||||||
|
|
||||||
// Bonus for tougher direction switches and "almost" hyperdashes at this point
|
|
||||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 10 / CatchPlayfield.BASE_WIDTH)
|
|
||||||
bonus = 0.3 * bonusFactor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Base bonus for every movement, giving some weight to streams.
|
// Base bonus for every movement, giving some weight to streams.
|
||||||
distanceAddition += 7.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
|
distanceAddition += 12.5 * Math.Min(Math.Abs(distanceMoved), normalized_hitobject_radius * 2) / (normalized_hitobject_radius * 6) / sqrtStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bonus for "almost" hyperdashes at corner points
|
// Bonus for edge dashes.
|
||||||
if (catchCurrent.LastObject.DistanceToHyperDash <= 10.0f / CatchPlayfield.BASE_WIDTH)
|
if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f)
|
||||||
{
|
{
|
||||||
if (!catchCurrent.LastObject.HyperDash)
|
if (!catchCurrent.LastObject.HyperDash)
|
||||||
bonus += 1.0;
|
edgeDashBonus += 5.7;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// After a hyperdash we ARE in the correct position. Always!
|
// After a hyperdash we ARE in the correct position. Always!
|
||||||
playerPosition = catchCurrent.NormalizedPosition;
|
playerPosition = catchCurrent.NormalizedPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
distanceAddition *= 1.0 + bonus * ((10 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 10);
|
distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values
|
||||||
}
|
}
|
||||||
|
|
||||||
lastPlayerPosition = playerPosition;
|
lastPlayerPosition = playerPosition;
|
||||||
lastDistanceMoved = distanceMoved;
|
lastDistanceMoved = distanceMoved;
|
||||||
|
lastStrainTime = catchCurrent.StrainTime;
|
||||||
|
|
||||||
return distanceAddition / catchCurrent.StrainTime;
|
return distanceAddition / weightedStrainTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Judgements
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
case HitResult.Perfect:
|
case HitResult.Perfect:
|
||||||
return 0.01;
|
return DEFAULT_MAX_HEALTH_INCREASE * 0.75;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,17 +9,26 @@ 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 osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Mods
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
{
|
{
|
||||||
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>
|
public class CatchModRelax : ModRelax, IApplicableToDrawableRuleset<CatchHitObject>, IApplicableToPlayer
|
||||||
{
|
{
|
||||||
public override string Description => @"Use the mouse to control the catcher.";
|
public override string Description => @"Use the mouse to control the catcher.";
|
||||||
|
|
||||||
|
private DrawableRuleset<CatchHitObject> drawableRuleset;
|
||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
|
this.drawableRuleset = drawableRuleset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyToPlayer(Player player)
|
||||||
|
{
|
||||||
|
if (!drawableRuleset.HasReplayLoaded.Value)
|
||||||
|
drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||||
@ -34,7 +43,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
//disable keyboard controls
|
// disable keyboard controls
|
||||||
public bool OnPressed(CatchAction action) => true;
|
public bool OnPressed(CatchAction action) => true;
|
||||||
|
|
||||||
public void OnReleased(CatchAction action)
|
public void OnReleased(CatchAction action)
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Threading;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
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, IHasDuration
|
||||||
{
|
{
|
||||||
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana;
|
||||||
|
|
||||||
@ -14,13 +15,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
public override Judgement CreateJudgement() => new IgnoreJudgement();
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects()
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects();
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
createBananas();
|
createBananas(cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createBananas()
|
private void createBananas(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
double spacing = Duration;
|
double spacing = Duration;
|
||||||
while (spacing > 100)
|
while (spacing > 100)
|
||||||
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
for (double i = StartTime; i <= EndTime; i += spacing)
|
for (double i = StartTime; i <= EndTime; i += spacing)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
AddNested(new Banana
|
AddNested(new Banana
|
||||||
{
|
{
|
||||||
Samples = Samples,
|
Samples = Samples,
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
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 osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -17,6 +18,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
private float x;
|
private float x;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The horizontal position of the fruit between 0 and <see cref="CatchPlayfield.WIDTH"/>.
|
||||||
|
/// </summary>
|
||||||
public float X
|
public float X
|
||||||
{
|
{
|
||||||
get => x + XOffset;
|
get => x + XOffset;
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -70,12 +71,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
|
|
||||||
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale;
|
||||||
|
|
||||||
protected override float SamplePlaybackPosition => HitObject.X;
|
protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH;
|
||||||
|
|
||||||
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
protected DrawableCatchHitObject(CatchHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.X;
|
|
||||||
X = hitObject.X;
|
X = hitObject.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
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.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
BorderColour = Color4.Red,
|
BorderColour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
BorderThickness = 12f * RADIUS_ADJUST,
|
BorderThickness = 12f * RADIUS_ADJUST,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -77,7 +78,7 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables
|
|||||||
Alpha = 0.3f,
|
Alpha = 0.3f,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = Color4.Red,
|
Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -3,17 +3,17 @@
|
|||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
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.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
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 class JuiceStream : CatchHitObject, IHasCurve
|
public class JuiceStream : CatchHitObject, IHasPathWithRepeats
|
||||||
{
|
{
|
||||||
/// <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.
|
||||||
@ -45,9 +45,9 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
TickDistance = scoringDistance / difficulty.SliderTickRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void CreateNestedHitObjects()
|
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
base.CreateNestedHitObjects();
|
base.CreateNestedHitObjects(cancellationToken);
|
||||||
|
|
||||||
var dropletSamples = Samples.Select(s => new HitSampleInfo
|
var dropletSamples = Samples.Select(s => new HitSampleInfo
|
||||||
{
|
{
|
||||||
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
SliderEventDescriptor? lastEvent = null;
|
SliderEventDescriptor? lastEvent = null;
|
||||||
|
|
||||||
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset))
|
foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken))
|
||||||
{
|
{
|
||||||
// generate tiny droplets since the last point
|
// generate tiny droplets since the last point
|
||||||
if (lastEvent != null)
|
if (lastEvent != null)
|
||||||
@ -73,11 +73,13 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny)
|
for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
AddNested(new TinyDroplet
|
AddNested(new TinyDroplet
|
||||||
{
|
{
|
||||||
StartTime = t + lastEvent.Value.Time,
|
StartTime = t + lastEvent.Value.Time,
|
||||||
X = X + Path.PositionAt(
|
X = X + Path.PositionAt(
|
||||||
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH,
|
lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
Samples = dropletSamples,
|
Samples = dropletSamples,
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH,
|
X = X + Path.PositionAt(e.PathProgress).X,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -105,22 +107,22 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
{
|
{
|
||||||
Samples = Samples,
|
Samples = Samples,
|
||||||
StartTime = e.Time,
|
StartTime = e.Time,
|
||||||
X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH,
|
X = X + Path.PositionAt(e.PathProgress).X,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public double EndTime
|
public float EndX => X + this.CurvePositionAt(1).X;
|
||||||
|
|
||||||
|
public double Duration
|
||||||
{
|
{
|
||||||
get => StartTime + this.SpanCount() * Path.Distance / Velocity;
|
get => this.SpanCount() * Path.Distance / Velocity;
|
||||||
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed.
|
||||||
}
|
}
|
||||||
|
|
||||||
public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH;
|
public double EndTime => StartTime + Duration;
|
||||||
|
|
||||||
public double Duration => EndTime - StartTime;
|
|
||||||
|
|
||||||
private readonly SliderPath path = new SliderPath();
|
private readonly SliderPath path = new SliderPath();
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
// todo: add support for HT DT
|
// todo: add support for HT DT
|
||||||
const double dash_speed = Catcher.BASE_SPEED;
|
const double dash_speed = Catcher.BASE_SPEED;
|
||||||
const double movement_speed = dash_speed / 2;
|
const double movement_speed = dash_speed / 2;
|
||||||
float lastPosition = 0.5f;
|
float lastPosition = CatchPlayfield.CENTER_X;
|
||||||
double lastTime = 0;
|
double lastTime = 0;
|
||||||
|
|
||||||
void moveToNext(CatchHitObject h)
|
void moveToNext(CatchHitObject h)
|
||||||
@ -51,11 +51,11 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
bool impossibleJump = speedRequired > movement_speed * 2;
|
bool impossibleJump = speedRequired > movement_speed * 2;
|
||||||
|
|
||||||
// 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 * 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;
|
||||||
addFrame(h.StartTime, lastPosition);
|
addFrame(h.StartTime, lastPosition);
|
||||||
return;
|
return;
|
||||||
@ -72,14 +72,14 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
}
|
}
|
||||||
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
|
||||||
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
|
addFrame(h.StartTime - timeAvailable + 1, lastPosition, true);
|
||||||
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
|
addFrame(h.StartTime - timeAvailable + timeAtDashSpeed, midPosition);
|
||||||
addFrame(h.StartTime, h.X);
|
addFrame(h.StartTime, h.X);
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Catch.UI;
|
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Replays.Types;
|
using osu.Game.Rulesets.Replays.Types;
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
|
public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null)
|
||||||
{
|
{
|
||||||
Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH;
|
Position = currentFrame.Position.X;
|
||||||
Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
|
Dashing = currentFrame.ButtonState == ReplayButtonState.Left1;
|
||||||
|
|
||||||
if (Dashing)
|
if (Dashing)
|
||||||
@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Replays
|
|||||||
|
|
||||||
if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1;
|
if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1;
|
||||||
|
|
||||||
return new LegacyReplayFrame(Time, Position * CatchPlayfield.BASE_WIDTH, null, state);
|
return new LegacyReplayFrame(Time, Position, null, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,21 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using osu.Framework.Audio.Sample;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Textures;
|
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Skinning
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
{
|
{
|
||||||
public class CatchLegacySkinTransformer : ISkin
|
public class CatchLegacySkinTransformer : LegacySkinTransformer
|
||||||
{
|
{
|
||||||
private readonly ISkin source;
|
|
||||||
|
|
||||||
public CatchLegacySkinTransformer(ISkinSource source)
|
public CatchLegacySkinTransformer(ISkinSource source)
|
||||||
|
: base(source)
|
||||||
{
|
{
|
||||||
this.source = source;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (!(component is CatchSkinComponent catchSkinComponent))
|
if (!(component is CatchSkinComponent catchSkinComponent))
|
||||||
return null;
|
return null;
|
||||||
@ -61,10 +56,15 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => source.GetTexture(componentName);
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
|
{
|
||||||
|
switch (lookup)
|
||||||
|
{
|
||||||
|
case CatchSkinColour colour:
|
||||||
|
return Source.GetConfig<SkinCustomColourLookup, TValue>(new SkinCustomColourLookup(colour));
|
||||||
|
}
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample);
|
return Source.GetConfig<TLookup, TValue>(lookup);
|
||||||
|
}
|
||||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => source.GetConfig<TLookup, TValue>(lookup);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
23
osu.Game.Rulesets.Catch/Skinning/CatchSkinColour.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning
|
||||||
|
{
|
||||||
|
public enum CatchSkinColour
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The colour to be used for the catcher while in hyper-dashing state.
|
||||||
|
/// </summary>
|
||||||
|
HyperDash,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The colour to be used for fruits that grant the catcher the ability to hyper-dash.
|
||||||
|
/// </summary>
|
||||||
|
HyperDashFruit,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The colour to be used for the "exploding" catcher sprite on beginning of hyper-dashing.
|
||||||
|
/// </summary>
|
||||||
|
HyperDashAfterImage,
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ 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.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -55,14 +56,16 @@ namespace osu.Game.Rulesets.Catch.Skinning
|
|||||||
{
|
{
|
||||||
var hyperDash = new Sprite
|
var hyperDash = new Sprite
|
||||||
{
|
{
|
||||||
Texture = skin.GetTexture(lookupName),
|
|
||||||
Colour = Color4.Red,
|
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
Depth = 1,
|
Depth = 1,
|
||||||
Alpha = 0.7f,
|
Alpha = 0.7f,
|
||||||
Scale = new Vector2(1.2f)
|
Scale = new Vector2(1.2f),
|
||||||
|
Texture = skin.GetTexture(lookupName),
|
||||||
|
Colour = skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashFruit)?.Value ??
|
||||||
|
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||||
|
Catcher.DEFAULT_HYPER_DASH_COLOUR,
|
||||||
};
|
};
|
||||||
|
|
||||||
AddInternal(hyperDash);
|
AddInternal(hyperDash);
|
||||||
|
@ -16,7 +16,16 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
public class CatchPlayfield : ScrollingPlayfield
|
public class CatchPlayfield : ScrollingPlayfield
|
||||||
{
|
{
|
||||||
public const float BASE_WIDTH = 512;
|
/// <summary>
|
||||||
|
/// The width of the playfield.
|
||||||
|
/// The horizontal movement of the catcher is confined in the area of this width.
|
||||||
|
/// </summary>
|
||||||
|
public const float WIDTH = 512;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The center position of the playfield.
|
||||||
|
/// </summary>
|
||||||
|
public const float CENTER_X = WIDTH / 2;
|
||||||
|
|
||||||
internal readonly CatcherArea CatcherArea;
|
internal readonly CatcherArea CatcherArea;
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.BASE_WIDTH);
|
Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH);
|
||||||
Size = Vector2.Divide(Vector2.One, Scale);
|
Size = Vector2.Divide(Vector2.One, Scale);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,24 +3,37 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
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.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class Catcher : Container, IKeyBindingHandler<CatchAction>
|
public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
|
||||||
|
/// and end glow/after-image during a hyper-dash.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Color4 DEFAULT_HYPER_DASH_COLOUR = Color4.Red;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The duration between transitioning to hyper-dash state.
|
||||||
|
/// </summary>
|
||||||
|
public const double HYPER_DASH_TRANSITION_DURATION = 180;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether we are hyper-dashing or not.
|
/// Whether we are hyper-dashing or not.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -29,11 +42,14 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <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;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget;
|
public Container ExplodingFruitTarget;
|
||||||
|
|
||||||
public Container AdditiveTarget;
|
[NotNull]
|
||||||
|
private readonly Container trailsTarget;
|
||||||
|
|
||||||
|
private CatcherTrailDisplay trails;
|
||||||
|
|
||||||
public CatcherAnimationState CurrentState { get; private set; }
|
public CatcherAnimationState CurrentState { get; private set; }
|
||||||
|
|
||||||
@ -43,39 +59,29 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private const float allowed_catch_range = 0.8f;
|
private const float allowed_catch_range = 0.8f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the area that can be used to attempt catches during gameplay.
|
/// The drawable catcher for <see cref="CurrentState"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal float CatchWidth => CatcherArea.CATCHER_SIZE * Math.Abs(Scale.X) * allowed_catch_range;
|
internal Drawable CurrentDrawableCatcher => currentCatcher.Drawable;
|
||||||
|
|
||||||
protected bool Dashing
|
private bool dashing;
|
||||||
|
|
||||||
|
public bool Dashing
|
||||||
{
|
{
|
||||||
get => dashing;
|
get => dashing;
|
||||||
set
|
protected set
|
||||||
{
|
{
|
||||||
if (value == dashing) return;
|
if (value == dashing) return;
|
||||||
|
|
||||||
dashing = value;
|
dashing = value;
|
||||||
|
|
||||||
Trail |= dashing;
|
updateTrailVisibility();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Activate or deactivate the trail. Will be automatically deactivated when conditions to keep the trail displayed are no longer met.
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected bool Trail
|
private readonly float catchWidth;
|
||||||
{
|
|
||||||
get => trail;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == trail || AdditiveTarget == null) return;
|
|
||||||
|
|
||||||
trail = value;
|
|
||||||
|
|
||||||
if (Trail)
|
|
||||||
beginTrail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruit;
|
private Container<DrawableHitObject> caughtFruit;
|
||||||
|
|
||||||
@ -85,32 +91,32 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
private CatcherSprite currentCatcher;
|
private CatcherSprite currentCatcher;
|
||||||
|
|
||||||
|
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
private int currentDirection;
|
private int currentDirection;
|
||||||
|
|
||||||
private bool dashing;
|
|
||||||
|
|
||||||
private bool trail;
|
|
||||||
|
|
||||||
private double hyperDashModifier = 1;
|
private double hyperDashModifier = 1;
|
||||||
private int hyperDashDirection;
|
private int hyperDashDirection;
|
||||||
private float hyperDashTargetPosition;
|
private float hyperDashTargetPosition;
|
||||||
|
|
||||||
public Catcher(BeatmapDifficulty difficulty = null)
|
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
RelativePositionAxes = Axes.X;
|
this.trailsTarget = trailsTarget;
|
||||||
X = 0.5f;
|
|
||||||
|
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
Size = new Vector2(CatcherArea.CATCHER_SIZE);
|
||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
|
catchWidth = CalculateCatchWidth(Scale);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Children = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
caughtFruit = new Container<DrawableHitObject>
|
caughtFruit = new Container<DrawableHitObject>
|
||||||
{
|
{
|
||||||
@ -134,9 +140,31 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
trailsTarget.Add(trails = new CatcherTrailDisplay(this));
|
||||||
|
|
||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
|
/// </summary>
|
||||||
|
private static Vector2 calculateScale(BeatmapDifficulty difficulty)
|
||||||
|
=> new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scale">The scale of the catcher.</param>
|
||||||
|
internal static float CalculateCatchWidth(Vector2 scale)
|
||||||
|
=> CatcherArea.CATCHER_SIZE * Math.Abs(scale.X) * allowed_catch_range;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the width of the area used for attempting catches in gameplay.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="difficulty">The beatmap difficulty.</param>
|
||||||
|
internal static float CalculateCatchWidth(BeatmapDifficulty difficulty)
|
||||||
|
=> CalculateCatchWidth(calculateScale(difficulty));
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a caught fruit to the catcher's stack.
|
/// Add a caught fruit to the catcher's stack.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -161,7 +189,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
caughtFruit.Add(fruit);
|
caughtFruit.Add(fruit);
|
||||||
|
|
||||||
Add(new HitExplosion(fruit)
|
AddInternal(new HitExplosion(fruit)
|
||||||
{
|
{
|
||||||
X = fruit.X,
|
X = fruit.X,
|
||||||
Scale = new Vector2(fruit.HitObject.Scale)
|
Scale = new Vector2(fruit.HitObject.Scale)
|
||||||
@ -175,11 +203,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <returns>Whether the catch is possible.</returns>
|
/// <returns>Whether the catch is possible.</returns>
|
||||||
public bool AttemptCatch(CatchHitObject fruit)
|
public bool AttemptCatch(CatchHitObject fruit)
|
||||||
{
|
{
|
||||||
var halfCatchWidth = CatchWidth * 0.5f;
|
var halfCatchWidth = catchWidth * 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;
|
||||||
var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH;
|
var catcherPosition = Position.X;
|
||||||
|
|
||||||
var validCatch =
|
var validCatch =
|
||||||
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
catchObjectPosition >= catcherPosition - halfCatchWidth &&
|
||||||
@ -193,7 +221,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
{
|
{
|
||||||
var target = fruit.HyperDashTarget;
|
var target = fruit.HyperDashTarget;
|
||||||
var timeDifference = target.StartTime - fruit.StartTime;
|
var timeDifference = target.StartTime - fruit.StartTime;
|
||||||
double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition;
|
double positionDifference = target.X - catcherPosition;
|
||||||
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0);
|
||||||
|
|
||||||
SetHyperDashState(Math.Abs(velocity), target.X);
|
SetHyperDashState(Math.Abs(velocity), target.X);
|
||||||
@ -216,8 +244,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
|
/// <param name="targetPosition">When this catcher crosses this position, this catcher ends hyper-dashing.</param>
|
||||||
public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
|
public void SetHyperDashState(double modifier = 1, float targetPosition = -1)
|
||||||
{
|
{
|
||||||
const float hyper_dash_transition_length = 180;
|
|
||||||
|
|
||||||
var wasHyperDashing = HyperDashing;
|
var wasHyperDashing = HyperDashing;
|
||||||
|
|
||||||
if (modifier <= 1 || X == targetPosition)
|
if (modifier <= 1 || X == targetPosition)
|
||||||
@ -226,11 +252,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
hyperDashDirection = 0;
|
hyperDashDirection = 0;
|
||||||
|
|
||||||
if (wasHyperDashing)
|
if (wasHyperDashing)
|
||||||
{
|
runHyperDashStateTransition(false);
|
||||||
this.FadeColour(Color4.White, hyper_dash_transition_length, Easing.OutQuint);
|
|
||||||
this.FadeTo(1, hyper_dash_transition_length, Easing.OutQuint);
|
|
||||||
Trail &= Dashing;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -240,20 +262,32 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
if (!wasHyperDashing)
|
if (!wasHyperDashing)
|
||||||
{
|
{
|
||||||
this.FadeColour(Color4.OrangeRed, hyper_dash_transition_length, Easing.OutQuint);
|
trails.DisplayEndGlow();
|
||||||
this.FadeTo(0.2f, hyper_dash_transition_length, Easing.OutQuint);
|
runHyperDashStateTransition(true);
|
||||||
Trail = true;
|
|
||||||
|
|
||||||
var hyperDashEndGlow = createAdditiveSprite();
|
|
||||||
|
|
||||||
hyperDashEndGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
|
|
||||||
hyperDashEndGlow.ScaleTo(hyperDashEndGlow.Scale * 0.95f).ScaleTo(hyperDashEndGlow.Scale * 1.2f, 1200, Easing.In);
|
|
||||||
hyperDashEndGlow.FadeOut(1200);
|
|
||||||
hyperDashEndGlow.Expire(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void runHyperDashStateTransition(bool hyperDashing)
|
||||||
|
{
|
||||||
|
trails.HyperDashTrailsColour = hyperDashColour;
|
||||||
|
trails.EndGlowSpritesColour = hyperDashEndGlowColour;
|
||||||
|
updateTrailVisibility();
|
||||||
|
|
||||||
|
if (hyperDashing)
|
||||||
|
{
|
||||||
|
this.FadeColour(hyperDashColour, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
this.FadeTo(0.2f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeColour(Color4.White, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
this.FadeTo(1f, HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTrailVisibility() => trails.DisplayTrail = Dashing || HyperDashing;
|
||||||
|
|
||||||
public bool OnPressed(CatchAction action)
|
public bool OnPressed(CatchAction action)
|
||||||
{
|
{
|
||||||
switch (action)
|
switch (action)
|
||||||
@ -294,7 +328,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public void UpdatePosition(float position)
|
public void UpdatePosition(float position)
|
||||||
{
|
{
|
||||||
position = Math.Clamp(position, 0, 1);
|
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
||||||
|
|
||||||
if (position == X)
|
if (position == X)
|
||||||
return;
|
return;
|
||||||
@ -342,6 +376,21 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
|
||||||
|
{
|
||||||
|
base.SkinChanged(skin, allowFallback);
|
||||||
|
|
||||||
|
hyperDashColour =
|
||||||
|
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||||
|
DEFAULT_HYPER_DASH_COLOUR;
|
||||||
|
|
||||||
|
hyperDashEndGlowColour =
|
||||||
|
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDashAfterImage)?.Value ??
|
||||||
|
hyperDashColour;
|
||||||
|
|
||||||
|
runHyperDashStateTransition(HyperDashing);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -387,22 +436,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
(currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
|
(currentCatcher.Drawable as IFramedAnimation)?.GotoFrame(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginTrail()
|
|
||||||
{
|
|
||||||
if (!dashing && !HyperDashing)
|
|
||||||
{
|
|
||||||
Trail = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var additive = createAdditiveSprite();
|
|
||||||
|
|
||||||
additive.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
|
|
||||||
additive.Expire(true);
|
|
||||||
|
|
||||||
Scheduler.AddDelayed(beginTrail, HyperDashing ? 25 : 50);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateState(CatcherAnimationState state)
|
private void updateState(CatcherAnimationState state)
|
||||||
{
|
{
|
||||||
if (CurrentState == state)
|
if (CurrentState == state)
|
||||||
@ -412,25 +445,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CatcherTrailSprite createAdditiveSprite()
|
|
||||||
{
|
|
||||||
var tex = (currentCatcher.Drawable as TextureAnimation)?.CurrentFrame ?? ((Sprite)currentCatcher.Drawable).Texture;
|
|
||||||
|
|
||||||
var sprite = new CatcherTrailSprite(tex)
|
|
||||||
{
|
|
||||||
Anchor = Anchor,
|
|
||||||
Scale = Scale,
|
|
||||||
Colour = HyperDashing ? Color4.Red : Color4.White,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
RelativePositionAxes = RelativePositionAxes,
|
|
||||||
Position = Position
|
|
||||||
};
|
|
||||||
|
|
||||||
AdditiveTarget?.Add(sprite);
|
|
||||||
|
|
||||||
return sprite;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action)
|
private void removeFromPlateWithTransform(DrawableHitObject fruit, Action<DrawableHitObject> action)
|
||||||
{
|
{
|
||||||
if (ExplodingFruitTarget != null)
|
if (ExplodingFruitTarget != null)
|
||||||
|
@ -31,17 +31,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatcherArea(BeatmapDifficulty difficulty = null)
|
public CatcherArea(BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||||
Height = CATCHER_SIZE;
|
Child = MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X };
|
||||||
Child = MovableCatcher = new Catcher(difficulty)
|
|
||||||
{
|
|
||||||
AdditiveTarget = this,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static float GetCatcherSize(BeatmapDifficulty difficulty)
|
|
||||||
{
|
|
||||||
return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
|
public void OnResult(DrawableCatchHitObject fruit, JudgementResult result)
|
||||||
|
135
osu.Game.Rulesets.Catch/UI/CatcherTrailDisplay.cs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a component responsible for displaying
|
||||||
|
/// the appropriate catcher trails when requested to.
|
||||||
|
/// </summary>
|
||||||
|
public class CatcherTrailDisplay : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Catcher catcher;
|
||||||
|
|
||||||
|
private readonly Container<CatcherTrailSprite> dashTrails;
|
||||||
|
private readonly Container<CatcherTrailSprite> hyperDashTrails;
|
||||||
|
private readonly Container<CatcherTrailSprite> endGlowSprites;
|
||||||
|
|
||||||
|
private Color4 hyperDashTrailsColour;
|
||||||
|
|
||||||
|
public Color4 HyperDashTrailsColour
|
||||||
|
{
|
||||||
|
get => hyperDashTrailsColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (hyperDashTrailsColour == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hyperDashTrailsColour = value;
|
||||||
|
hyperDashTrails.FadeColour(hyperDashTrailsColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Color4 endGlowSpritesColour;
|
||||||
|
|
||||||
|
public Color4 EndGlowSpritesColour
|
||||||
|
{
|
||||||
|
get => endGlowSpritesColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (endGlowSpritesColour == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
endGlowSpritesColour = value;
|
||||||
|
endGlowSprites.FadeColour(endGlowSpritesColour, Catcher.HYPER_DASH_TRANSITION_DURATION, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool trail;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to start displaying trails following the catcher.
|
||||||
|
/// </summary>
|
||||||
|
public bool DisplayTrail
|
||||||
|
{
|
||||||
|
get => trail;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (trail == value)
|
||||||
|
return;
|
||||||
|
|
||||||
|
trail = value;
|
||||||
|
|
||||||
|
if (trail)
|
||||||
|
displayTrail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CatcherTrailDisplay([NotNull] Catcher catcher)
|
||||||
|
{
|
||||||
|
this.catcher = catcher ?? throw new ArgumentNullException(nameof(catcher));
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
dashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both },
|
||||||
|
hyperDashTrails = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||||
|
endGlowSprites = new Container<CatcherTrailSprite> { RelativeSizeAxes = Axes.Both, Colour = Catcher.DEFAULT_HYPER_DASH_COLOUR },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Displays a single end-glow catcher sprite.
|
||||||
|
/// </summary>
|
||||||
|
public void DisplayEndGlow()
|
||||||
|
{
|
||||||
|
var endGlow = createTrailSprite(endGlowSprites);
|
||||||
|
|
||||||
|
endGlow.MoveToOffset(new Vector2(0, -10), 1200, Easing.In);
|
||||||
|
endGlow.ScaleTo(endGlow.Scale * 0.95f).ScaleTo(endGlow.Scale * 1.2f, 1200, Easing.In);
|
||||||
|
endGlow.FadeOut(1200);
|
||||||
|
endGlow.Expire(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayTrail()
|
||||||
|
{
|
||||||
|
if (!DisplayTrail)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var sprite = createTrailSprite(catcher.HyperDashing ? hyperDashTrails : dashTrails);
|
||||||
|
|
||||||
|
sprite.FadeTo(0.4f).FadeOut(800, Easing.OutQuint);
|
||||||
|
sprite.Expire(true);
|
||||||
|
|
||||||
|
Scheduler.AddDelayed(displayTrail, catcher.HyperDashing ? 25 : 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
private CatcherTrailSprite createTrailSprite(Container<CatcherTrailSprite> target)
|
||||||
|
{
|
||||||
|
var texture = (catcher.CurrentDrawableCatcher as TextureAnimation)?.CurrentFrame ?? ((Sprite)catcher.CurrentDrawableCatcher).Texture;
|
||||||
|
|
||||||
|
var sprite = new CatcherTrailSprite(texture)
|
||||||
|
{
|
||||||
|
Anchor = catcher.Anchor,
|
||||||
|
Scale = catcher.Scale,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
RelativePositionAxes = catcher.RelativePositionAxes,
|
||||||
|
Position = catcher.Position
|
||||||
|
};
|
||||||
|
|
||||||
|
target.Add(sprite);
|
||||||
|
|
||||||
|
return sprite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class ManiaBeatmapSampleConversionTest : BeatmapConversionTest<ConvertMapping<SampleConvertValue>, SampleConvertValue>
|
||||||
|
{
|
||||||
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||||
|
|
||||||
|
[TestCase("convert-samples")]
|
||||||
|
[TestCase("mania-samples")]
|
||||||
|
[TestCase("slider-convert-samples")]
|
||||||
|
public void Test(string name) => base.Test(name);
|
||||||
|
|
||||||
|
protected override IEnumerable<SampleConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
|
{
|
||||||
|
yield return new SampleConvertValue
|
||||||
|
{
|
||||||
|
StartTime = hitObject.StartTime,
|
||||||
|
EndTime = hitObject.GetEndTime(),
|
||||||
|
Column = ((ManiaHitObject)hitObject).Column,
|
||||||
|
Samples = getSampleNames(hitObject.Samples),
|
||||||
|
NodeSamples = getNodeSampleNames((hitObject as HoldNote)?.NodeSamples)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private IList<string> getSampleNames(IList<HitSampleInfo> hitSampleInfo)
|
||||||
|
=> hitSampleInfo.Select(sample => sample.LookupNames.First()).ToList();
|
||||||
|
|
||||||
|
private IList<IList<string>> getNodeSampleNames(List<IList<HitSampleInfo>> hitSampleInfo)
|
||||||
|
=> hitSampleInfo?.Select(getSampleNames)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct SampleConvertValue : IEquatable<SampleConvertValue>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A sane value to account for osu!stable using ints everywhere.
|
||||||
|
/// </summary>
|
||||||
|
private const float conversion_lenience = 2;
|
||||||
|
|
||||||
|
public double StartTime;
|
||||||
|
public double EndTime;
|
||||||
|
public int Column;
|
||||||
|
public IList<string> Samples;
|
||||||
|
public IList<IList<string>> NodeSamples;
|
||||||
|
|
||||||
|
public bool Equals(SampleConvertValue other)
|
||||||
|
=> Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience)
|
||||||
|
&& Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience)
|
||||||
|
&& samplesEqual(Samples, other.Samples)
|
||||||
|
&& nodeSamplesEqual(NodeSamples, other.NodeSamples);
|
||||||
|
|
||||||
|
private static bool samplesEqual(ICollection<string> firstSampleList, ICollection<string> secondSampleList)
|
||||||
|
=> firstSampleList.SequenceEqual(secondSampleList);
|
||||||
|
|
||||||
|
private static bool nodeSamplesEqual(ICollection<IList<string>> firstSampleList, ICollection<IList<string>> secondSampleList)
|
||||||
|
{
|
||||||
|
if (firstSampleList == null && secondSampleList == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// both items can't be null now, so if any single one is, then they're not equal
|
||||||
|
if (firstSampleList == null || secondSampleList == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return firstSampleList.Count == secondSampleList.Count
|
||||||
|
// cannot use .Zip() without the selector function as it doesn't compile in android test project
|
||||||
|
&& firstSampleList.Zip(secondSampleList, (first, second) => (first, second))
|
||||||
|
.All(samples => samples.first.SequenceEqual(samples.second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,20 +7,18 @@ 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.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Rulesets.Mania.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
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.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
{
|
{
|
||||||
[Cached(Type = typeof(IManiaHitObjectComposer))]
|
public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene
|
||||||
public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer
|
|
||||||
{
|
{
|
||||||
private readonly Column column;
|
private readonly Column column;
|
||||||
|
|
||||||
@ -41,16 +39,20 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AccentColour = Color4.OrangeRed,
|
AccentColour = Color4.OrangeRed,
|
||||||
Clock = new FramedClock(new StopwatchClock()), // No scroll
|
Clock = new FramedClock(new StopwatchClock()), // No scroll
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip());
|
protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint)
|
||||||
|
{
|
||||||
|
var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position);
|
||||||
|
var pos = column.ScreenSpacePositionAtTime(time);
|
||||||
|
|
||||||
|
return new SnapResult(pos, time, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
||||||
|
|
||||||
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
|
protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject);
|
||||||
|
|
||||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
public ManiaPlayfield Playfield => null;
|
||||||
|
|
||||||
public int TotalColumns => 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,25 +4,20 @@
|
|||||||
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.Rulesets.Mania.Edit;
|
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
{
|
{
|
||||||
[Cached(Type = typeof(IManiaHitObjectComposer))]
|
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene
|
||||||
public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer
|
|
||||||
{
|
{
|
||||||
[Cached(Type = typeof(IAdjustableClock))]
|
[Cached(Type = typeof(IAdjustableClock))]
|
||||||
private readonly IAdjustableClock clock = new StopwatchClock();
|
private readonly IAdjustableClock clock = new StopwatchClock();
|
||||||
|
|
||||||
private readonly Column column;
|
|
||||||
|
|
||||||
protected ManiaSelectionBlueprintTestScene()
|
protected ManiaSelectionBlueprintTestScene()
|
||||||
{
|
{
|
||||||
Add(column = new Column(0)
|
Add(new Column(0)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -31,8 +26,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Column ColumnAt(Vector2 screenSpacePosition) => column;
|
public ManiaPlayfield Playfield => null;
|
||||||
|
|
||||||
public int TotalColumns => 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,10 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
|||||||
{
|
{
|
||||||
public class TestSceneManiaModPerfect : ModPerfectTestScene
|
public class TestSceneManiaModPerfect : ModPerfectTestScene
|
||||||
{
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
public TestSceneManiaModPerfect()
|
public TestSceneManiaModPerfect()
|
||||||
: base(new ManiaRuleset(), new ManiaModPerfect())
|
: base(new ManiaModPerfect())
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
Mode: 3
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
0,300,4,0,2,100,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
444,320,1000,5,2,0:0:0:0:
|
@ -0,0 +1,10 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[General]
|
||||||
|
Mode: 3
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
0,300,4,0,2,100,1,0
|
||||||
|
|
||||||
|
[HitObjects]
|
||||||
|
444,320,1000,5,1,0:0:0:0:
|
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 55 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 165 B |
After Width: | Height: | Size: 899 B |
@ -0,0 +1,14 @@
|
|||||||
|
[General]
|
||||||
|
Version: 2.5
|
||||||
|
|
||||||
|
[Mania]
|
||||||
|
Keys: 4
|
||||||
|
ColumnLineWidth: 3,1,3,1,1
|
||||||
|
Hit0: mania/hit0
|
||||||
|
Hit50: mania/hit50
|
||||||
|
Hit100: mania/hit100
|
||||||
|
Hit200: mania/hit200
|
||||||
|
Hit300: mania/hit300
|
||||||
|
Hit300g: mania/hit300g
|
||||||
|
StageLeft: mania/stage-left
|
||||||
|
StageRight: mania/stage-right
|
@ -1,15 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Mania.Skinning;
|
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -27,13 +24,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[Cached(Type = typeof(IScrollingInfo))]
|
[Cached(Type = typeof(IScrollingInfo))]
|
||||||
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
|
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
|
||||||
|
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(ManiaRuleset),
|
|
||||||
typeof(ManiaLegacySkinTransformer),
|
|
||||||
typeof(ManiaSettingsSubsection)
|
|
||||||
};
|
|
||||||
|
|
||||||
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
|
protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset();
|
||||||
|
|
||||||
protected ManiaSkinnableTestScene()
|
protected ManiaSkinnableTestScene()
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
@ -15,22 +15,21 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
|
public class TestSceneDrawableJudgement : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(DrawableJudgement),
|
|
||||||
typeof(DrawableManiaJudgement)
|
|
||||||
};
|
|
||||||
|
|
||||||
public TestSceneDrawableJudgement()
|
public TestSceneDrawableJudgement()
|
||||||
{
|
{
|
||||||
|
var hitWindows = new ManiaHitWindows();
|
||||||
|
|
||||||
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
|
foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Skip(1))
|
||||||
{
|
{
|
||||||
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
|
if (hitWindows.IsHitResultAllowed(result))
|
||||||
new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
|
{
|
||||||
{
|
AddStep("Show " + result.GetDescription(), () => SetContents(() =>
|
||||||
Anchor = Anchor.Centre,
|
new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)
|
||||||
Origin = Anchor.Centre,
|
{
|
||||||
}));
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
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.Extensions.IEnumerableExtensions;
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -21,12 +18,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneHitExplosion : ManiaSkinnableTestScene
|
public class TestSceneHitExplosion : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(DrawableNote),
|
|
||||||
typeof(DrawableManiaHitObject),
|
|
||||||
};
|
|
||||||
|
|
||||||
public TestSceneHitExplosion()
|
public TestSceneHitExplosion()
|
||||||
{
|
{
|
||||||
int runcount = 0;
|
int runcount = 0;
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
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.Game.Rulesets.Mania.Skinning;
|
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -15,12 +12,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
public class TestSceneKeyArea : ManiaSkinnableTestScene
|
public class TestSceneKeyArea : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(DefaultKeyArea),
|
|
||||||
typeof(LegacyKeyArea)
|
|
||||||
};
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Skinning;
|
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
@ -14,12 +10,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
public class TestSceneStageBackground : ManiaSkinnableTestScene
|
public class TestSceneStageBackground : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
|
||||||
{
|
|
||||||
typeof(DefaultStageBackground),
|
|
||||||
typeof(LegacyStageBackground),
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -1,23 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Skinning;
|
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
||||||
{
|
{
|
||||||
public class TestSceneStageForeground : ManiaSkinnableTestScene
|
public class TestSceneStageForeground : ManiaSkinnableTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
|
|
||||||
{
|
|
||||||
typeof(LegacyStageForeground),
|
|
||||||
}).ToList();
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
@ -12,7 +12,6 @@ using osu.Game.Beatmaps.ControlPoints;
|
|||||||
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.Mania.UI.Components;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -24,15 +23,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneColumn : ManiaInputTestScene
|
public class TestSceneColumn : ManiaInputTestScene
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
|
||||||
{
|
|
||||||
typeof(Column),
|
|
||||||
typeof(ColumnBackground),
|
|
||||||
typeof(ColumnHitObjectArea),
|
|
||||||
typeof(DefaultKeyArea),
|
|
||||||
typeof(DefaultHitTarget)
|
|
||||||
};
|
|
||||||
|
|
||||||
[Cached(typeof(IReadOnlyList<Mod>))]
|
[Cached(typeof(IReadOnlyList<Mod>))]
|
||||||
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
|
private IReadOnlyList<Mod> mods { get; set; } = Array.Empty<Mod>();
|
||||||
|
|
||||||
|
@ -15,8 +15,9 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
private readonly Bindable<ManiaScrollingDirection> direction = new Bindable<ManiaScrollingDirection>();
|
private readonly Bindable<ManiaScrollingDirection> direction = new Bindable<ManiaScrollingDirection>();
|
||||||
|
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
public TestSceneEditor()
|
public TestSceneEditor()
|
||||||
: base(new ManiaRuleset())
|
|
||||||
{
|
{
|
||||||
AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up);
|
AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up);
|
||||||
AddStep("downwards scroll", () => direction.Value = ManiaScrollingDirection.Down);
|
AddStep("downwards scroll", () => direction.Value = ManiaScrollingDirection.Down);
|
||||||
|
@ -27,7 +27,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
private const double time_after_tail = 5250;
|
private const double time_after_tail = 5250;
|
||||||
|
|
||||||
private List<JudgementResult> judgementResults;
|
private List<JudgementResult> judgementResults;
|
||||||
private bool allJudgedFired;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -----[ ]-----
|
/// -----[ ]-----
|
||||||
@ -283,20 +282,15 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
if (currentPlayer == p) judgementResults.Add(result);
|
if (currentPlayer == p) judgementResults.Add(result);
|
||||||
};
|
};
|
||||||
p.ScoreProcessor.AllJudged += () =>
|
|
||||||
{
|
|
||||||
if (currentPlayer == p) allJudgedFired = true;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
LoadScreen(currentPlayer = p);
|
LoadScreen(currentPlayer = p);
|
||||||
allJudgedFired = false;
|
|
||||||
judgementResults = new List<JudgementResult>();
|
judgementResults = new List<JudgementResult>();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
AddUntilStep("Wait for all judged", () => allJudgedFired);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
124
osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneManiaBeatSnapGrid : EditorClockTestScene
|
||||||
|
{
|
||||||
|
[Cached(typeof(IScrollingInfo))]
|
||||||
|
private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo();
|
||||||
|
|
||||||
|
[Cached(typeof(EditorBeatmap))]
|
||||||
|
private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition()));
|
||||||
|
|
||||||
|
private readonly ManiaBeatSnapGrid beatSnapGrid;
|
||||||
|
|
||||||
|
public TestSceneManiaBeatSnapGrid()
|
||||||
|
{
|
||||||
|
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 });
|
||||||
|
editorBeatmap.ControlPointInfo.Add(10000, new TimingControlPoint { BeatLength = 200 });
|
||||||
|
|
||||||
|
BeatDivisor.Value = 3;
|
||||||
|
|
||||||
|
// Some sane defaults
|
||||||
|
scrollingInfo.Algorithm.Algorithm = ScrollVisualisationMethod.Constant;
|
||||||
|
scrollingInfo.Direction.Value = ScrollingDirection.Up;
|
||||||
|
scrollingInfo.TimeRange.Value = 1000;
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
Playfield = new ManiaPlayfield(new List<StageDefinition>
|
||||||
|
{
|
||||||
|
new StageDefinition { Columns = 4 },
|
||||||
|
new StageDefinition { Columns = 3 }
|
||||||
|
})
|
||||||
|
{
|
||||||
|
Clock = new FramedClock(new StopwatchClock())
|
||||||
|
},
|
||||||
|
new TestHitObjectComposer(Playfield)
|
||||||
|
{
|
||||||
|
Child = beatSnapGrid = new ManiaBeatSnapGrid()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
// We're providing a constant scroll algorithm.
|
||||||
|
float relativePosition = Playfield.Stages[0].HitObjectContainer.ToLocalSpace(e.ScreenSpaceMousePosition).Y / Playfield.Stages[0].HitObjectContainer.DrawHeight;
|
||||||
|
double timeValue = scrollingInfo.TimeRange.Value * relativePosition;
|
||||||
|
|
||||||
|
beatSnapGrid.SelectionTimeRange = (timeValue, timeValue);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ManiaPlayfield Playfield { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class TestHitObjectComposer : HitObjectComposer
|
||||||
|
{
|
||||||
|
public override Playfield Playfield { get; }
|
||||||
|
public override IEnumerable<DrawableHitObject> HitObjects => Enumerable.Empty<DrawableHitObject>();
|
||||||
|
public override bool CursorInPlacementArea => false;
|
||||||
|
|
||||||
|
public TestHitObjectComposer(Playfield playfield)
|
||||||
|
{
|
||||||
|
Playfield = playfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Drawable Child
|
||||||
|
{
|
||||||
|
set => InternalChild = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetBeatSnapDistanceAt(double referenceTime)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float DurationToDistance(double referenceTime, double duration)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double DistanceToDuration(double referenceTime, float distance)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double GetSnappedDurationFromDistance(double referenceTime, float distance)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetSnappedDistanceFromDistance(double referenceTime, float distance)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
218
osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneManiaHitObjectComposer : EditorClockTestScene
|
||||||
|
{
|
||||||
|
private TestComposer composer;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
BeatDivisor.Value = 8;
|
||||||
|
Clock.Seek(0);
|
||||||
|
|
||||||
|
Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both };
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragOffscreenSelectionVerticallyUpScroll()
|
||||||
|
{
|
||||||
|
DrawableHitObject lastObject = null;
|
||||||
|
double originalTime = 0;
|
||||||
|
Vector2 originalPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
setScrollStep(ScrollingDirection.Up);
|
||||||
|
|
||||||
|
AddStep("seek to last object", () =>
|
||||||
|
{
|
||||||
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
|
originalTime = lastObject.HitObject.StartTime;
|
||||||
|
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
||||||
|
|
||||||
|
AddStep("click last object", () =>
|
||||||
|
{
|
||||||
|
originalPosition = lastObject.DrawPosition;
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(lastObject);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse downwards", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 4));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
||||||
|
AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0);
|
||||||
|
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragOffscreenSelectionVerticallyDownScroll()
|
||||||
|
{
|
||||||
|
DrawableHitObject lastObject = null;
|
||||||
|
double originalTime = 0;
|
||||||
|
Vector2 originalPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
setScrollStep(ScrollingDirection.Down);
|
||||||
|
|
||||||
|
AddStep("seek to last object", () =>
|
||||||
|
{
|
||||||
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
|
originalTime = lastObject.HitObject.StartTime;
|
||||||
|
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
||||||
|
|
||||||
|
AddStep("click last object", () =>
|
||||||
|
{
|
||||||
|
originalPosition = lastObject.DrawPosition;
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(lastObject);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse upwards", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 4));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0));
|
||||||
|
AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0);
|
||||||
|
AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragOffscreenSelectionHorizontally()
|
||||||
|
{
|
||||||
|
DrawableHitObject lastObject = null;
|
||||||
|
Vector2 originalPosition = Vector2.Zero;
|
||||||
|
|
||||||
|
setScrollStep(ScrollingDirection.Down);
|
||||||
|
|
||||||
|
AddStep("seek to last object", () =>
|
||||||
|
{
|
||||||
|
lastObject = this.ChildrenOfType<DrawableHitObject>().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last());
|
||||||
|
Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("select all objects", () => composer.EditorBeatmap.SelectedHitObjects.AddRange(composer.EditorBeatmap.HitObjects));
|
||||||
|
|
||||||
|
AddStep("click last object", () =>
|
||||||
|
{
|
||||||
|
originalPosition = lastObject.DrawPosition;
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(lastObject);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse right", () =>
|
||||||
|
{
|
||||||
|
var firstColumn = composer.Composer.Playfield.GetColumn(0);
|
||||||
|
var secondColumn = composer.Composer.Playfield.GetColumn(1);
|
||||||
|
|
||||||
|
InputManager.MoveMouseTo(lastObject, new Vector2(secondColumn.ScreenSpaceDrawQuad.Centre.X - firstColumn.ScreenSpaceDrawQuad.Centre.X + 1, 0));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("hitobjects moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 1));
|
||||||
|
|
||||||
|
// Todo: They'll move vertically by the height of a note since there's no snapping and the selection point is the middle of the note.
|
||||||
|
AddAssert("hitobjects not moved vertically", () => lastObject.DrawPosition.Y - originalPosition.Y <= DefaultNotePiece.NOTE_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragHoldNoteSelectionVertically()
|
||||||
|
{
|
||||||
|
setScrollStep(ScrollingDirection.Down);
|
||||||
|
|
||||||
|
AddStep("setup beatmap", () =>
|
||||||
|
{
|
||||||
|
composer.EditorBeatmap.Clear();
|
||||||
|
composer.EditorBeatmap.Add(new HoldNote
|
||||||
|
{
|
||||||
|
Column = 1,
|
||||||
|
EndTime = 200
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
DrawableHoldNote holdNote = null;
|
||||||
|
|
||||||
|
AddStep("grab hold note", () =>
|
||||||
|
{
|
||||||
|
holdNote = this.ChildrenOfType<DrawableHoldNote>().Single();
|
||||||
|
InputManager.MoveMouseTo(holdNote);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move drag upwards", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(holdNote, new Vector2(0, -100));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("head note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.BottomLeft, holdNote.Head.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
AddAssert("tail note positioned correctly", () => Precision.AlmostEquals(holdNote.ScreenSpaceDrawQuad.TopLeft, holdNote.Tail.ScreenSpaceDrawQuad.BottomLeft));
|
||||||
|
|
||||||
|
AddAssert("head blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(0).DrawPosition == holdNote.Head.DrawPosition);
|
||||||
|
AddAssert("tail blueprint positioned correctly", () => this.ChildrenOfType<HoldNoteNoteSelectionBlueprint>().ElementAt(1).DrawPosition == holdNote.Tail.DrawPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setScrollStep(ScrollingDirection direction)
|
||||||
|
=> AddStep($"set scroll direction = {direction}", () => ((Bindable<ScrollingDirection>)composer.Composer.ScrollingInfo.Direction).Value = direction);
|
||||||
|
|
||||||
|
private class TestComposer : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Cached(typeof(EditorBeatmap))]
|
||||||
|
[Cached(typeof(IBeatSnapProvider))]
|
||||||
|
public readonly EditorBeatmap EditorBeatmap;
|
||||||
|
|
||||||
|
public readonly ManiaHitObjectComposer Composer;
|
||||||
|
|
||||||
|
public TestComposer()
|
||||||
|
{
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
EditorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
|
||||||
|
{
|
||||||
|
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo }
|
||||||
|
},
|
||||||
|
Composer = new ManiaHitObjectComposer(new ManiaRuleset())
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
EditorBeatmap.Add(new Note { StartTime = 125 * i });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.IO.Stores;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneManiaHitObjectSamples : HitObjectSampleTest
|
||||||
|
{
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
protected override IResourceStore<byte[]> Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples)));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that when a normal sample bank is used, the normal hitsound will be looked up.
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestManiaHitObjectNormalSampleBank()
|
||||||
|
{
|
||||||
|
const string expected_sample = "normal-hitnormal2";
|
||||||
|
|
||||||
|
SetupSkins(expected_sample, expected_sample);
|
||||||
|
|
||||||
|
CreateTestWithBeatmap("mania-hitobject-beatmap-normal-sample-bank.osu");
|
||||||
|
|
||||||
|
AssertBeatmapLookup(expected_sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tests that when a custom sample bank is used, layered hitsounds are not played
|
||||||
|
/// (only the sample from the custom bank is looked up).
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestManiaHitObjectCustomSampleBank()
|
||||||
|
{
|
||||||
|
const string expected_sample = "normal-hitwhistle2";
|
||||||
|
const string unwanted_sample = "normal-hitnormal2";
|
||||||
|
|
||||||
|
SetupSkins(expected_sample, unwanted_sample);
|
||||||
|
|
||||||
|
CreateTestWithBeatmap("mania-hitobject-beatmap-custom-sample-bank.osu");
|
||||||
|
|
||||||
|
AssertBeatmapLookup(expected_sample);
|
||||||
|
AssertNoLookup(unwanted_sample);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,11 +5,8 @@ using osu.Game.Tests.Visual;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
{
|
{
|
||||||
public class TestScenePlayer : PlayerTestScene
|
public class TestSceneManiaPlayer : PlayerTestScene
|
||||||
{
|
{
|
||||||
public TestScenePlayer()
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
: base(new ManiaRuleset())
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|