mirror of
https://github.com/osukey/osukey.git
synced 2025-08-07 00:23:59 +09:00
Merge remote-tracking branch 'refs/remotes/ppy/master' into beatmap-mod-selector
This commit is contained in:
8
.github/ISSUE_TEMPLATE/00-mobile-issues.md
vendored
Normal file
8
.github/ISSUE_TEMPLATE/00-mobile-issues.md
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: Mobile Report
|
||||||
|
about: ⚠ Due to current development priorities we are not accepting mobile reports at this time (unless you're willing to fix them yourself!)
|
||||||
|
---
|
||||||
|
|
||||||
|
⚠ **PLEASE READ** ⚠: Due to prioritising finishing the client for desktop first we are not accepting reports related to mobile platforms for the time being, unless you're willing to fix them.
|
||||||
|
If you'd like to report a problem or suggest a feature and then work on it, feel free to open an issue and highlight that you'd like to address it yourself in the issue body; mobile pull requests are also welcome.
|
||||||
|
Otherwise, please check back in the future when the focus of development shifts towards mobile!
|
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: osu!stable issues
|
||||||
|
url: https://github.com/ppy/osu-stable-issues
|
||||||
|
about: For issues regarding osu!stable (not osu!lazer), open them here.
|
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
name: Missing for Live
|
|
||||||
about: Features which are available in osu!stable but not yet in osu!lazer.
|
|
||||||
---
|
|
||||||
**Describe the missing feature:**
|
|
||||||
|
|
||||||
**Proposal designs of the feature:**
|
|
@ -1,6 +1,6 @@
|
|||||||
<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">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="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" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<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=v2.2" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="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" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<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=v2.2" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="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" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<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=v2.2" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Taiko.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Taiko.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="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" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<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=v2.2" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<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=v2.2" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
4
.idea/.idea.osu/.idea/runConfigurations/osu_.xml
generated
4
.idea/.idea.osu/.idea/runConfigurations/osu_.xml
generated
@ -1,6 +1,6 @@
|
|||||||
<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!">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<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=v2.2" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<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!">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<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=v2.2" />
|
<option name="PROJECT_TFM" value=".NETCoreApp,Version=v3.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" enabled="true" />
|
||||||
</method>
|
</method>
|
||||||
|
32
.vscode/launch.json
vendored
32
.vscode/launch.json
vendored
@ -6,13 +6,13 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll"
|
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Debug)",
|
"preLaunchTask": "Build osu! (Debug)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
@ -23,13 +23,13 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll"
|
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Release)",
|
"preLaunchTask": "Build osu! (Release)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
@ -40,13 +40,13 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tests.dll"
|
"${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tests (Debug)",
|
"preLaunchTask": "Build tests (Debug)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
@ -56,13 +56,13 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2/osu.Game.Tests.dll"
|
"${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0/osu.Game.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tests (Release)",
|
"preLaunchTask": "Build tests (Release)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tests/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
@ -73,14 +73,14 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2/osu!.dll",
|
"${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0/osu!.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Debug)",
|
"preLaunchTask": "Build osu! (Debug)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
@ -91,14 +91,14 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2/osu!.dll",
|
"${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0/osu!.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Release)",
|
"preLaunchTask": "Build osu! (Release)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Desktop/bin/Release/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
@ -109,14 +109,14 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll",
|
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tournament tests (Debug)",
|
"preLaunchTask": "Build tournament tests (Debug)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
@ -127,14 +127,14 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2/osu.Game.Tournament.Tests.dll",
|
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0/osu.Game.Tournament.Tests.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tournament tests (Release)",
|
"preLaunchTask": "Build tournament tests (Release)",
|
||||||
"linux": {
|
"linux": {
|
||||||
"env": {
|
"env": {
|
||||||
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp2.2:${env:LD_LIBRARY_PATH}"
|
"LD_LIBRARY_PATH": "${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/netcoreapp3.0:${env:LD_LIBRARY_PATH}"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"console": "internalConsole"
|
"console": "internalConsole"
|
||||||
|
2
.vscode/tasks.json
vendored
2
.vscode/tasks.json
vendored
@ -95,7 +95,7 @@
|
|||||||
"problemMatcher": "$msCompile"
|
"problemMatcher": "$msCompile"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "Restore (netcoreapp2.2)",
|
"label": "Restore (netcoreapp3.0)",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "dotnet",
|
"command": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
|
34
Gemfile.lock
34
Gemfile.lock
@ -1,11 +1,11 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.0)
|
CFPropertyList (3.0.1)
|
||||||
addressable (2.6.0)
|
addressable (2.7.0)
|
||||||
public_suffix (>= 2.0.2, < 4.0)
|
public_suffix (>= 2.0.2, < 5.0)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
babosa (1.0.2)
|
babosa (1.0.3)
|
||||||
claide (1.0.3)
|
claide (1.0.3)
|
||||||
colored (1.2)
|
colored (1.2)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
@ -18,7 +18,7 @@ GEM
|
|||||||
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.66.0)
|
excon (0.67.0)
|
||||||
faraday (0.15.4)
|
faraday (0.15.4)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
faraday-cookie_jar (0.0.6)
|
faraday-cookie_jar (0.0.6)
|
||||||
@ -26,8 +26,8 @@ GEM
|
|||||||
http-cookie (~> 1.0.0)
|
http-cookie (~> 1.0.0)
|
||||||
faraday_middleware (0.13.1)
|
faraday_middleware (0.13.1)
|
||||||
faraday (>= 0.7.4, < 1.0)
|
faraday (>= 0.7.4, < 1.0)
|
||||||
fastimage (2.1.5)
|
fastimage (2.1.7)
|
||||||
fastlane (2.129.0)
|
fastlane (2.133.0)
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
addressable (>= 2.3, < 3.0.0)
|
addressable (>= 2.3, < 3.0.0)
|
||||||
babosa (>= 1.0.2, < 2.0.0)
|
babosa (>= 1.0.2, < 2.0.0)
|
||||||
@ -37,9 +37,9 @@ 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.45.0, < 1.0.0)
|
excon (>= 0.45.0, < 1.0.0)
|
||||||
faraday (~> 0.9)
|
faraday (< 0.16.0)
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
faraday_middleware (~> 0.9)
|
faraday_middleware (< 0.16.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.21.2, < 0.24.0)
|
google-api-client (>= 0.21.2, < 0.24.0)
|
||||||
@ -52,7 +52,7 @@ GEM
|
|||||||
multipart-post (~> 2.0.0)
|
multipart-post (~> 2.0.0)
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
public_suffix (~> 2.0.0)
|
public_suffix (~> 2.0.0)
|
||||||
rubyzip (>= 1.2.2, < 2.0.0)
|
rubyzip (>= 1.3.0, < 2.0.0)
|
||||||
security (= 0.1.3)
|
security (= 0.1.3)
|
||||||
simctl (~> 1.6.3)
|
simctl (~> 1.6.3)
|
||||||
slack-notifier (>= 2.0.0, < 3.0.0)
|
slack-notifier (>= 2.0.0, < 3.0.0)
|
||||||
@ -77,9 +77,9 @@ GEM
|
|||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.0)
|
retriable (>= 2.0, < 4.0)
|
||||||
signet (~> 0.9)
|
signet (~> 0.9)
|
||||||
google-cloud-core (1.3.0)
|
google-cloud-core (1.3.1)
|
||||||
google-cloud-env (~> 1.0)
|
google-cloud-env (~> 1.0)
|
||||||
google-cloud-env (1.2.0)
|
google-cloud-env (1.2.1)
|
||||||
faraday (~> 0.11)
|
faraday (~> 0.11)
|
||||||
google-cloud-storage (1.16.0)
|
google-cloud-storage (1.16.0)
|
||||||
digest-crc (~> 0.4)
|
digest-crc (~> 0.4)
|
||||||
@ -100,9 +100,9 @@ GEM
|
|||||||
json (2.2.0)
|
json (2.2.0)
|
||||||
jwt (2.1.0)
|
jwt (2.1.0)
|
||||||
memoist (0.16.0)
|
memoist (0.16.0)
|
||||||
mime-types (3.2.2)
|
mime-types (3.3)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2019.0331)
|
mime-types-data (3.2019.1009)
|
||||||
mini_magick (4.9.5)
|
mini_magick (4.9.5)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.4.0)
|
||||||
multi_json (1.13.1)
|
multi_json (1.13.1)
|
||||||
@ -121,14 +121,14 @@ GEM
|
|||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
rubyzip (1.2.3)
|
rubyzip (1.3.0)
|
||||||
security (0.1.3)
|
security (0.1.3)
|
||||||
signet (0.11.0)
|
signet (0.12.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (~> 0.9)
|
faraday (~> 0.9)
|
||||||
jwt (>= 1.5, < 3.0)
|
jwt (>= 1.5, < 3.0)
|
||||||
multi_json (~> 1.10)
|
multi_json (~> 1.10)
|
||||||
simctl (1.6.5)
|
simctl (1.6.6)
|
||||||
CFPropertyList
|
CFPropertyList
|
||||||
naturally
|
naturally
|
||||||
slack-notifier (2.3.2)
|
slack-notifier (2.3.2)
|
||||||
|
10
README.md
10
README.md
@ -18,7 +18,7 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
|
- A desktop platform with the [.NET Core SDK 3.0](https://www.microsoft.com/net/learn/get-started) or higher installed.
|
||||||
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
|
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
|
||||||
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
|
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
|
||||||
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
|
||||||
@ -31,12 +31,10 @@ If you are not interested in developing the game, you can still consume our [bin
|
|||||||
|
|
||||||
**Latest build:**
|
**Latest build:**
|
||||||
|
|
||||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) |
|
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [iOS(iOS 10+)](https://testflight.apple.com/join/2tLcjWlF) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||||
| ------------- | ------------- |
|
| ------------- | ------------- | ------------- | ------------- |
|
||||||
|
|
||||||
- **Linux** users are recommended to self-compile until we have official deployment in place.
|
- **Linux** users are recommended to self-compile until we have official deployment in place.
|
||||||
- **iOS** users can join the [TestFlight beta program](https://testflight.apple.com/join/2tLcjWlF) (note that due to high demand this is regularly full).
|
|
||||||
- **Android** users can self-compile, and expect a public beta soon.
|
|
||||||
|
|
||||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||||
|
|
||||||
@ -80,7 +78,7 @@ On Linux, the environment variable `LD_LIBRARY_PATH` must point to the build dir
|
|||||||
For example, you can run osu! with the following command:
|
For example, you can run osu! with the following command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp2.2" dotnet run --project osu.Desktop
|
LD_LIBRARY_PATH="$(pwd)/osu.Desktop/bin/Debug/netcoreapp3.0" dotnet run --project osu.Desktop
|
||||||
```
|
```
|
||||||
|
|
||||||
### Testing with resource/framework modifications
|
### Testing with resource/framework modifications
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
version: '{branch}-{build}'
|
version: '{branch}-{build}'
|
||||||
image: Previous Visual Studio 2017
|
image: Visual Studio 2019 Preview
|
||||||
test: off
|
test: off
|
||||||
build_script:
|
build_script:
|
||||||
- cmd: PowerShell -Version 2.0 .\build.ps1
|
- cmd: PowerShell -Version 2.0 .\build.ps1
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
clone_depth: 1
|
clone_depth: 1
|
||||||
version: '{build}'
|
version: '{build}'
|
||||||
image: Previous Visual Studio 2017
|
image: Visual Studio 2019 Preview
|
||||||
test: off
|
test: off
|
||||||
skip_non_tags: true
|
skip_non_tags: true
|
||||||
build_script:
|
build_script:
|
||||||
|
65
build.ps1
Normal file → Executable file
65
build.ps1
Normal file → Executable file
@ -1,39 +1,5 @@
|
|||||||
##########################################################################
|
|
||||||
# This is a customized Cake bootstrapper script for PowerShell.
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
<#
|
|
||||||
|
|
||||||
.SYNOPSIS
|
|
||||||
This is a Powershell script to bootstrap a Cake build.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This Powershell script restores NuGet tools (including Cake)
|
|
||||||
and execute your Cake build script with the parameters you provide.
|
|
||||||
|
|
||||||
.PARAMETER Script
|
|
||||||
The build script to execute.
|
|
||||||
.PARAMETER Target
|
|
||||||
The build script target to run.
|
|
||||||
.PARAMETER Configuration
|
|
||||||
The build configuration to use.
|
|
||||||
.PARAMETER Verbosity
|
|
||||||
Specifies the amount of information to be displayed.
|
|
||||||
.PARAMETER ShowDescription
|
|
||||||
Shows description about tasks.
|
|
||||||
.PARAMETER DryRun
|
|
||||||
Performs a dry run.
|
|
||||||
.PARAMETER ScriptArgs
|
|
||||||
Remaining arguments are added here.
|
|
||||||
|
|
||||||
.LINK
|
|
||||||
https://cakebuild.net
|
|
||||||
|
|
||||||
#>
|
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
Param(
|
Param(
|
||||||
[string]$Script = "build.cake",
|
|
||||||
[string]$Target,
|
[string]$Target,
|
||||||
[string]$Configuration,
|
[string]$Configuration,
|
||||||
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
|
[ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")]
|
||||||
@ -45,27 +11,8 @@ Param(
|
|||||||
[string[]]$ScriptArgs
|
[string[]]$ScriptArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
Write-Host "Preparing to run build script..."
|
|
||||||
|
|
||||||
# Determine the script root for resolving other paths.
|
|
||||||
if(!$PSScriptRoot) {
|
|
||||||
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
|
||||||
}
|
|
||||||
|
|
||||||
# Resolve the paths for resources used for debugging.
|
|
||||||
$BUILD_DIR = Join-Path $PSScriptRoot "build"
|
|
||||||
$TOOLS_DIR = Join-Path $BUILD_DIR "tools"
|
|
||||||
$CAKE_CSPROJ = Join-Path $BUILD_DIR "cakebuild.csproj"
|
|
||||||
|
|
||||||
# Install the required tools locally.
|
|
||||||
Write-Host "Restoring cake tools..."
|
|
||||||
Invoke-Expression "dotnet restore `"$CAKE_CSPROJ`" --packages `"$TOOLS_DIR`"" | Out-Null
|
|
||||||
|
|
||||||
# Find the Cake executable
|
|
||||||
$CAKE_EXECUTABLE = (Get-ChildItem -Path "$TOOLS_DIR/cake.coreclr/" -Filter Cake.dll -Recurse).FullName
|
|
||||||
|
|
||||||
# Build Cake arguments
|
# Build Cake arguments
|
||||||
$cakeArguments = @("$Script");
|
$cakeArguments = "";
|
||||||
if ($Target) { $cakeArguments += "-target=$Target" }
|
if ($Target) { $cakeArguments += "-target=$Target" }
|
||||||
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
|
if ($Configuration) { $cakeArguments += "-configuration=$Configuration" }
|
||||||
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
|
if ($Verbosity) { $cakeArguments += "-verbosity=$Verbosity" }
|
||||||
@ -74,9 +21,7 @@ if ($DryRun) { $cakeArguments += "-dryrun" }
|
|||||||
if ($Experimental) { $cakeArguments += "-experimental" }
|
if ($Experimental) { $cakeArguments += "-experimental" }
|
||||||
$cakeArguments += $ScriptArgs
|
$cakeArguments += $ScriptArgs
|
||||||
|
|
||||||
# Start Cake
|
dotnet tool install Cake.Tool --global --version 0.35.0
|
||||||
Write-Host "Running build script..."
|
dotnet cake ./build/build.cake --bootstrap
|
||||||
Push-Location -Path $BUILD_DIR
|
dotnet cake ./build/build.cake $cakeArguments
|
||||||
Invoke-Expression "dotnet `"$CAKE_EXECUTABLE`" $cakeArguments"
|
exit $LASTEXITCODE
|
||||||
Pop-Location
|
|
||||||
exit $LASTEXITCODE
|
|
29
build.sh
29
build.sh
@ -1,18 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
echo "Installing Cake.Tool..."
|
||||||
|
dotnet tool install Cake.Tool --global --version 0.35.0
|
||||||
##########################################################################
|
|
||||||
# This is a customized Cake bootstrapper script for Shell.
|
|
||||||
##########################################################################
|
|
||||||
|
|
||||||
echo "Preparing to run build script..."
|
|
||||||
|
|
||||||
cd build
|
|
||||||
SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
|
||||||
TOOLS_DIR=$SCRIPT_DIR/tools
|
|
||||||
CAKE_BINARY_PATH=$TOOLS_DIR/"cake.coreclr"
|
|
||||||
|
|
||||||
SCRIPT="build.cake"
|
|
||||||
CAKE_CSPROJ=$SCRIPT_DIR/"cakebuild.csproj"
|
|
||||||
|
|
||||||
# Parse arguments.
|
# Parse arguments.
|
||||||
CAKE_ARGUMENTS=()
|
CAKE_ARGUMENTS=()
|
||||||
@ -25,14 +12,6 @@ for i in "$@"; do
|
|||||||
shift
|
shift
|
||||||
done
|
done
|
||||||
|
|
||||||
# Install the required tools locally.
|
|
||||||
echo "Restoring cake tools..."
|
|
||||||
dotnet restore $CAKE_CSPROJ --packages $TOOLS_DIR > /dev/null 2>&1
|
|
||||||
|
|
||||||
# Search for the CakeBuild binary.
|
|
||||||
CAKE_BINARY=$(find $CAKE_BINARY_PATH -name "Cake.dll")
|
|
||||||
|
|
||||||
# Start Cake
|
|
||||||
echo "Running build script..."
|
echo "Running build script..."
|
||||||
|
dotnet cake ./build/build.cake --bootstrap
|
||||||
dotnet "$CAKE_BINARY" $SCRIPT "${CAKE_ARGUMENTS[@]}"
|
dotnet cake ./build/build.cake "${CAKE_ARGUMENTS[@]}"
|
@ -1,5 +1,5 @@
|
|||||||
#addin "nuget:?package=CodeFileSanity&version=0.0.21"
|
#addin "nuget:?package=CodeFileSanity&version=0.0.33"
|
||||||
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.1.1"
|
#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.2.1"
|
||||||
#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();
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>Exe</OutputType>
|
|
||||||
<PackAsTool>true</PackAsTool>
|
|
||||||
<TargetFrameworks>netcoreapp2.0</TargetFrameworks>
|
|
||||||
</PropertyGroup>
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Cake" Version="0.34.1" />
|
|
||||||
<PackageReference Include="Cake.CoreCLR" Version="0.34.1" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
|
@ -1,5 +1,83 @@
|
|||||||
update_fastlane
|
update_fastlane
|
||||||
|
|
||||||
|
platform :android do
|
||||||
|
desc 'Deploy to play store'
|
||||||
|
lane :beta do |options|
|
||||||
|
|
||||||
|
update_version(
|
||||||
|
version: options[:version],
|
||||||
|
build: options[:build],
|
||||||
|
)
|
||||||
|
|
||||||
|
build(options)
|
||||||
|
|
||||||
|
supply(
|
||||||
|
apk: './osu.Android/bin/Release/sh.ppy.osulazer-Signed.apk',
|
||||||
|
package_name: 'sh.ppy.osulazer',
|
||||||
|
track: 'alpha', # upload to alpha, we can promote it later
|
||||||
|
json_key: options[:json_key],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Deploy to github release'
|
||||||
|
lane :build_github do |options|
|
||||||
|
|
||||||
|
update_version(
|
||||||
|
version: options[:version],
|
||||||
|
build: options[:build],
|
||||||
|
)
|
||||||
|
|
||||||
|
build(options)
|
||||||
|
|
||||||
|
client = HTTPClient.new
|
||||||
|
changelog = client.get_content 'https://gist.githubusercontent.com/peppy/aaa2ec1a323554b619671cac6dbbb776/raw'
|
||||||
|
changelog.gsub!('$BUILD_ID', options[:build])
|
||||||
|
|
||||||
|
set_github_release(
|
||||||
|
repository_name: "ppy/osu",
|
||||||
|
api_token: ENV["GITHUB_TOKEN"],
|
||||||
|
name: options[:build],
|
||||||
|
tag_name: options[:build],
|
||||||
|
is_draft: true,
|
||||||
|
description: changelog,
|
||||||
|
commitish: "master",
|
||||||
|
upload_assets: ["osu.Android/bin/Release/sh.ppy.osulazer.apk"]
|
||||||
|
)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
desc 'Compile the project'
|
||||||
|
lane :build do |options|
|
||||||
|
nuget_restore(
|
||||||
|
project_path: 'osu.Android.sln'
|
||||||
|
)
|
||||||
|
|
||||||
|
souyuz(
|
||||||
|
build_configuration: 'Release',
|
||||||
|
solution_path: 'osu.Android.sln',
|
||||||
|
platform: "android",
|
||||||
|
output_path: "osu.Android/bin/Release/",
|
||||||
|
keystore_path: options[:keystore_path],
|
||||||
|
keystore_alias: options[:keystore_alias],
|
||||||
|
keystore_password: ENV["KEYSTORE_PASSWORD"]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
lane :update_version do |options|
|
||||||
|
|
||||||
|
split = options[:build].split('.')
|
||||||
|
split[1] = split[1].to_s.rjust(4, '0')
|
||||||
|
android_build = split.join('')
|
||||||
|
|
||||||
|
app_version(
|
||||||
|
solution_path: 'osu.Android.sln',
|
||||||
|
version: options[:version],
|
||||||
|
build: android_build,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
platform :ios do
|
platform :ios do
|
||||||
desc 'Deploy to testflight'
|
desc 'Deploy to testflight'
|
||||||
lane :beta do |options|
|
lane :beta do |options|
|
||||||
|
@ -15,6 +15,30 @@ Install _fastlane_ using
|
|||||||
or alternatively using `brew cask install fastlane`
|
or alternatively using `brew cask install fastlane`
|
||||||
|
|
||||||
# Available Actions
|
# Available Actions
|
||||||
|
## Android
|
||||||
|
### android beta
|
||||||
|
```
|
||||||
|
fastlane android beta
|
||||||
|
```
|
||||||
|
Deploy to play store
|
||||||
|
### android build_github
|
||||||
|
```
|
||||||
|
fastlane android build_github
|
||||||
|
```
|
||||||
|
Deploy to github release
|
||||||
|
### android build
|
||||||
|
```
|
||||||
|
fastlane android build
|
||||||
|
```
|
||||||
|
Compile the project
|
||||||
|
### android update_version
|
||||||
|
```
|
||||||
|
fastlane android update_version
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
## iOS
|
## iOS
|
||||||
### ios beta
|
### ios beta
|
||||||
```
|
```
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.913.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.921.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1108.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -16,6 +16,11 @@ namespace osu.Android
|
|||||||
|
|
||||||
protected override void OnCreate(Bundle savedInstanceState)
|
protected override void OnCreate(Bundle savedInstanceState)
|
||||||
{
|
{
|
||||||
|
// The default current directory on android is '/'.
|
||||||
|
// On some devices '/' maps to the app data directory. On others it maps to the root of the internal storage.
|
||||||
|
// In order to have a consistent current directory on all devices the full path of the app data directory is set as the current directory.
|
||||||
|
System.Environment.CurrentDirectory = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
||||||
|
|
||||||
base.OnCreate(savedInstanceState);
|
base.OnCreate(savedInstanceState);
|
||||||
|
|
||||||
Window.AddFlags(WindowManagerFlags.Fullscreen);
|
Window.AddFlags(WindowManagerFlags.Fullscreen);
|
||||||
|
@ -4,11 +4,37 @@
|
|||||||
using System;
|
using System;
|
||||||
using Android.App;
|
using Android.App;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
|
using osu.Game.Updater;
|
||||||
|
|
||||||
namespace osu.Android
|
namespace osu.Android
|
||||||
{
|
{
|
||||||
public class OsuGameAndroid : OsuGame
|
public class OsuGameAndroid : OsuGame
|
||||||
{
|
{
|
||||||
public override Version AssemblyVersion => new Version(Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0).VersionName);
|
public override Version AssemblyVersion
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var packageInfo = Application.Context.ApplicationContext.PackageManager.GetPackageInfo(Application.Context.ApplicationContext.PackageName, 0);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string versionName = packageInfo.VersionCode.ToString();
|
||||||
|
// undo play store version garbling
|
||||||
|
return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1)));
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Version(packageInfo.VersionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Add(new SimpleUpdateManager());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,6 +17,7 @@ using osu.Framework.Logging;
|
|||||||
using osu.Framework.Platform.Windows;
|
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;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
|
@ -8,29 +8,18 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Overlays.Notifications;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Desktop.Overlays
|
namespace osu.Desktop.Overlays
|
||||||
{
|
{
|
||||||
public class VersionManager : OverlayContainer
|
public class VersionManager : VisibilityContainer
|
||||||
{
|
{
|
||||||
private OsuConfigManager config;
|
|
||||||
private OsuGameBase game;
|
|
||||||
private NotificationOverlay notificationOverlay;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config)
|
private void load(OsuColour colours, TextureStore textures, OsuGameBase game)
|
||||||
{
|
{
|
||||||
notificationOverlay = notification;
|
|
||||||
this.config = config;
|
|
||||||
this.game = game;
|
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.BottomCentre;
|
Origin = Anchor.BottomCentre;
|
||||||
@ -85,48 +74,6 @@ namespace osu.Desktop.Overlays
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
var version = game.Version;
|
|
||||||
var lastVersion = config.Get<string>(OsuSetting.Version);
|
|
||||||
|
|
||||||
if (game.IsDeployedBuild && version != lastVersion)
|
|
||||||
{
|
|
||||||
config.Set(OsuSetting.Version, version);
|
|
||||||
|
|
||||||
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
|
|
||||||
if (!string.IsNullOrEmpty(lastVersion))
|
|
||||||
notificationOverlay.Post(new UpdateCompleteNotification(version));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class UpdateCompleteNotification : SimpleNotification
|
|
||||||
{
|
|
||||||
private readonly string version;
|
|
||||||
|
|
||||||
public UpdateCompleteNotification(string version)
|
|
||||||
{
|
|
||||||
this.version = version;
|
|
||||||
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay)
|
|
||||||
{
|
|
||||||
Icon = FontAwesome.Solid.CheckSquare;
|
|
||||||
IconBackgound.Colour = colours.BlueDark;
|
|
||||||
|
|
||||||
Activated = delegate
|
|
||||||
{
|
|
||||||
notificationOverlay.Hide();
|
|
||||||
changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
{
|
{
|
||||||
this.FadeIn(1400, Easing.OutQuint);
|
this.FadeIn(1400, Easing.OutQuint);
|
||||||
|
@ -20,7 +20,7 @@ using LogLevel = Splat.LogLevel;
|
|||||||
|
|
||||||
namespace osu.Desktop.Updater
|
namespace osu.Desktop.Updater
|
||||||
{
|
{
|
||||||
public class SquirrelUpdateManager : Component
|
public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager
|
||||||
{
|
{
|
||||||
private UpdateManager updateManager;
|
private UpdateManager updateManager;
|
||||||
private NotificationOverlay notificationOverlay;
|
private NotificationOverlay notificationOverlay;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.Game.props" />
|
<Import Project="..\osu.Game.props" />
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
@ -23,10 +23,10 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
<PackageReference Include="Microsoft.Win32.Registry" Version="4.5.0" />
|
<PackageReference Include="Microsoft.Win32.Registry" Version="4.6.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="System.IO.Packaging" Version="4.5.0" />
|
<PackageReference Include="System.IO.Packaging" Version="4.6.0" />
|
||||||
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.4" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" />
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll"
|
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Catch.Tests.dll"
|
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
var controlPointInfo = new ControlPointInfo();
|
var controlPointInfo = new ControlPointInfo();
|
||||||
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
|
controlPointInfo.Add(0, new TimingControlPoint());
|
||||||
|
|
||||||
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap
|
||||||
{
|
{
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
<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.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||||
<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.15.1" />
|
||||||
<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">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Project References">
|
<ItemGroup Label="Project References">
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj" />
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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 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;
|
||||||
@ -37,9 +38,21 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
public int ComboOffset { get; set; }
|
public int ComboOffset { get; set; }
|
||||||
|
|
||||||
public int IndexInCurrentCombo { get; set; }
|
public Bindable<int> IndexInCurrentComboBindable { get; } = new Bindable<int>();
|
||||||
|
|
||||||
public int ComboIndex { get; set; }
|
public int IndexInCurrentCombo
|
||||||
|
{
|
||||||
|
get => IndexInCurrentComboBindable.Value;
|
||||||
|
set => IndexInCurrentComboBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Bindable<int> ComboIndexBindable { get; } = new Bindable<int>();
|
||||||
|
|
||||||
|
public int ComboIndex
|
||||||
|
{
|
||||||
|
get => ComboIndexBindable.Value;
|
||||||
|
set => ComboIndexBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Difference between the distance to the next object
|
/// Difference between the distance to the next object
|
||||||
@ -48,10 +61,16 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public float DistanceToHyperDash { get; set; }
|
public float DistanceToHyperDash { get; set; }
|
||||||
|
|
||||||
|
public Bindable<bool> LastInComboBindable { get; } = new Bindable<bool>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The next fruit starts a new combo. Used for explodey.
|
/// The next fruit starts a new combo. Used for explodey.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual bool LastInCombo { get; set; }
|
public virtual bool LastInCombo
|
||||||
|
{
|
||||||
|
get => LastInComboBindable.Value;
|
||||||
|
set => LastInComboBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public float Scale { get; set; } = 1;
|
public float Scale { get; set; } = 1;
|
||||||
|
|
||||||
@ -74,7 +93,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
|
Scale = 1.0f - 0.7f * (difficulty.CircleSize - 5) / 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => null;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FruitVisualRepresentation
|
public enum FruitVisualRepresentation
|
||||||
|
@ -2,35 +2,50 @@
|
|||||||
// 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.Linq;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||||
{
|
{
|
||||||
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
|
public class DrawableBananaShower : DrawableCatchHitObject<BananaShower>
|
||||||
{
|
{
|
||||||
|
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
||||||
private readonly Container bananaContainer;
|
private readonly Container bananaContainer;
|
||||||
|
|
||||||
public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
|
public DrawableBananaShower(BananaShower s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
|
||||||
: base(s)
|
: base(s)
|
||||||
{
|
{
|
||||||
|
this.createDrawableRepresentation = createDrawableRepresentation;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Origin = Anchor.BottomLeft;
|
Origin = Anchor.BottomLeft;
|
||||||
X = 0;
|
X = 0;
|
||||||
|
|
||||||
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
|
AddInternal(bananaContainer = new Container { RelativeSizeAxes = Axes.Both });
|
||||||
|
|
||||||
foreach (var b in s.NestedHitObjects.Cast<Banana>())
|
|
||||||
AddNested(createDrawableRepresentation?.Invoke(b));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddNested(DrawableHitObject h)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
|
base.AddNestedHitObject(hitObject);
|
||||||
bananaContainer.Add(h);
|
bananaContainer.Add(hitObject);
|
||||||
base.AddNested(h);
|
}
|
||||||
|
|
||||||
|
protected override void ClearNestedHitObjects()
|
||||||
|
{
|
||||||
|
base.ClearNestedHitObjects();
|
||||||
|
bananaContainer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case Banana banana:
|
||||||
|
return createDrawableRepresentation?.Invoke(banana)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateNestedHitObject(hitObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,50 @@
|
|||||||
// 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.Linq;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
namespace osu.Game.Rulesets.Catch.Objects.Drawable
|
||||||
{
|
{
|
||||||
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
|
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
|
||||||
{
|
{
|
||||||
|
private readonly Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation;
|
||||||
private readonly Container dropletContainer;
|
private readonly Container dropletContainer;
|
||||||
|
|
||||||
public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
|
public DrawableJuiceStream(JuiceStream s, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation = null)
|
||||||
: base(s)
|
: base(s)
|
||||||
{
|
{
|
||||||
|
this.createDrawableRepresentation = createDrawableRepresentation;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Origin = Anchor.BottomLeft;
|
Origin = Anchor.BottomLeft;
|
||||||
X = 0;
|
X = 0;
|
||||||
|
|
||||||
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
|
AddInternal(dropletContainer = new Container { RelativeSizeAxes = Axes.Both, });
|
||||||
|
|
||||||
foreach (var o in s.NestedHitObjects.Cast<CatchHitObject>())
|
|
||||||
AddNested(createDrawableRepresentation?.Invoke(o));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void AddNested(DrawableHitObject h)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
var catchObject = (DrawableCatchHitObject)h;
|
base.AddNestedHitObject(hitObject);
|
||||||
|
dropletContainer.Add(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
|
protected override void ClearNestedHitObjects()
|
||||||
|
{
|
||||||
|
base.ClearNestedHitObjects();
|
||||||
|
dropletContainer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
dropletContainer.Add(h);
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
base.AddNested(h);
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case CatchHitObject catchObject:
|
||||||
|
return createDrawableRepresentation?.Invoke(catchObject)?.With(o => ((DrawableCatchHitObject)o).CheckPosition = p => CheckPosition?.Invoke(p) ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateNestedHitObject(hitObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll"
|
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Mania.Tests.dll"
|
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Mania.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -27,8 +27,13 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
[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>();
|
||||||
|
|
||||||
|
[Cached(typeof(IScrollingInfo))]
|
||||||
|
private IScrollingInfo scrollingInfo;
|
||||||
|
|
||||||
protected ManiaPlacementBlueprintTestScene()
|
protected ManiaPlacementBlueprintTestScene()
|
||||||
{
|
{
|
||||||
|
scrollingInfo = ((ScrollingTestContainer)HitObjectContainer).ScrollingInfo;
|
||||||
|
|
||||||
Add(column = new Column(0)
|
Add(column = new Column(0)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -36,15 +41,8 @@ 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
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip());
|
||||||
{
|
|
||||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
|
||||||
|
|
||||||
dependencies.CacheAs(((ScrollingTestContainer)HitObjectContainer).ScrollingInfo);
|
|
||||||
|
|
||||||
return dependencies;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };
|
||||||
|
@ -6,7 +6,6 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
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;
|
||||||
@ -39,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AccentColour = { Value = OsuColour.Gray(0.3f) }
|
AccentColour = { Value = OsuColour.Gray(0.3f) }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
AddBlueprint(new HoldNoteSelectionBlueprint(drawableObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -51,7 +52,5 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
nested.Y = (float)(-finalPosition * content.DrawHeight);
|
nested.Y = (float)(-finalPosition * content.DrawHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
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;
|
||||||
@ -17,8 +16,6 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
public class TestSceneNoteSelectionBlueprint : ManiaSelectionBlueprintTestScene
|
||||||
{
|
{
|
||||||
private readonly DrawableNote drawableObject;
|
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content ?? base.Content;
|
protected override Container<Drawable> Content => content ?? base.Content;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
@ -27,6 +24,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
var note = new Note { Column = 0 };
|
var note = new Note { Column = 0 };
|
||||||
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
DrawableNote drawableObject;
|
||||||
|
|
||||||
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
|
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
@ -34,8 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
Size = new Vector2(50, 20),
|
Size = new Vector2(50, 20),
|
||||||
Child = drawableObject = new DrawableNote(note)
|
Child = drawableObject = new DrawableNote(note)
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject);
|
AddBlueprint(new NoteSelectionBlueprint(drawableObject));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@ 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.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
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;
|
||||||
@ -67,6 +66,8 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre));
|
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.TopCentre));
|
||||||
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre));
|
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.BottomCentre));
|
||||||
|
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.TopCentre));
|
||||||
|
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.BottomCentre));
|
||||||
|
|
||||||
AddStep("flip direction", () =>
|
AddStep("flip direction", () =>
|
||||||
{
|
{
|
||||||
@ -76,10 +77,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre));
|
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[0], Anchor.BottomCentre));
|
||||||
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre));
|
AddAssert("check note anchors", () => notesInStageAreAnchored(stages[1], Anchor.TopCentre));
|
||||||
|
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[0], Anchor.BottomCentre));
|
||||||
|
AddAssert("check bar anchors", () => barsInStageAreAnchored(stages[1], Anchor.TopCentre));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
|
private bool notesInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.Columns.SelectMany(c => c.AllHitObjects).All(o => o.Anchor == anchor);
|
||||||
|
|
||||||
|
private bool barsInStageAreAnchored(ManiaStage stage, Anchor anchor) => stage.AllHitObjects.Where(obj => obj is DrawableBarLine).All(o => o.Anchor == anchor);
|
||||||
|
|
||||||
private void createNote()
|
private void createNote()
|
||||||
{
|
{
|
||||||
foreach (var stage in stages)
|
foreach (var stage in stages)
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
<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.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||||
<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.15.1" />
|
||||||
<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">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Project References">
|
<ItemGroup Label="Project References">
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj" />
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public class HoldNoteNoteSelectionBlueprint : ManiaSelectionBlueprint
|
||||||
|
{
|
||||||
|
protected new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
||||||
|
|
||||||
|
private readonly HoldNotePosition position;
|
||||||
|
|
||||||
|
public HoldNoteNoteSelectionBlueprint(DrawableHoldNote holdNote, HoldNotePosition position)
|
||||||
|
: base(holdNote)
|
||||||
|
{
|
||||||
|
this.position = position;
|
||||||
|
InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X };
|
||||||
|
|
||||||
|
Select();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
||||||
|
if (DrawableObject.IsLoaded)
|
||||||
|
{
|
||||||
|
DrawableNote note = position == HoldNotePosition.Start ? DrawableObject.Head : DrawableObject.Tail;
|
||||||
|
|
||||||
|
Anchor = note.Anchor;
|
||||||
|
Origin = note.Origin;
|
||||||
|
|
||||||
|
Size = note.DrawSize;
|
||||||
|
Position = note.DrawPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
|
||||||
|
public override bool HandlePositionalInput => false;
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -49,13 +48,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
private double originalStartTime;
|
private double originalStartTime;
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
base.OnMouseMove(e);
|
base.UpdatePosition(screenSpacePosition);
|
||||||
|
|
||||||
if (PlacementBegun)
|
if (PlacementBegun)
|
||||||
{
|
{
|
||||||
var endTime = TimeAt(e.ScreenSpaceMousePosition);
|
var endTime = TimeAt(screenSpacePosition);
|
||||||
|
|
||||||
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime;
|
||||||
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
HitObject.Duration = Math.Abs(endTime - originalStartTime);
|
||||||
@ -65,10 +64,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
headPiece.Width = tailPiece.Width = SnappedWidth;
|
headPiece.Width = tailPiece.Width = SnappedWidth;
|
||||||
headPiece.X = tailPiece.X = SnappedMousePosition.X;
|
headPiece.X = tailPiece.X = SnappedMousePosition.X;
|
||||||
|
|
||||||
originalStartTime = HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
|
originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs
Normal file
11
osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePosition.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||||
|
{
|
||||||
|
public enum HoldNotePosition
|
||||||
|
{
|
||||||
|
Start,
|
||||||
|
End
|
||||||
|
}
|
||||||
|
}
|
@ -16,69 +16,57 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
|
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
|
||||||
{
|
{
|
||||||
public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
|
public new DrawableHoldNote DrawableObject => (DrawableHoldNote)base.DrawableObject;
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
private readonly BodyPiece body;
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
|
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
|
||||||
: base(hold)
|
: base(hold)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new HoldNoteNoteSelectionBlueprint(hold.Head),
|
|
||||||
new HoldNoteNoteSelectionBlueprint(hold.Tail),
|
|
||||||
body = new BodyPiece
|
|
||||||
{
|
|
||||||
AccentColour = Color4.Transparent
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours, IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
body.BorderColour = colours.Yellow;
|
|
||||||
|
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.Start),
|
||||||
|
new HoldNoteNoteSelectionBlueprint(DrawableObject, HoldNotePosition.End),
|
||||||
|
new BodyPiece
|
||||||
|
{
|
||||||
|
AccentColour = Color4.Transparent,
|
||||||
|
BorderColour = colours.Yellow
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Size = HitObject.DrawSize + new Vector2(0, HitObject.Tail.DrawHeight);
|
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
||||||
|
if (DrawableObject.IsLoaded)
|
||||||
|
{
|
||||||
|
Size = DrawableObject.DrawSize + new Vector2(0, DrawableObject.Tail.DrawHeight);
|
||||||
|
|
||||||
// This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
|
// This is a side-effect of not matching the hitobject's anchors/origins, which is kinda hard to do
|
||||||
// When scrolling upwards our origin is already at the top of the head note (which is the intended location),
|
// When scrolling upwards our origin is already at the top of the head note (which is the intended location),
|
||||||
// but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
|
// but when scrolling downwards our origin is at the _bottom_ of the tail note (where we need to be at the _top_ of the tail note)
|
||||||
if (direction.Value == ScrollingDirection.Down)
|
if (direction.Value == ScrollingDirection.Down)
|
||||||
Y -= HitObject.Tail.DrawHeight;
|
Y -= DrawableObject.Tail.DrawHeight;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
private class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint
|
|
||||||
{
|
|
||||||
public HoldNoteNoteSelectionBlueprint(DrawableNote note)
|
|
||||||
: base(note)
|
|
||||||
{
|
|
||||||
Select();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
Anchor = HitObject.Anchor;
|
|
||||||
Origin = HitObject.Origin;
|
|
||||||
|
|
||||||
Position = HitObject.DrawPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Todo: This is temporary, since the note masks don't do anything special yet. In the future they will handle input.
|
|
||||||
public override bool HandlePositionalInput => false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,10 +49,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
if (Column == null)
|
if (Column == null)
|
||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
|
|
||||||
HitObject.StartTime = TimeAt(e.ScreenSpaceMousePosition);
|
|
||||||
HitObject.Column = Column.Index;
|
HitObject.Column = Column.Index;
|
||||||
|
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition));
|
||||||
BeginPlacement();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,19 +60,18 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
return base.OnMouseUp(e);
|
return base.OnMouseUp(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
if (!PlacementBegun)
|
if (!PlacementBegun)
|
||||||
Column = ColumnAt(e.ScreenSpaceMousePosition);
|
Column = ColumnAt(screenSpacePosition);
|
||||||
|
|
||||||
if (Column == null) return false;
|
if (Column == null) return;
|
||||||
|
|
||||||
SnappedWidth = Column.DrawWidth;
|
SnappedWidth = Column.DrawWidth;
|
||||||
|
|
||||||
// Snap to the column
|
// Snap to the column
|
||||||
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
|
var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0)));
|
||||||
SnappedMousePosition = new Vector2(parentPos.X, e.MousePosition.Y);
|
SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected double TimeAt(Vector2 screenSpacePosition)
|
protected double TimeAt(Vector2 screenSpacePosition)
|
||||||
@ -86,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
|
|
||||||
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
|
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
|
||||||
// so we need to flip the vertical coordinate in the hitobject container's space
|
// so we need to flip the vertical coordinate in the hitobject container's space
|
||||||
var hitObjectPos = Column.HitObjectContainer.ToLocalSpace(applyPositionOffset(screenSpacePosition, false)).Y;
|
var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y;
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
|
hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos;
|
||||||
|
|
||||||
@ -103,16 +100,58 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
scrollingInfo.TimeRange.Value,
|
scrollingInfo.TimeRange.Value,
|
||||||
Column.HitObjectContainer.DrawHeight);
|
Column.HitObjectContainer.DrawHeight);
|
||||||
|
|
||||||
return applyPositionOffset(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent), true).Y;
|
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
|
pos = Column.HitObjectContainer.DrawHeight - pos;
|
||||||
|
|
||||||
|
return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected Column ColumnAt(Vector2 screenSpacePosition)
|
protected Column ColumnAt(Vector2 screenSpacePosition)
|
||||||
=> composer.ColumnAt(applyPositionOffset(screenSpacePosition, false));
|
=> composer.ColumnAt(screenSpacePosition);
|
||||||
|
|
||||||
private Vector2 applyPositionOffset(Vector2 position, bool reverse)
|
/// <summary>
|
||||||
|
/// Converts a mouse position to a hitobject position.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="mousePosition">The mouse position.</param>
|
||||||
|
/// <returns>The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction.</returns>
|
||||||
|
private Vector2 mouseToHitObjectPosition(Vector2 mousePosition)
|
||||||
{
|
{
|
||||||
position.Y += (scrollingInfo.Direction.Value == ScrollingDirection.Up && !reverse ? -1 : 1) * NotePiece.NOTE_HEIGHT / 2;
|
switch (scrollingInfo.Direction.Value)
|
||||||
return position;
|
{
|
||||||
|
case ScrollingDirection.Up:
|
||||||
|
mousePosition.Y -= NotePiece.NOTE_HEIGHT / 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScrollingDirection.Down:
|
||||||
|
mousePosition.Y += NotePiece.NOTE_HEIGHT / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mousePosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a hitobject position to a mouse position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObjectPosition">The hitobject position.</param>
|
||||||
|
/// <returns>The resulting mouse position, anchored at the centre of the hitobject.</returns>
|
||||||
|
private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition)
|
||||||
|
{
|
||||||
|
switch (scrollingInfo.Direction.Value)
|
||||||
|
{
|
||||||
|
case ScrollingDirection.Up:
|
||||||
|
hitObjectPosition.Y += NotePiece.NOTE_HEIGHT / 2;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ScrollingDirection.Down:
|
||||||
|
hitObjectPosition.Y -= NotePiece.NOTE_HEIGHT / 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hitObjectPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
public Vector2 ScreenSpaceDragPosition { get; private set; }
|
public Vector2 ScreenSpaceDragPosition { get; private set; }
|
||||||
public Vector2 DragPosition { get; private set; }
|
public Vector2 DragPosition { get; private set; }
|
||||||
|
|
||||||
protected new DrawableManiaHitObject HitObject => (DrawableManiaHitObject)base.HitObject;
|
public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject;
|
||||||
|
|
||||||
protected IClock EditorClock { get; private set; }
|
protected IClock EditorClock { get; private set; }
|
||||||
|
|
||||||
@ -28,8 +28,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IManiaHitObjectComposer composer { get; set; }
|
private IManiaHitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
public ManiaSelectionBlueprint(DrawableHitObject hitObject)
|
public ManiaSelectionBlueprint(DrawableHitObject drawableObject)
|
||||||
: base(hitObject)
|
: base(drawableObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
}
|
}
|
||||||
@ -44,13 +44,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Position = Parent.ToLocalSpace(HitObject.ToScreenSpace(Vector2.Zero));
|
Position = Parent.ToLocalSpace(DrawableObject.ToScreenSpace(Vector2.Zero));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
{
|
{
|
||||||
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
|
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
|
||||||
DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
|
DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
|
||||||
|
|
||||||
return base.OnMouseDown(e);
|
return base.OnMouseDown(e);
|
||||||
}
|
}
|
||||||
@ -60,20 +60,20 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
var result = base.OnDrag(e);
|
var result = base.OnDrag(e);
|
||||||
|
|
||||||
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
|
ScreenSpaceDragPosition = e.ScreenSpaceMousePosition;
|
||||||
DragPosition = HitObject.ToLocalSpace(e.ScreenSpaceMousePosition);
|
DragPosition = DrawableObject.ToLocalSpace(e.ScreenSpaceMousePosition);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Show()
|
public override void Show()
|
||||||
{
|
{
|
||||||
HitObject.AlwaysAlive = true;
|
DrawableObject.AlwaysAlive = true;
|
||||||
base.Show();
|
base.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Hide()
|
public override void Hide()
|
||||||
{
|
{
|
||||||
HitObject.AlwaysAlive = false;
|
DrawableObject.AlwaysAlive = false;
|
||||||
base.Hide();
|
base.Hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Size = HitObject.DrawSize;
|
// Todo: This shouldn't exist, mania should not reference the drawable hitobject directly.
|
||||||
|
if (DrawableObject.IsLoaded)
|
||||||
|
Size = DrawableObject.DrawSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@
|
|||||||
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
using osu.Game.Rulesets.Mania.Edit.Blueprints;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -31,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
editorClock = clock;
|
editorClock = clock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
|
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||||
{
|
{
|
||||||
adjustOrigins((ManiaSelectionBlueprint)blueprint);
|
var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint;
|
||||||
performDragMovement(dragEvent);
|
int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column;
|
||||||
performColumnMovement(dragEvent);
|
|
||||||
|
|
||||||
base.HandleDrag(blueprint, dragEvent);
|
adjustOrigins(maniaBlueprint);
|
||||||
|
performDragMovement(moveEvent);
|
||||||
|
performColumnMovement(lastColumn, moveEvent);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -47,41 +48,44 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
/// <param name="reference">The <see cref="ManiaSelectionBlueprint"/> that received the drag event.</param>
|
/// <param name="reference">The <see cref="ManiaSelectionBlueprint"/> that received the drag event.</param>
|
||||||
private void adjustOrigins(ManiaSelectionBlueprint reference)
|
private void adjustOrigins(ManiaSelectionBlueprint reference)
|
||||||
{
|
{
|
||||||
var referenceParent = (HitObjectContainer)reference.HitObject.Parent;
|
var referenceParent = (HitObjectContainer)reference.DrawableObject.Parent;
|
||||||
|
|
||||||
float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.HitObject.OriginPosition.Y;
|
float offsetFromReferenceOrigin = reference.DragPosition.Y - reference.DrawableObject.OriginPosition.Y;
|
||||||
float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin;
|
float targetPosition = referenceParent.ToLocalSpace(reference.ScreenSpaceDragPosition).Y - offsetFromReferenceOrigin;
|
||||||
|
|
||||||
// Flip the vertical coordinate space when scrolling downwards
|
// Flip the vertical coordinate space when scrolling downwards
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
targetPosition = targetPosition - referenceParent.DrawHeight;
|
targetPosition = targetPosition - referenceParent.DrawHeight;
|
||||||
|
|
||||||
float movementDelta = targetPosition - reference.HitObject.Position.Y;
|
float movementDelta = targetPosition - reference.DrawableObject.Position.Y;
|
||||||
|
|
||||||
foreach (var b in SelectedBlueprints.OfType<ManiaSelectionBlueprint>())
|
foreach (var b in SelectedBlueprints.OfType<ManiaSelectionBlueprint>())
|
||||||
b.HitObject.Y += movementDelta;
|
b.DrawableObject.Y += movementDelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performDragMovement(DragEvent dragEvent)
|
private void performDragMovement(MoveSelectionEvent moveEvent)
|
||||||
{
|
{
|
||||||
|
float delta = moveEvent.InstantDelta.Y;
|
||||||
|
|
||||||
|
// When scrolling downwards the anchor position is at the bottom of the screen, however the movement event assumes the anchor is at the top of the screen.
|
||||||
|
// This causes the delta to assume a positive hitobject position, and which can be corrected for by subtracting the parent height.
|
||||||
|
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
|
delta -= moveEvent.Blueprint.DrawableObject.Parent.DrawHeight;
|
||||||
|
|
||||||
foreach (var b in SelectedBlueprints)
|
foreach (var b in SelectedBlueprints)
|
||||||
{
|
{
|
||||||
var hitObject = b.HitObject;
|
var hitObject = b.DrawableObject;
|
||||||
|
|
||||||
var objectParent = (HitObjectContainer)hitObject.Parent;
|
var objectParent = (HitObjectContainer)hitObject.Parent;
|
||||||
|
|
||||||
// Using the hitobject position is required since AdjustPosition can be invoked multiple times per frame
|
// StartTime could be used to adjust the position if only one movement event was received per frame.
|
||||||
// without the position having been updated by the parenting ScrollingHitObjectContainer
|
// However this is not the case and ScrollingHitObjectContainer performs movement in UpdateAfterChildren() so the position must also be updated to be valid for further movement events
|
||||||
hitObject.Y += dragEvent.Delta.Y;
|
hitObject.Y += delta;
|
||||||
|
|
||||||
float targetPosition;
|
float targetPosition = hitObject.Position.Y;
|
||||||
|
|
||||||
// If we're scrolling downwards, a position of 0 is actually further away from the hit target
|
// The scrolling algorithm always assumes an anchor at the top of the screen, so the position must be flipped when scrolling downwards to reflect a top anchor
|
||||||
// so we need to flip the vertical coordinate in the hitobject container's space
|
|
||||||
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
if (scrollingInfo.Direction.Value == ScrollingDirection.Down)
|
||||||
targetPosition = -hitObject.Position.Y;
|
targetPosition = -targetPosition;
|
||||||
else
|
|
||||||
targetPosition = hitObject.Position.Y;
|
|
||||||
|
|
||||||
objectParent.Remove(hitObject);
|
objectParent.Remove(hitObject);
|
||||||
|
|
||||||
@ -94,14 +98,13 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performColumnMovement(DragEvent dragEvent)
|
private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent)
|
||||||
{
|
{
|
||||||
var lastColumn = composer.ColumnAt(dragEvent.ScreenSpaceLastMousePosition);
|
var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition);
|
||||||
var currentColumn = composer.ColumnAt(dragEvent.ScreenSpaceMousePosition);
|
if (currentColumn == null)
|
||||||
if (lastColumn == null || currentColumn == null)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int columnDelta = currentColumn.Index - lastColumn.Index;
|
int columnDelta = currentColumn.Index - lastColumn;
|
||||||
if (columnDelta == 0)
|
if (columnDelta == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks
|
|||||||
{
|
{
|
||||||
public abstract class ManiaSelectionBlueprint : SelectionBlueprint
|
public abstract class ManiaSelectionBlueprint : SelectionBlueprint
|
||||||
{
|
{
|
||||||
protected ManiaSelectionBlueprint(DrawableHitObject hitObject)
|
protected ManiaSelectionBlueprint(DrawableHitObject drawableObject)
|
||||||
: base(hitObject)
|
: base(drawableObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None;
|
RelativeSizeAxes = Axes.None;
|
||||||
}
|
}
|
||||||
|
12
osu.Game.Rulesets.Mania/Objects/BarLine.cs
Normal file
12
osu.Game.Rulesets.Mania/Objects/BarLine.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
|
{
|
||||||
|
public class BarLine : ManiaHitObject, IBarLine
|
||||||
|
{
|
||||||
|
public bool Major { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,6 @@
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -14,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// Visualises a <see cref="BarLine"/>. Although this derives DrawableManiaHitObject,
|
/// Visualises a <see cref="BarLine"/>. Although this derives DrawableManiaHitObject,
|
||||||
/// this does not handle input/sound like a normal hit object.
|
/// this does not handle input/sound like a normal hit object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DrawableBarLine : DrawableHitObject<BarLine>
|
public class DrawableBarLine : DrawableManiaHitObject<BarLine>
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Height of major bar line triangles.
|
/// Height of major bar line triangles.
|
||||||
|
@ -2,13 +2,12 @@
|
|||||||
// 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.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
|
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.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -22,8 +21,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public override bool DisplayResult => false;
|
public override bool DisplayResult => false;
|
||||||
|
|
||||||
public readonly DrawableNote Head;
|
public DrawableNote Head => headContainer.Child;
|
||||||
public readonly DrawableNote Tail;
|
public DrawableNote Tail => tailContainer.Child;
|
||||||
|
|
||||||
|
private readonly Container<DrawableHeadNote> headContainer;
|
||||||
|
private readonly Container<DrawableTailNote> tailContainer;
|
||||||
|
private readonly Container<DrawableHoldNoteTick> tickContainer;
|
||||||
|
|
||||||
private readonly BodyPiece bodyPiece;
|
private readonly BodyPiece bodyPiece;
|
||||||
|
|
||||||
@ -40,50 +43,81 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public DrawableHoldNote(HoldNote hitObject)
|
public DrawableHoldNote(HoldNote hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
Container<DrawableHoldNoteTick> tickContainer;
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
{
|
{
|
||||||
bodyPiece = new BodyPiece
|
bodyPiece = new BodyPiece { RelativeSizeAxes = Axes.X },
|
||||||
{
|
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
|
||||||
RelativeSizeAxes = Axes.X,
|
headContainer = new Container<DrawableHeadNote> { RelativeSizeAxes = Axes.Both },
|
||||||
},
|
tailContainer = new Container<DrawableTailNote> { RelativeSizeAxes = Axes.Both },
|
||||||
tickContainer = new Container<DrawableHoldNoteTick>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ChildrenEnumerable = HitObject.NestedHitObjects.OfType<HoldNoteTick>().Select(tick => new DrawableHoldNoteTick(tick)
|
|
||||||
{
|
|
||||||
HoldStartTime = () => holdStartTime
|
|
||||||
})
|
|
||||||
},
|
|
||||||
Head = new DrawableHeadNote(this)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre
|
|
||||||
},
|
|
||||||
Tail = new DrawableTailNote(this)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
foreach (var tick in tickContainer)
|
|
||||||
AddNested(tick);
|
|
||||||
|
|
||||||
AddNested(Head);
|
|
||||||
AddNested(Tail);
|
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
bodyPiece.AccentColour = colour.NewValue;
|
bodyPiece.AccentColour = colour.NewValue;
|
||||||
Head.AccentColour.Value = colour.NewValue;
|
|
||||||
Tail.AccentColour.Value = colour.NewValue;
|
|
||||||
tickContainer.ForEach(t => t.AccentColour.Value = colour.NewValue);
|
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
base.AddNestedHitObject(hitObject);
|
||||||
|
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case DrawableHeadNote head:
|
||||||
|
headContainer.Child = head;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableTailNote tail:
|
||||||
|
tailContainer.Child = tail;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableHoldNoteTick tick:
|
||||||
|
tickContainer.Add(tick);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ClearNestedHitObjects()
|
||||||
|
{
|
||||||
|
base.ClearNestedHitObjects();
|
||||||
|
headContainer.Clear();
|
||||||
|
tailContainer.Clear();
|
||||||
|
tickContainer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case TailNote _:
|
||||||
|
return new DrawableTailNote(this)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
AccentColour = { BindTarget = AccentColour }
|
||||||
|
};
|
||||||
|
|
||||||
|
case Note _:
|
||||||
|
return new DrawableHeadNote(this)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
AccentColour = { BindTarget = AccentColour }
|
||||||
|
};
|
||||||
|
|
||||||
|
case HoldNoteTick tick:
|
||||||
|
return new DrawableHoldNoteTick(tick)
|
||||||
|
{
|
||||||
|
HoldStartTime = () => holdStartTime,
|
||||||
|
AccentColour = { BindTarget = AccentColour }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateNestedHitObject(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||||
{
|
{
|
||||||
base.OnDirectionChanged(e);
|
base.OnDirectionChanged(e);
|
||||||
|
@ -101,6 +101,6 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
public override Judgement CreateJudgement() => new HoldNoteJudgement();
|
public override Judgement CreateJudgement() => new HoldNoteJudgement();
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => null;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
{
|
{
|
||||||
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
|
public override Judgement CreateJudgement() => new HoldNoteTickJudgement();
|
||||||
|
|
||||||
protected override HitWindows CreateHitWindows() => null;
|
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public DrawableManiaRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
: base(ruleset, beatmap, mods)
|
: base(ruleset, beatmap, mods)
|
||||||
{
|
{
|
||||||
BarLines = new BarLineGenerator(Beatmap).BarLines;
|
BarLines = new BarLineGenerator<BarLine>(Beatmap).BarLines;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
@ -12,7 +12,6 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Debug/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll"
|
"${workspaceRoot}/bin/Debug/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/netcoreapp2.2/osu.Game.Rulesets.Osu.Tests.dll"
|
"${workspaceRoot}/bin/Release/netcoreapp3.0/osu.Game.Rulesets.Osu.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Tests.Beatmaps;
|
using osu.Game.Tests.Beatmaps;
|
||||||
@ -21,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
public void TestStacking()
|
public void TestStacking()
|
||||||
{
|
{
|
||||||
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(beatmap_data)))
|
using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(beatmap_data)))
|
||||||
using (var reader = new StreamReader(stream))
|
using (var reader = new LineBufferedReader(stream))
|
||||||
{
|
{
|
||||||
var beatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
var beatmap = Decoder.GetDecoder<Beatmap>(reader).Decode(reader);
|
||||||
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>());
|
var converted = new TestWorkingBeatmap(beatmap).GetPlayableBeatmap(new OsuRuleset().RulesetInfo, Array.Empty<Mod>());
|
||||||
|
230
osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
Normal file
230
osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Connections;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneFollowPoints : OsuTestScene
|
||||||
|
{
|
||||||
|
private Container<DrawableOsuHitObject> hitObjectContainer;
|
||||||
|
private FollowPointRenderer followPointRenderer;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both },
|
||||||
|
followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddObject()
|
||||||
|
{
|
||||||
|
addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } });
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveObject()
|
||||||
|
{
|
||||||
|
addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } });
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(0));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAddMultipleObjects()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveEndObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(4));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveStartObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(0));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRemoveMiddleObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
removeObjectStep(() => getObject(2));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMoveObject()
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100));
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(0, 0)] // Start -> Start
|
||||||
|
[TestCase(0, 2)] // Start -> Middle
|
||||||
|
[TestCase(0, 5)] // Start -> End
|
||||||
|
[TestCase(2, 0)] // Middle -> Start
|
||||||
|
[TestCase(1, 3)] // Middle -> Middle (forwards)
|
||||||
|
[TestCase(3, 1)] // Middle -> Middle (backwards)
|
||||||
|
[TestCase(4, 0)] // End -> Start
|
||||||
|
[TestCase(4, 2)] // End -> Middle
|
||||||
|
[TestCase(4, 4)] // End -> End
|
||||||
|
public void TestReorderObjects(int startIndex, int endIndex)
|
||||||
|
{
|
||||||
|
addMultipleObjectsStep();
|
||||||
|
|
||||||
|
reorderObjectStep(startIndex, endIndex);
|
||||||
|
|
||||||
|
assertGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[]
|
||||||
|
{
|
||||||
|
new HitCircle { Position = new Vector2(100, 100) },
|
||||||
|
new HitCircle { Position = new Vector2(200, 200) },
|
||||||
|
new HitCircle { Position = new Vector2(300, 300) },
|
||||||
|
new HitCircle { Position = new Vector2(400, 400) },
|
||||||
|
new HitCircle { Position = new Vector2(500, 500) },
|
||||||
|
});
|
||||||
|
|
||||||
|
private void addObjectsStep(Func<OsuHitObject[]> ctorFunc)
|
||||||
|
{
|
||||||
|
AddStep("add hitobjects", () =>
|
||||||
|
{
|
||||||
|
var objects = ctorFunc();
|
||||||
|
|
||||||
|
for (int i = 0; i < objects.Length; i++)
|
||||||
|
{
|
||||||
|
objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1);
|
||||||
|
objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
DrawableOsuHitObject drawableObject = null;
|
||||||
|
|
||||||
|
switch (objects[i])
|
||||||
|
{
|
||||||
|
case HitCircle circle:
|
||||||
|
drawableObject = new DrawableHitCircle(circle);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Slider slider:
|
||||||
|
drawableObject = new DrawableSlider(slider);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Spinner spinner:
|
||||||
|
drawableObject = new DrawableSpinner(spinner);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
hitObjectContainer.Add(drawableObject);
|
||||||
|
followPointRenderer.AddFollowPoints(drawableObject);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeObjectStep(Func<DrawableOsuHitObject> getFunc)
|
||||||
|
{
|
||||||
|
AddStep("remove hitobject", () =>
|
||||||
|
{
|
||||||
|
var drawableObject = getFunc?.Invoke();
|
||||||
|
|
||||||
|
hitObjectContainer.Remove(drawableObject);
|
||||||
|
followPointRenderer.RemoveFollowPoints(drawableObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reorderObjectStep(int startIndex, int endIndex)
|
||||||
|
{
|
||||||
|
AddStep($"move object {startIndex} to {endIndex}", () =>
|
||||||
|
{
|
||||||
|
DrawableOsuHitObject toReorder = getObject(startIndex);
|
||||||
|
|
||||||
|
double targetTime;
|
||||||
|
if (endIndex < hitObjectContainer.Count)
|
||||||
|
targetTime = getObject(endIndex).HitObject.StartTime - 1;
|
||||||
|
else
|
||||||
|
targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1;
|
||||||
|
|
||||||
|
hitObjectContainer.Remove(toReorder);
|
||||||
|
toReorder.HitObject.StartTime = targetTime;
|
||||||
|
hitObjectContainer.Add(toReorder);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertGroups()
|
||||||
|
{
|
||||||
|
AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count);
|
||||||
|
AddAssert("group endpoints are correct", () =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < hitObjectContainer.Count; i++)
|
||||||
|
{
|
||||||
|
DrawableOsuHitObject expectedStart = getObject(i);
|
||||||
|
DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null;
|
||||||
|
|
||||||
|
if (getGroup(i).Start != expectedStart)
|
||||||
|
throw new AssertionException($"Object {i} expected to be the start of group {i}.");
|
||||||
|
|
||||||
|
if (getGroup(i).End != expectedEnd)
|
||||||
|
throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index];
|
||||||
|
|
||||||
|
private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index];
|
||||||
|
|
||||||
|
private class TestHitObjectContainer : Container<DrawableOsuHitObject>
|
||||||
|
{
|
||||||
|
protected override int Compare(Drawable x, Drawable y)
|
||||||
|
{
|
||||||
|
var osuX = (DrawableOsuHitObject)x;
|
||||||
|
var osuY = (DrawableOsuHitObject)y;
|
||||||
|
|
||||||
|
int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime);
|
||||||
|
|
||||||
|
if (compare == 0)
|
||||||
|
return base.Compare(x, y);
|
||||||
|
|
||||||
|
return compare;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs
Normal file
26
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleComboChange.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneHitCircleComboChange : TestSceneHitCircle
|
||||||
|
{
|
||||||
|
private readonly Bindable<int> comboIndex = new Bindable<int>();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override TestDrawableHitCircle CreateDrawableHitCircle(HitCircle circle, bool auto)
|
||||||
|
{
|
||||||
|
circle.ComboIndexBindable.BindTo(comboIndex);
|
||||||
|
circle.IndexInCurrentComboBindable.BindTo(comboIndex);
|
||||||
|
return base.CreateDrawableHitCircle(circle, auto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
// 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 NUnit.Framework;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
@ -14,16 +15,58 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
|
public class TestSceneHitCircleSelectionBlueprint : SelectionBlueprintTestScene
|
||||||
{
|
{
|
||||||
private readonly DrawableHitCircle drawableObject;
|
private HitCircle hitCircle;
|
||||||
|
private DrawableHitCircle drawableObject;
|
||||||
|
private TestBlueprint blueprint;
|
||||||
|
|
||||||
public TestSceneHitCircleSelectionBlueprint()
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
var hitCircle = new HitCircle { Position = new Vector2(256, 192) };
|
Clear();
|
||||||
|
|
||||||
|
hitCircle = new HitCircle { Position = new Vector2(256, 192) };
|
||||||
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||||
|
|
||||||
Add(drawableObject = new DrawableHitCircle(hitCircle));
|
Add(drawableObject = new DrawableHitCircle(hitCircle));
|
||||||
|
AddBlueprint(blueprint = new TestBlueprint(drawableObject));
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInitialState()
|
||||||
|
{
|
||||||
|
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject);
|
[Test]
|
||||||
|
public void TestMoveHitObject()
|
||||||
|
{
|
||||||
|
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
|
||||||
|
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMoveAfterApplyingDefaults()
|
||||||
|
{
|
||||||
|
AddStep("apply defaults", () => hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
|
||||||
|
AddStep("move hitobject", () => hitCircle.Position = new Vector2(300, 225));
|
||||||
|
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStackedHitObject()
|
||||||
|
{
|
||||||
|
AddStep("set stacking", () => hitCircle.StackHeight = 5);
|
||||||
|
AddAssert("blueprint positioned over hitobject", () => blueprint.CirclePiece.Position == hitCircle.StackedPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestBlueprint : HitCircleSelectionBlueprint
|
||||||
|
{
|
||||||
|
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||||
|
|
||||||
|
public TestBlueprint(DrawableHitCircle drawableCircle)
|
||||||
|
: base(drawableCircle)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
197
osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
Normal file
197
osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneOsuDistanceSnapGrid : ManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private const double beat_length = 100;
|
||||||
|
private static readonly Vector2 grid_position = new Vector2(512, 384);
|
||||||
|
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(CircularDistanceSnapGrid)
|
||||||
|
};
|
||||||
|
|
||||||
|
[Cached(typeof(IEditorBeatmap))]
|
||||||
|
private readonly EditorBeatmap<OsuHitObject> editorBeatmap;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
|
||||||
|
|
||||||
|
[Cached(typeof(IDistanceSnapProvider))]
|
||||||
|
private readonly SnapProvider snapProvider = new SnapProvider();
|
||||||
|
|
||||||
|
private TestOsuDistanceSnapGrid grid;
|
||||||
|
|
||||||
|
public TestSceneOsuDistanceSnapGrid()
|
||||||
|
{
|
||||||
|
editorBeatmap = new EditorBeatmap<OsuHitObject>(new OsuBeatmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1;
|
||||||
|
editorBeatmap.ControlPointInfo.Clear();
|
||||||
|
editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length });
|
||||||
|
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.SlateGray
|
||||||
|
},
|
||||||
|
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }),
|
||||||
|
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[TestCase(1)]
|
||||||
|
[TestCase(2)]
|
||||||
|
[TestCase(3)]
|
||||||
|
[TestCase(4)]
|
||||||
|
[TestCase(6)]
|
||||||
|
[TestCase(8)]
|
||||||
|
[TestCase(12)]
|
||||||
|
[TestCase(16)]
|
||||||
|
public void TestBeatDivisor(int divisor)
|
||||||
|
{
|
||||||
|
AddStep($"set beat divisor = {divisor}", () => beatDivisor.Value = divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCursorInCentre()
|
||||||
|
{
|
||||||
|
AddStep("move mouse to centre", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position)));
|
||||||
|
assertSnappedDistance((float)beat_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCursorBeforeMovementPoint()
|
||||||
|
{
|
||||||
|
AddStep("move mouse to just before movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.49f)));
|
||||||
|
assertSnappedDistance((float)beat_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCursorAfterMovementPoint()
|
||||||
|
{
|
||||||
|
AddStep("move mouse to just after movement point", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 1.51f)));
|
||||||
|
assertSnappedDistance((float)beat_length * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLimitedDistance()
|
||||||
|
{
|
||||||
|
AddStep("create limited grid", () =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.SlateGray
|
||||||
|
},
|
||||||
|
grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }),
|
||||||
|
new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f)));
|
||||||
|
assertSnappedDistance((float)beat_length * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () =>
|
||||||
|
{
|
||||||
|
Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position;
|
||||||
|
|
||||||
|
return Precision.AlmostEquals(expectedDistance, Vector2.Distance(snappedPosition, grid_position));
|
||||||
|
});
|
||||||
|
|
||||||
|
private class SnappingCursorContainer : CompositeDrawable
|
||||||
|
{
|
||||||
|
public Func<Vector2, Vector2> GetSnapPosition;
|
||||||
|
|
||||||
|
private readonly Drawable cursor;
|
||||||
|
|
||||||
|
public SnappingCursorContainer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = cursor = new Circle
|
||||||
|
{
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(50),
|
||||||
|
Colour = Color4.Red
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
updatePosition(GetContainingInputManager().CurrentState.Mouse.Position);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
|
{
|
||||||
|
base.OnMouseMove(e);
|
||||||
|
|
||||||
|
updatePosition(e.ScreenSpaceMousePosition);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePosition(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
cursor.Position = GetSnapPosition.Invoke(screenSpacePosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestOsuDistanceSnapGrid : OsuDistanceSnapGrid
|
||||||
|
{
|
||||||
|
public new float DistanceSpacing => base.DistanceSpacing;
|
||||||
|
|
||||||
|
public TestOsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject = null)
|
||||||
|
: base(hitObject, nextHitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SnapProvider : IDistanceSnapProvider
|
||||||
|
{
|
||||||
|
public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time);
|
||||||
|
|
||||||
|
public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length;
|
||||||
|
|
||||||
|
public float DurationToDistance(double referenceTime, double duration) => (float)duration;
|
||||||
|
|
||||||
|
public double DistanceToDuration(double referenceTime, float distance) => distance;
|
||||||
|
|
||||||
|
public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0;
|
||||||
|
|
||||||
|
public float GetSnappedDistanceFromDistance(double referenceTime, float distance) => 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -111,6 +111,21 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1)));
|
AddStep("Distance Overflow 1 Repeat", () => SetContents(() => testDistanceOverflow(1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangeStackHeight()
|
||||||
|
{
|
||||||
|
DrawableSlider slider = null;
|
||||||
|
|
||||||
|
AddStep("create slider", () =>
|
||||||
|
{
|
||||||
|
slider = (DrawableSlider)createSlider(repeats: 1);
|
||||||
|
Add(slider);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change stack height", () => slider.HitObject.StackHeight = 10);
|
||||||
|
AddAssert("body positioned correctly", () => slider.Position == slider.HitObject.StackedPosition);
|
||||||
|
}
|
||||||
|
|
||||||
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
|
private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats);
|
||||||
|
|
||||||
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
|
private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10);
|
||||||
@ -293,15 +308,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier)
|
||||||
{
|
{
|
||||||
var cpi = new ControlPointInfo();
|
var cpi = new ControlPointInfo();
|
||||||
cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
|
cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier });
|
||||||
|
|
||||||
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
|
slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 });
|
||||||
|
|
||||||
var drawable = new DrawableSlider(slider)
|
var drawable = CreateDrawableSlider(slider);
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Depth = depthIndex++
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
|
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
|
||||||
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
mod.ApplyToDrawableHitObjects(new[] { drawable });
|
||||||
@ -311,6 +322,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual DrawableSlider CreateDrawableSlider(Slider slider) => new DrawableSlider(slider)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Depth = depthIndex++
|
||||||
|
};
|
||||||
|
|
||||||
private float judgementOffsetDirection = 1;
|
private float judgementOffsetDirection = 1;
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
28
osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs
Normal file
28
osu.Game.Rulesets.Osu.Tests/TestSceneSliderComboChange.cs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneSliderComboChange : TestSceneSlider
|
||||||
|
{
|
||||||
|
private readonly Bindable<int> comboIndex = new Bindable<int>();
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Scheduler.AddDelayed(() => comboIndex.Value++, 250, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawableSlider CreateDrawableSlider(Slider slider)
|
||||||
|
{
|
||||||
|
slider.ComboIndexBindable.BindTo(comboIndex);
|
||||||
|
slider.IndexInCurrentComboBindable.BindTo(comboIndex);
|
||||||
|
|
||||||
|
return base.CreateDrawableSlider(slider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
}, 25),
|
}, 25),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ControlPointInfo =
|
|
||||||
{
|
|
||||||
DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } }
|
|
||||||
},
|
|
||||||
BeatmapInfo =
|
BeatmapInfo =
|
||||||
{
|
{
|
||||||
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
|
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 },
|
||||||
@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
p.OnLoadComplete += _ =>
|
p.OnLoadComplete += _ =>
|
||||||
|
@ -3,17 +3,20 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
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.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
@ -29,11 +32,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
typeof(PathControlPointPiece)
|
typeof(PathControlPointPiece)
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly DrawableSlider drawableObject;
|
private Slider slider;
|
||||||
|
private DrawableSlider drawableObject;
|
||||||
|
private TestSliderBlueprint blueprint;
|
||||||
|
|
||||||
public TestSceneSliderSelectionBlueprint()
|
[SetUp]
|
||||||
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
var slider = new Slider
|
Clear();
|
||||||
|
|
||||||
|
slider = new Slider
|
||||||
{
|
{
|
||||||
Position = new Vector2(256, 192),
|
Position = new Vector2(256, 192),
|
||||||
Path = new SliderPath(PathType.Bezier, new[]
|
Path = new SliderPath(PathType.Bezier, new[]
|
||||||
@ -47,8 +55,178 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||||
|
|
||||||
Add(drawableObject = new DrawableSlider(slider));
|
Add(drawableObject = new DrawableSlider(slider));
|
||||||
|
AddBlueprint(blueprint = new TestSliderBlueprint(drawableObject));
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInitialState()
|
||||||
|
{
|
||||||
|
checkPositions();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject);
|
[Test]
|
||||||
|
public void TestMoveHitObject()
|
||||||
|
{
|
||||||
|
moveHitObject();
|
||||||
|
checkPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMoveAfterApplyingDefaults()
|
||||||
|
{
|
||||||
|
AddStep("apply defaults", () => slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 }));
|
||||||
|
moveHitObject();
|
||||||
|
checkPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStackedHitObject()
|
||||||
|
{
|
||||||
|
AddStep("set stacking", () => slider.StackHeight = 5);
|
||||||
|
checkPositions();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleControlPointSelection()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, true);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleControlPointDeselectionViaOtherControlPoint()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSingleControlPointDeselectionViaClickOutside()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject));
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleControlPointSelection()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("ctrl + click", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
checkControlPointSelected(0, true);
|
||||||
|
checkControlPointSelected(1, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleControlPointDeselectionViaOtherControlPoint()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("ctrl + click", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
moveMouseToControlPoint(2);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultipleControlPointDeselectionViaClickOutside()
|
||||||
|
{
|
||||||
|
moveMouseToControlPoint(0);
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
moveMouseToControlPoint(1);
|
||||||
|
AddStep("ctrl + click", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.ControlLeft);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.ReleaseKey(Key.ControlLeft);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject));
|
||||||
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
checkControlPointSelected(0, false);
|
||||||
|
checkControlPointSelected(1, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveHitObject()
|
||||||
|
{
|
||||||
|
AddStep("move hitobject", () =>
|
||||||
|
{
|
||||||
|
slider.Position = new Vector2(300, 225);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkPositions()
|
||||||
|
{
|
||||||
|
AddAssert("body positioned correctly", () => blueprint.BodyPiece.Position == slider.StackedPosition);
|
||||||
|
|
||||||
|
AddAssert("head positioned correctly",
|
||||||
|
() => Precision.AlmostEquals(blueprint.HeadBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.HeadCircle.ScreenSpaceDrawQuad.Centre));
|
||||||
|
|
||||||
|
AddAssert("tail positioned correctly",
|
||||||
|
() => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void moveMouseToControlPoint(int index)
|
||||||
|
{
|
||||||
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
|
{
|
||||||
|
Vector2 position = slider.Position + slider.Path.ControlPoints[index];
|
||||||
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkControlPointSelected(int index, bool selected)
|
||||||
|
=> AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected);
|
||||||
|
|
||||||
|
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||||
|
{
|
||||||
|
public new SliderBodyPiece BodyPiece => base.BodyPiece;
|
||||||
|
public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint;
|
||||||
|
public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint;
|
||||||
|
public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser;
|
||||||
|
|
||||||
|
public TestSliderBlueprint(DrawableSlider slider)
|
||||||
|
: base(slider)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new TestSliderCircleBlueprint(slider, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestSliderCircleBlueprint : SliderCircleSelectionBlueprint
|
||||||
|
{
|
||||||
|
public new HitCirclePiece CirclePiece => base.CirclePiece;
|
||||||
|
|
||||||
|
public TestSliderCircleBlueprint(DrawableSlider slider, SliderPosition position)
|
||||||
|
: base(slider, position)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
96
osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
Normal file
96
osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.MathUtils;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneSpinnerRotation : TestSceneOsuPlayer
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private AudioManager audioManager { get; set; }
|
||||||
|
|
||||||
|
private TrackVirtualManual track;
|
||||||
|
|
||||||
|
protected override bool Autoplay => true;
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var working = new ClockBackedTestWorkingBeatmap(beatmap, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
track = (TrackVirtualManual)working.Track;
|
||||||
|
return working;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableSpinner drawableSpinner;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddUntilStep("wait for track to start running", () => track.IsRunning);
|
||||||
|
AddStep("retrieve spinner", () => drawableSpinner = (DrawableSpinner)((TestPlayer)Player).DrawableRuleset.Playfield.AllHitObjects.First());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpinnerRewindingRotation()
|
||||||
|
{
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddAssert("is rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
|
||||||
|
|
||||||
|
addSeekStep(0);
|
||||||
|
AddAssert("is rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSpinnerMiddleRewindingRotation()
|
||||||
|
{
|
||||||
|
double estimatedRotation = 0;
|
||||||
|
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddStep("retrieve rotation", () => estimatedRotation = drawableSpinner.Disc.RotationAbsolute);
|
||||||
|
|
||||||
|
addSeekStep(2500);
|
||||||
|
addSeekStep(5000);
|
||||||
|
AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addSeekStep(double time)
|
||||||
|
{
|
||||||
|
AddStep($"seek to {time}", () => track.Seek(time));
|
||||||
|
|
||||||
|
AddUntilStep("wait for seek to finish", () => Precision.AlmostEquals(time, ((TestPlayer)Player).DrawableRuleset.FrameStableClock.CurrentTime, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Spinner
|
||||||
|
{
|
||||||
|
Position = new Vector2(256, 192),
|
||||||
|
EndTime = 5000,
|
||||||
|
},
|
||||||
|
// placeholder object to avoid hitting the results screen
|
||||||
|
new HitObject
|
||||||
|
{
|
||||||
|
StartTime = 99999,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -25,8 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
typeof(SpinnerPiece)
|
typeof(SpinnerPiece)
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly DrawableSpinner drawableSpinner;
|
|
||||||
|
|
||||||
public TestSceneSpinnerSelectionBlueprint()
|
public TestSceneSpinnerSelectionBlueprint()
|
||||||
{
|
{
|
||||||
var spinner = new Spinner
|
var spinner = new Spinner
|
||||||
@ -35,16 +32,19 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
StartTime = -1000,
|
StartTime = -1000,
|
||||||
EndTime = 2000
|
EndTime = 2000
|
||||||
};
|
};
|
||||||
|
|
||||||
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
|
||||||
|
|
||||||
|
DrawableSpinner drawableSpinner;
|
||||||
|
|
||||||
Add(new Container
|
Add(new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Size = new Vector2(0.5f),
|
Size = new Vector2(0.5f),
|
||||||
Child = drawableSpinner = new DrawableSpinner(spinner)
|
Child = drawableSpinner = new DrawableSpinner(spinner)
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) };
|
AddBlueprint(new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,14 @@
|
|||||||
<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.3.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||||
<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.15.1" />
|
||||||
<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">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
<TargetFramework>netcoreapp3.0</TargetFramework>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Project References">
|
<ItemGroup Label="Project References">
|
||||||
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj" />
|
||||||
|
25
osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs
Normal file
25
osu.Game.Rulesets.Osu/Edit/Blueprints/BlueprintPiece.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A piece of a selection or placement blueprint which visualises an <see cref="OsuHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of <see cref="OsuHitObject"/> which this <see cref="BlueprintPiece{T}"/> visualises.</typeparam>
|
||||||
|
public abstract class BlueprintPiece<T> : CompositeDrawable
|
||||||
|
where T : OsuHitObject
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Updates this <see cref="BlueprintPiece{T}"/> using the properties of a <see cref="OsuHitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="OsuHitObject"/> to reference properties from.</param>
|
||||||
|
public virtual void UpdateFrom(T hitObject)
|
||||||
|
{
|
||||||
|
Position = hitObject.StackedPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,18 +10,13 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||||
{
|
{
|
||||||
public class HitCirclePiece : HitObjectPiece
|
public class HitCirclePiece : BlueprintPiece<HitCircle>
|
||||||
{
|
{
|
||||||
private readonly HitCircle hitCircle;
|
public HitCirclePiece()
|
||||||
|
|
||||||
public HitCirclePiece(HitCircle hitCircle)
|
|
||||||
: base(hitCircle)
|
|
||||||
{
|
{
|
||||||
this.hitCircle = hitCircle;
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
Scale = new Vector2(hitCircle.Scale);
|
|
||||||
CornerRadius = Size.X / 2;
|
CornerRadius = Size.X / 2;
|
||||||
|
|
||||||
InternalChild = new RingPiece();
|
InternalChild = new RingPiece();
|
||||||
@ -31,12 +26,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
|||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
Colour = colours.Yellow;
|
Colour = colours.Yellow;
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => UpdatePosition(), true);
|
|
||||||
StackHeightBindable.BindValueChanged(_ => UpdatePosition());
|
|
||||||
ScaleBindable.BindValueChanged(scale => Scale = new Vector2(scale.NewValue), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void UpdatePosition() => Position = hitCircle.StackedPosition;
|
public override void UpdateFrom(HitCircle hitObject)
|
||||||
|
{
|
||||||
|
base.UpdateFrom(hitObject);
|
||||||
|
|
||||||
|
Scale = new Vector2(hitObject.Scale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,31 +13,30 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
|||||||
{
|
{
|
||||||
public new HitCircle HitObject => (HitCircle)base.HitObject;
|
public new HitCircle HitObject => (HitCircle)base.HitObject;
|
||||||
|
|
||||||
|
private readonly HitCirclePiece circlePiece;
|
||||||
|
|
||||||
public HitCirclePlacementBlueprint()
|
public HitCirclePlacementBlueprint()
|
||||||
: base(new HitCircle())
|
: base(new HitCircle())
|
||||||
{
|
{
|
||||||
InternalChild = new HitCirclePiece(HitObject);
|
InternalChild = circlePiece = new HitCirclePiece();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.Update();
|
||||||
|
|
||||||
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
circlePiece.UpdateFrom(HitObject);
|
||||||
HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
HitObject.StartTime = EditorClock.CurrentTime;
|
|
||||||
EndPlacement();
|
EndPlacement();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
HitObject.Position = e.MousePosition;
|
HitObject.Position = ToLocalSpace(screenSpacePosition);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,35 @@
|
|||||||
// 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 osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
|
||||||
{
|
{
|
||||||
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint
|
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint<HitCircle>
|
||||||
{
|
{
|
||||||
public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle)
|
protected new DrawableHitCircle DrawableObject => (DrawableHitCircle)base.DrawableObject;
|
||||||
: base(hitCircle)
|
|
||||||
|
protected readonly HitCirclePiece CirclePiece;
|
||||||
|
|
||||||
|
public HitCircleSelectionBlueprint(DrawableHitCircle drawableCircle)
|
||||||
|
: base(drawableCircle)
|
||||||
{
|
{
|
||||||
InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);
|
InternalChild = CirclePiece = new HitCirclePiece();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
CirclePiece.UpdateFrom(HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.HitArea.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
public override Quad SelectionQuad => DrawableObject.HitArea.ScreenSpaceDrawQuad;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A piece of a blueprint which responds to changes in the state of a <see cref="OsuHitObject"/>.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class HitObjectPiece : CompositeDrawable
|
|
||||||
{
|
|
||||||
protected readonly IBindable<Vector2> PositionBindable = new Bindable<Vector2>();
|
|
||||||
protected readonly IBindable<int> StackHeightBindable = new Bindable<int>();
|
|
||||||
protected readonly IBindable<float> ScaleBindable = new Bindable<float>();
|
|
||||||
|
|
||||||
private readonly OsuHitObject hitObject;
|
|
||||||
|
|
||||||
protected HitObjectPiece(OsuHitObject hitObject)
|
|
||||||
{
|
|
||||||
this.hitObject = hitObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
PositionBindable.BindTo(hitObject.PositionBindable);
|
|
||||||
StackHeightBindable.BindTo(hitObject.StackHeightBindable);
|
|
||||||
ScaleBindable.BindTo(hitObject.ScaleBindable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,12 +7,13 @@ using osu.Game.Rulesets.Osu.Objects;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||||
{
|
{
|
||||||
public class OsuSelectionBlueprint : SelectionBlueprint
|
public abstract class OsuSelectionBlueprint<T> : SelectionBlueprint
|
||||||
|
where T : OsuHitObject
|
||||||
{
|
{
|
||||||
protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject;
|
protected T HitObject => (T)DrawableObject.HitObject;
|
||||||
|
|
||||||
public OsuSelectionBlueprint(DrawableHitObject hitObject)
|
protected OsuSelectionBlueprint(DrawableHitObject drawableObject)
|
||||||
: base(hitObject)
|
: base(drawableObject)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A piece of a blueprint which responds to changes in the state of a <see cref="Slider"/>.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class SliderPiece : HitObjectPiece
|
|
||||||
{
|
|
||||||
protected readonly IBindable<SliderPath> PathBindable = new Bindable<SliderPath>();
|
|
||||||
|
|
||||||
private readonly Slider slider;
|
|
||||||
|
|
||||||
protected SliderPiece(Slider slider)
|
|
||||||
: base(slider)
|
|
||||||
{
|
|
||||||
this.slider = slider;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
PathBindable.BindTo(slider.PathBindable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +1,37 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
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.Lines;
|
using osu.Framework.Graphics.Lines;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class PathControlPointPiece : CompositeDrawable
|
public class PathControlPointPiece : BlueprintPiece<Slider>
|
||||||
{
|
{
|
||||||
private readonly Slider slider;
|
public Action<int> RequestSelection;
|
||||||
private readonly int index;
|
public Action<Vector2[]> ControlPointsChanged;
|
||||||
|
|
||||||
|
public readonly BindableBool IsSelected = new BindableBool();
|
||||||
|
public readonly int Index;
|
||||||
|
|
||||||
|
private readonly Slider slider;
|
||||||
private readonly Path path;
|
private readonly Path path;
|
||||||
private readonly CircularContainer marker;
|
private readonly Container marker;
|
||||||
|
private readonly Drawable markerRing;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private IDistanceSnapProvider snapProvider { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
@ -28,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
public PathControlPointPiece(Slider slider, int index)
|
public PathControlPointPiece(Slider slider, int index)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
this.index = index;
|
Index = index;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -40,13 +51,36 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
PathRadius = 1
|
PathRadius = 1
|
||||||
},
|
},
|
||||||
marker = new CircularContainer
|
marker = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(10),
|
AutoSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Children = new[]
|
||||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(10),
|
||||||
|
},
|
||||||
|
markerRing = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(14),
|
||||||
|
Masking = true,
|
||||||
|
BorderThickness = 2,
|
||||||
|
BorderColour = Color4.White,
|
||||||
|
Alpha = 0,
|
||||||
|
Child = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Alpha = 0,
|
||||||
|
AlwaysPresent = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -55,48 +89,88 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
Position = slider.StackedPosition + slider.Path.ControlPoints[index];
|
Position = slider.StackedPosition + slider.Path.ControlPoints[Index];
|
||||||
|
|
||||||
marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow;
|
updateMarkerDisplay();
|
||||||
|
updateConnectingPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the state of the circular control point marker.
|
||||||
|
/// </summary>
|
||||||
|
private void updateMarkerDisplay()
|
||||||
|
{
|
||||||
|
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||||
|
|
||||||
|
Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow;
|
||||||
|
if (IsHovered || IsSelected.Value)
|
||||||
|
colour = Color4.White;
|
||||||
|
marker.Colour = colour;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the path connecting this control point to the previous one.
|
||||||
|
/// </summary>
|
||||||
|
private void updateConnectingPath()
|
||||||
|
{
|
||||||
path.ClearVertices();
|
path.ClearVertices();
|
||||||
|
|
||||||
if (index != slider.Path.ControlPoints.Length - 1)
|
if (Index != slider.Path.ControlPoints.Length - 1)
|
||||||
{
|
{
|
||||||
path.AddVertex(Vector2.Zero);
|
path.AddVertex(Vector2.Zero);
|
||||||
path.AddVertex(slider.Path.ControlPoints[index + 1] - slider.Path.ControlPoints[index]);
|
path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The connecting path is excluded from positional input
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
if (RequestSelection != null)
|
||||||
|
{
|
||||||
|
RequestSelection.Invoke(Index);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e) => RequestSelection != null;
|
||||||
|
|
||||||
protected override bool OnDragStart(DragStartEvent e) => true;
|
protected override bool OnDragStart(DragStartEvent e) => true;
|
||||||
|
|
||||||
protected override bool OnDrag(DragEvent e)
|
protected override bool OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
var newControlPoints = slider.Path.ControlPoints.ToArray();
|
var newControlPoints = slider.Path.ControlPoints.ToArray();
|
||||||
|
|
||||||
if (index == 0)
|
if (Index == 0)
|
||||||
{
|
{
|
||||||
// Special handling for the head - only the position of the slider changes
|
// Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account
|
||||||
slider.Position += e.Delta;
|
(Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime);
|
||||||
|
Vector2 movementDelta = snappedPosition - slider.Position;
|
||||||
|
|
||||||
|
slider.Position += movementDelta;
|
||||||
|
slider.StartTime = snappedTime;
|
||||||
|
|
||||||
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
||||||
for (int i = 1; i < newControlPoints.Length; i++)
|
for (int i = 1; i < newControlPoints.Length; i++)
|
||||||
newControlPoints[i] -= e.Delta;
|
newControlPoints[i] -= movementDelta;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
newControlPoints[index] += e.Delta;
|
newControlPoints[Index] += e.Delta;
|
||||||
|
|
||||||
if (isSegmentSeparatorWithNext)
|
if (isSegmentSeparatorWithNext)
|
||||||
newControlPoints[index + 1] = newControlPoints[index];
|
newControlPoints[Index + 1] = newControlPoints[Index];
|
||||||
|
|
||||||
if (isSegmentSeparatorWithPrevious)
|
if (isSegmentSeparatorWithPrevious)
|
||||||
newControlPoints[index - 1] = newControlPoints[index];
|
newControlPoints[Index - 1] = newControlPoints[Index];
|
||||||
|
|
||||||
slider.Path = new SliderPath(slider.Path.Type, newControlPoints);
|
ControlPointsChanged?.Invoke(newControlPoints);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -105,8 +179,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
|
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithNext => index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[index + 1] == slider.Path.ControlPoints[index];
|
private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index];
|
||||||
|
|
||||||
private bool isSegmentSeparatorWithPrevious => index > 0 && slider.Path.ControlPoints[index - 1] == slider.Path.ControlPoints[index];
|
private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,39 +1,133 @@
|
|||||||
// 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.Framework.Input;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class PathControlPointVisualiser : SliderPiece
|
public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler<PlatformAction>
|
||||||
{
|
{
|
||||||
|
public Action<Vector2[]> ControlPointsChanged;
|
||||||
|
|
||||||
|
internal readonly Container<PathControlPointPiece> Pieces;
|
||||||
private readonly Slider slider;
|
private readonly Slider slider;
|
||||||
|
private readonly bool allowSelection;
|
||||||
|
|
||||||
private readonly Container<PathControlPointPiece> pieces;
|
private InputManager inputManager;
|
||||||
|
|
||||||
public PathControlPointVisualiser(Slider slider)
|
[Resolved(CanBeNull = true)]
|
||||||
: base(slider)
|
private IPlacementHandler placementHandler { get; set; }
|
||||||
|
|
||||||
|
public PathControlPointVisualiser(Slider slider, bool allowSelection)
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
this.slider = slider;
|
||||||
|
this.allowSelection = allowSelection;
|
||||||
|
|
||||||
InternalChild = pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
InternalChild = Pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
protected override void LoadComplete()
|
||||||
private void load()
|
|
||||||
{
|
{
|
||||||
PathBindable.BindValueChanged(_ => updatePathControlPoints(), true);
|
base.LoadComplete();
|
||||||
|
|
||||||
|
inputManager = GetContainingInputManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePathControlPoints()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
while (slider.Path.ControlPoints.Length > pieces.Count)
|
base.Update();
|
||||||
pieces.Add(new PathControlPointPiece(slider, pieces.Count));
|
|
||||||
while (slider.Path.ControlPoints.Length < pieces.Count)
|
while (slider.Path.ControlPoints.Length > Pieces.Count)
|
||||||
pieces.Remove(pieces[pieces.Count - 1]);
|
{
|
||||||
|
var piece = new PathControlPointPiece(slider, Pieces.Count)
|
||||||
|
{
|
||||||
|
ControlPointsChanged = c => ControlPointsChanged?.Invoke(c),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (allowSelection)
|
||||||
|
piece.RequestSelection = selectPiece;
|
||||||
|
|
||||||
|
Pieces.Add(piece);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (slider.Path.ControlPoints.Length < Pieces.Count)
|
||||||
|
Pieces.Remove(Pieces[Pieces.Count - 1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectPiece(int index)
|
||||||
|
{
|
||||||
|
if (inputManager.CurrentState.Keyboard.ControlPressed)
|
||||||
|
Pieces[index].IsSelected.Toggle();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = piece.Index == index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(PlatformAction action)
|
||||||
|
{
|
||||||
|
switch (action.ActionMethod)
|
||||||
|
{
|
||||||
|
case PlatformActionMethod.Delete:
|
||||||
|
var newControlPoints = new List<Vector2>();
|
||||||
|
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
{
|
||||||
|
if (!piece.IsSelected.Value)
|
||||||
|
newControlPoints.Add(slider.Path.ControlPoints[piece.Index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that there are any points to be deleted
|
||||||
|
if (newControlPoints.Count == slider.Path.ControlPoints.Length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// If there are 0 remaining control points, treat the slider as being deleted
|
||||||
|
if (newControlPoints.Count == 0)
|
||||||
|
{
|
||||||
|
placementHandler?.Delete(slider);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make control points relative
|
||||||
|
Vector2 first = newControlPoints[0];
|
||||||
|
for (int i = 0; i < newControlPoints.Count; i++)
|
||||||
|
newControlPoints[i] = newControlPoints[i] - first;
|
||||||
|
|
||||||
|
// The slider's position defines the position of the first control point, and all further control points are relative to that point
|
||||||
|
slider.Position = slider.Position + first;
|
||||||
|
|
||||||
|
// Since pieces are re-used, they will not point to the deleted control points while remaining selected
|
||||||
|
foreach (var piece in Pieces)
|
||||||
|
piece.IsSelected.Value = false;
|
||||||
|
|
||||||
|
ControlPointsChanged?.Invoke(newControlPoints.ToArray());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,19 +11,15 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
||||||
{
|
{
|
||||||
public class SliderBodyPiece : SliderPiece
|
public class SliderBodyPiece : BlueprintPiece<Slider>
|
||||||
{
|
{
|
||||||
private readonly Slider slider;
|
|
||||||
private readonly ManualSliderBody body;
|
private readonly ManualSliderBody body;
|
||||||
|
|
||||||
public SliderBodyPiece(Slider slider)
|
public SliderBodyPiece()
|
||||||
: base(slider)
|
|
||||||
{
|
{
|
||||||
this.slider = slider;
|
|
||||||
|
|
||||||
InternalChild = body = new ManualSliderBody
|
InternalChild = body = new ManualSliderBody
|
||||||
{
|
{
|
||||||
AccentColour = Color4.Transparent,
|
AccentColour = Color4.Transparent
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,24 +27,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
body.BorderColour = colours.Yellow;
|
body.BorderColour = colours.Yellow;
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
|
||||||
ScaleBindable.BindValueChanged(scale => body.PathRadius = scale.NewValue * OsuHitObject.OBJECT_RADIUS, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePosition() => Position = slider.StackedPosition;
|
public override void UpdateFrom(Slider hitObject)
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
{
|
||||||
base.Update();
|
base.UpdateFrom(hitObject);
|
||||||
|
|
||||||
|
body.PathRadius = hitObject.Scale * OsuHitObject.OBJECT_RADIUS;
|
||||||
|
|
||||||
var vertices = new List<Vector2>();
|
var vertices = new List<Vector2>();
|
||||||
slider.Path.GetPathToProgress(vertices, 0, 1);
|
hitObject.Path.GetPathToProgress(vertices, 0, 1);
|
||||||
|
|
||||||
body.SetVertices(vertices);
|
body.SetVertices(vertices);
|
||||||
|
|
||||||
Size = body.Size;
|
Size = body.Size;
|
||||||
OriginPosition = body.PathOffset;
|
OriginPosition = body.PathOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,47 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|
||||||
{
|
|
||||||
public class SliderCirclePiece : HitCirclePiece
|
|
||||||
{
|
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
|
||||||
|
|
||||||
private readonly Slider slider;
|
|
||||||
private readonly SliderPosition position;
|
|
||||||
|
|
||||||
public SliderCirclePiece(Slider slider, SliderPosition position)
|
|
||||||
: base(slider.HeadCircle)
|
|
||||||
{
|
|
||||||
this.slider = slider;
|
|
||||||
this.position = position;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
|
||||||
pathBindable.BindValueChanged(_ => UpdatePosition(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdatePosition()
|
|
||||||
{
|
|
||||||
switch (position)
|
|
||||||
{
|
|
||||||
case SliderPosition.Start:
|
|
||||||
Position = slider.StackedPosition + slider.Path.PositionAt(0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SliderPosition.End:
|
|
||||||
Position = slider.StackedPosition + slider.Path.PositionAt(1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,34 @@
|
|||||||
// 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 osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||||
{
|
{
|
||||||
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint
|
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
||||||
{
|
{
|
||||||
public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
|
protected readonly HitCirclePiece CirclePiece;
|
||||||
: base(hitObject)
|
|
||||||
|
private readonly SliderPosition position;
|
||||||
|
|
||||||
|
public SliderCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position)
|
||||||
|
: base(slider)
|
||||||
{
|
{
|
||||||
InternalChild = new SliderCirclePiece(slider, position);
|
this.position = position;
|
||||||
|
InternalChild = CirclePiece = new HitCirclePiece();
|
||||||
|
|
||||||
Select();
|
Select();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
CirclePiece.UpdateFrom(position == SliderPosition.Start ? HitObject.HeadCircle : HitObject.TailCircle);
|
||||||
|
}
|
||||||
|
|
||||||
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
|
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
|
||||||
public override bool HandlePositionalInput => false;
|
public override bool HandlePositionalInput => false;
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,13 @@ using System.Linq;
|
|||||||
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.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
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.Osu.Edit.Blueprints.HitCircles.Components;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -21,11 +23,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
|
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
|
||||||
|
|
||||||
|
private SliderBodyPiece bodyPiece;
|
||||||
|
private HitCirclePiece headCirclePiece;
|
||||||
|
private HitCirclePiece tailCirclePiece;
|
||||||
|
|
||||||
private readonly List<Segment> segments = new List<Segment>();
|
private readonly List<Segment> segments = new List<Segment>();
|
||||||
private Vector2 cursor;
|
private Vector2 cursor;
|
||||||
|
private InputManager inputManager;
|
||||||
|
|
||||||
private PlacementState state;
|
private PlacementState state;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private HitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
public SliderPlacementBlueprint()
|
public SliderPlacementBlueprint()
|
||||||
: base(new Objects.Slider())
|
: base(new Objects.Slider())
|
||||||
{
|
{
|
||||||
@ -38,10 +48,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new SliderBodyPiece(HitObject),
|
bodyPiece = new SliderBodyPiece(),
|
||||||
new SliderCirclePiece(HitObject, SliderPosition.Start),
|
headCirclePiece = new HitCirclePiece(),
|
||||||
new SliderCirclePiece(HitObject, SliderPosition.End),
|
tailCirclePiece = new HitCirclePiece(),
|
||||||
new PathControlPointVisualiser(HitObject),
|
new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() },
|
||||||
};
|
};
|
||||||
|
|
||||||
setState(PlacementState.Initial);
|
setState(PlacementState.Initial);
|
||||||
@ -50,25 +60,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
inputManager = GetContainingInputManager();
|
||||||
// Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame
|
|
||||||
HitObject.Position = Parent?.ToLocalSpace(GetContainingInputManager().CurrentState.Mouse.Position) ?? Vector2.Zero;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case PlacementState.Initial:
|
case PlacementState.Initial:
|
||||||
HitObject.Position = e.MousePosition;
|
HitObject.Position = ToLocalSpace(screenSpacePosition);
|
||||||
return true;
|
break;
|
||||||
|
|
||||||
case PlacementState.Body:
|
case PlacementState.Body:
|
||||||
cursor = e.MousePosition - HitObject.Position;
|
// The given screen-space position may have been externally snapped, but the unsnapped position from the input manager
|
||||||
return true;
|
// is used instead since snapping control points doesn't make much sense
|
||||||
|
cursor = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
@ -109,8 +117,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
private void beginCurve()
|
private void beginCurve()
|
||||||
{
|
{
|
||||||
BeginPlacement();
|
BeginPlacement();
|
||||||
|
|
||||||
HitObject.StartTime = EditorClock.CurrentTime;
|
|
||||||
setState(PlacementState.Body);
|
setState(PlacementState.Body);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,8 +134,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
private void updateSlider()
|
private void updateSlider()
|
||||||
{
|
{
|
||||||
var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
|
Vector2[] newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
|
||||||
HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
|
|
||||||
|
var unsnappedPath = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
|
||||||
|
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
||||||
|
|
||||||
|
HitObject.Path = new SliderPath(unsnappedPath.Type, newControlPoints, snappedDistance);
|
||||||
|
|
||||||
|
bodyPiece.UpdateFrom(HitObject);
|
||||||
|
headCirclePiece.UpdateFrom(HitObject.HeadCircle);
|
||||||
|
tailCirclePiece.UpdateFrom(HitObject.TailCircle);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setState(PlacementState newState)
|
private void setState(PlacementState newState)
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
@ -9,9 +13,15 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||||
{
|
{
|
||||||
public class SliderSelectionBlueprint : OsuSelectionBlueprint
|
public class SliderSelectionBlueprint : OsuSelectionBlueprint<Slider>
|
||||||
{
|
{
|
||||||
private readonly SliderCircleSelectionBlueprint headBlueprint;
|
protected readonly SliderBodyPiece BodyPiece;
|
||||||
|
protected readonly SliderCircleSelectionBlueprint HeadBlueprint;
|
||||||
|
protected readonly SliderCircleSelectionBlueprint TailBlueprint;
|
||||||
|
protected readonly PathControlPointVisualiser ControlPointVisualiser;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private HitObjectComposer composer { get; set; }
|
||||||
|
|
||||||
public SliderSelectionBlueprint(DrawableSlider slider)
|
public SliderSelectionBlueprint(DrawableSlider slider)
|
||||||
: base(slider)
|
: base(slider)
|
||||||
@ -20,13 +30,34 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new SliderBodyPiece(sliderObject),
|
BodyPiece = new SliderBodyPiece(),
|
||||||
headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start),
|
HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start),
|
||||||
new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End),
|
TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End),
|
||||||
new PathControlPointVisualiser(sliderObject),
|
ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Vector2 SelectionPoint => headBlueprint.SelectionPoint;
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
BodyPiece.UpdateFrom(HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNewControlPoints(Vector2[] controlPoints)
|
||||||
|
{
|
||||||
|
var unsnappedPath = new SliderPath(controlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, controlPoints);
|
||||||
|
var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance;
|
||||||
|
|
||||||
|
HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance);
|
||||||
|
|
||||||
|
UpdateHitObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint;
|
||||||
|
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
protected virtual SliderCircleSelectionBlueprint CreateCircleSelectionBlueprint(DrawableSlider slider, SliderPosition position) => new SliderCircleSelectionBlueprint(slider, position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,17 +12,13 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
||||||
{
|
{
|
||||||
public class SpinnerPiece : HitObjectPiece
|
public class SpinnerPiece : BlueprintPiece<Spinner>
|
||||||
{
|
{
|
||||||
private readonly Spinner spinner;
|
|
||||||
private readonly CircularContainer circle;
|
private readonly CircularContainer circle;
|
||||||
private readonly RingPiece ring;
|
private readonly RingPiece ring;
|
||||||
|
|
||||||
public SpinnerPiece(Spinner spinner)
|
public SpinnerPiece()
|
||||||
: base(spinner)
|
|
||||||
{
|
{
|
||||||
this.spinner = spinner;
|
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
@ -44,21 +40,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
|
|||||||
Origin = Anchor.Centre
|
Origin = Anchor.Centre
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ring.Scale = new Vector2(spinner.Scale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
Colour = colours.Yellow;
|
Colour = colours.Yellow;
|
||||||
|
|
||||||
PositionBindable.BindValueChanged(_ => updatePosition(), true);
|
|
||||||
StackHeightBindable.BindValueChanged(_ => updatePosition());
|
|
||||||
ScaleBindable.BindValueChanged(scale => ring.Scale = new Vector2(scale.NewValue), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePosition() => Position = spinner.Position;
|
public override void UpdateFrom(Spinner hitObject)
|
||||||
|
{
|
||||||
|
base.UpdateFrom(hitObject);
|
||||||
|
|
||||||
|
ring.Scale = new Vector2(hitObject.Scale);
|
||||||
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => circle.ReceivePositionalInputAt(screenSpacePos);
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Game.Rulesets.Edit;
|
|||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||||
{
|
{
|
||||||
@ -21,7 +22,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
|||||||
public SpinnerPlacementBlueprint()
|
public SpinnerPlacementBlueprint()
|
||||||
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
|
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
|
||||||
{
|
{
|
||||||
InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f };
|
InternalChild = piece = new SpinnerPiece { Alpha = 0.5f };
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
piece.UpdateFrom(HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
@ -33,8 +41,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HitObject.StartTime = EditorClock.CurrentTime;
|
|
||||||
|
|
||||||
isPlacingEnd = true;
|
isPlacingEnd = true;
|
||||||
piece.FadeTo(1f, 150, Easing.OutQuint);
|
piece.FadeTo(1f, 150, Easing.OutQuint);
|
||||||
|
|
||||||
@ -43,5 +49,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void UpdatePosition(Vector2 screenSpacePosition)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,21 @@ using osuTK;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
|
||||||
{
|
{
|
||||||
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint
|
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint<Spinner>
|
||||||
{
|
{
|
||||||
private readonly SpinnerPiece piece;
|
private readonly SpinnerPiece piece;
|
||||||
|
|
||||||
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
|
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
|
||||||
: base(spinner)
|
: base(spinner)
|
||||||
{
|
{
|
||||||
InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject);
|
InternalChild = piece = new SpinnerPiece();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
piece.UpdateFrom(HitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
17
osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
Normal file
17
osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
|
{
|
||||||
|
public class OsuDistanceSnapGrid : CircularDistanceSnapGrid
|
||||||
|
{
|
||||||
|
public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject)
|
||||||
|
: base(hitObject, nextHitObject, hitObject.StackedEndPosition)
|
||||||
|
{
|
||||||
|
Masking = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
|
||||||
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
|
||||||
@ -52,5 +55,46 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
return base.CreateBlueprintFor(hitObject);
|
return base.CreateBlueprintFor(hitObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable<HitObject> selectedHitObjects)
|
||||||
|
{
|
||||||
|
var objects = selectedHitObjects.ToList();
|
||||||
|
|
||||||
|
if (objects.Count == 0)
|
||||||
|
return createGrid(h => h.StartTime <= EditorClock.CurrentTime);
|
||||||
|
|
||||||
|
double minTime = objects.Min(h => h.StartTime);
|
||||||
|
return createGrid(h => h.StartTime < minTime, objects.Count + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a grid from the last <see cref="HitObject"/> matching a predicate to a target <see cref="HitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="sourceSelector">A predicate that matches <see cref="HitObject"/>s where the grid can start from.
|
||||||
|
/// Only the last <see cref="HitObject"/> matching the predicate is used.</param>
|
||||||
|
/// <param name="targetOffset">An offset from the <see cref="HitObject"/> selected via <paramref name="sourceSelector"/> at which the grid should stop.</param>
|
||||||
|
/// <returns>The <see cref="OsuDistanceSnapGrid"/> from a selected <see cref="HitObject"/> to a target <see cref="HitObject"/>.</returns>
|
||||||
|
private OsuDistanceSnapGrid createGrid(Func<HitObject, bool> sourceSelector, int targetOffset = 1)
|
||||||
|
{
|
||||||
|
if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset));
|
||||||
|
|
||||||
|
int sourceIndex = -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++)
|
||||||
|
{
|
||||||
|
if (!sourceSelector(EditorBeatmap.HitObjects[i]))
|
||||||
|
break;
|
||||||
|
|
||||||
|
sourceIndex = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceIndex == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex];
|
||||||
|
OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null;
|
||||||
|
|
||||||
|
return new OsuDistanceSnapGrid(sourceObject, targetObject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,17 +2,20 @@
|
|||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Input.Events;
|
|
||||||
using osu.Game.Rulesets.Edit;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Edit
|
namespace osu.Game.Rulesets.Osu.Edit
|
||||||
{
|
{
|
||||||
public class OsuSelectionHandler : SelectionHandler
|
public class OsuSelectionHandler : SelectionHandler
|
||||||
{
|
{
|
||||||
public override void HandleDrag(SelectionBlueprint blueprint, DragEvent dragEvent)
|
public override bool HandleMovement(MoveSelectionEvent moveEvent)
|
||||||
{
|
{
|
||||||
|
Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue);
|
||||||
|
Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue);
|
||||||
|
|
||||||
|
// Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted
|
||||||
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
|
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
|
||||||
{
|
{
|
||||||
if (h is Spinner)
|
if (h is Spinner)
|
||||||
@ -21,10 +24,26 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Position += dragEvent.Delta;
|
// Stacking is not considered
|
||||||
|
minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
|
||||||
|
maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta));
|
||||||
}
|
}
|
||||||
|
|
||||||
base.HandleDrag(blueprint, dragEvent);
|
if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
foreach (var h in SelectedHitObjects.OfType<OsuHitObject>())
|
||||||
|
{
|
||||||
|
if (h is Spinner)
|
||||||
|
{
|
||||||
|
// Spinners don't support position adjustments
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
h.Position += moveEvent.InstantDelta;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Connects hit objects visually, for example with follow points.
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ConnectionRenderer<T> : LifetimeManagementContainer
|
|
||||||
where T : HitObject
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Hit objects to create connections for
|
|
||||||
/// </summary>
|
|
||||||
public abstract IEnumerable<T> HitObjects { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,6 +12,9 @@ using osu.Game.Skinning;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A single follow point positioned between two adjacent <see cref="DrawableOsuHitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
public class FollowPoint : Container
|
public class FollowPoint : Container
|
||||||
{
|
{
|
||||||
private const float width = 8;
|
private const float width = 8;
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Visualises the <see cref="FollowPoint"/>s between two <see cref="DrawableOsuHitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class FollowPointConnection : CompositeDrawable
|
||||||
|
{
|
||||||
|
// Todo: These shouldn't be constants
|
||||||
|
private const int spacing = 32;
|
||||||
|
private const double preempt = 800;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The start time of <see cref="Start"/>.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<double> StartTime = new Bindable<double>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.
|
||||||
|
/// </summary>
|
||||||
|
[NotNull]
|
||||||
|
public readonly DrawableOsuHitObject Start;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="FollowPointConnection"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="start">The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will exit from.</param>
|
||||||
|
public FollowPointConnection([NotNull] DrawableOsuHitObject start)
|
||||||
|
{
|
||||||
|
Start = start;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
|
StartTime.BindTo(Start.HitObject.StartTimeBindable);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
bindEvents(Start);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DrawableOsuHitObject end;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="DrawableOsuHitObject"/> which <see cref="FollowPoint"/>s will enter.
|
||||||
|
/// </summary>
|
||||||
|
[CanBeNull]
|
||||||
|
public DrawableOsuHitObject End
|
||||||
|
{
|
||||||
|
get => end;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
end = value;
|
||||||
|
|
||||||
|
if (end != null)
|
||||||
|
bindEvents(end);
|
||||||
|
|
||||||
|
if (IsLoaded)
|
||||||
|
scheduleRefresh();
|
||||||
|
else
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bindEvents(DrawableOsuHitObject drawableObject)
|
||||||
|
{
|
||||||
|
drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh());
|
||||||
|
drawableObject.HitObject.DefaultsApplied += scheduleRefresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleRefresh() => Scheduler.AddOnce(refresh);
|
||||||
|
|
||||||
|
private void refresh()
|
||||||
|
{
|
||||||
|
ClearInternal();
|
||||||
|
|
||||||
|
if (End == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
OsuHitObject osuStart = Start.HitObject;
|
||||||
|
OsuHitObject osuEnd = End.HitObject;
|
||||||
|
|
||||||
|
if (osuEnd.NewCombo)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (osuStart is Spinner || osuEnd is Spinner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Vector2 startPosition = osuStart.EndPosition;
|
||||||
|
Vector2 endPosition = osuEnd.Position;
|
||||||
|
double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime;
|
||||||
|
double endTime = osuEnd.StartTime;
|
||||||
|
|
||||||
|
Vector2 distanceVector = endPosition - startPosition;
|
||||||
|
int distance = (int)distanceVector.Length;
|
||||||
|
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
|
||||||
|
double duration = endTime - startTime;
|
||||||
|
|
||||||
|
for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing)
|
||||||
|
{
|
||||||
|
float fraction = (float)d / distance;
|
||||||
|
Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
|
||||||
|
Vector2 pointEndPosition = startPosition + fraction * distanceVector;
|
||||||
|
double fadeOutTime = startTime + fraction * duration;
|
||||||
|
double fadeInTime = fadeOutTime - preempt;
|
||||||
|
|
||||||
|
FollowPoint fp;
|
||||||
|
|
||||||
|
AddInternal(fp = new FollowPoint
|
||||||
|
{
|
||||||
|
Position = pointStartPosition,
|
||||||
|
Rotation = rotation,
|
||||||
|
Alpha = 0,
|
||||||
|
Scale = new Vector2(1.5f * osuEnd.Scale),
|
||||||
|
});
|
||||||
|
|
||||||
|
using (fp.BeginAbsoluteSequence(fadeInTime))
|
||||||
|
{
|
||||||
|
fp.FadeIn(osuEnd.TimeFadeIn);
|
||||||
|
fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out);
|
||||||
|
fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out);
|
||||||
|
fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn);
|
||||||
|
}
|
||||||
|
|
||||||
|
fp.Expire(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,121 +1,110 @@
|
|||||||
// 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 osuTK;
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
||||||
{
|
{
|
||||||
public class FollowPointRenderer : ConnectionRenderer<OsuHitObject>
|
/// <summary>
|
||||||
|
/// Visualises connections between <see cref="DrawableOsuHitObject"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public class FollowPointRenderer : CompositeDrawable
|
||||||
{
|
{
|
||||||
private int pointDistance = 32;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Determines how much space there is between points.
|
/// All the <see cref="FollowPointConnection"/>s contained by this <see cref="FollowPointRenderer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int PointDistance
|
internal IReadOnlyList<FollowPointConnection> Connections => connections;
|
||||||
{
|
|
||||||
get => pointDistance;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (pointDistance == value) return;
|
|
||||||
|
|
||||||
pointDistance = value;
|
private readonly List<FollowPointConnection> connections = new List<FollowPointConnection>();
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int preEmpt = 800;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time.
|
|
||||||
/// </summary>
|
|
||||||
public int PreEmpt
|
|
||||||
{
|
|
||||||
get => preEmpt;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (preEmpt == value) return;
|
|
||||||
|
|
||||||
preEmpt = value;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<OsuHitObject> hitObjects;
|
|
||||||
|
|
||||||
public override IEnumerable<OsuHitObject> HitObjects
|
|
||||||
{
|
|
||||||
get => hitObjects;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
hitObjects = value;
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool RemoveCompletedTransforms => false;
|
public override bool RemoveCompletedTransforms => false;
|
||||||
|
|
||||||
private void update()
|
/// <summary>
|
||||||
|
/// Adds the <see cref="FollowPoint"/>s around a <see cref="DrawableOsuHitObject"/>.
|
||||||
|
/// This includes <see cref="FollowPoint"/>s leading into <paramref name="hitObject"/>, and <see cref="FollowPoint"/>s exiting <paramref name="hitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="DrawableOsuHitObject"/> to add <see cref="FollowPoint"/>s for.</param>
|
||||||
|
public void AddFollowPoints(DrawableOsuHitObject hitObject)
|
||||||
|
=> addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g))));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the <see cref="FollowPoint"/>s around a <see cref="DrawableOsuHitObject"/>.
|
||||||
|
/// This includes <see cref="FollowPoint"/>s leading into <paramref name="hitObject"/>, and <see cref="FollowPoint"/>s exiting <paramref name="hitObject"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hitObject">The <see cref="DrawableOsuHitObject"/> to remove <see cref="FollowPoint"/>s for.</param>
|
||||||
|
public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject));
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a <see cref="FollowPointConnection"/> to this <see cref="FollowPointRenderer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection">The <see cref="FollowPointConnection"/> to add.</param>
|
||||||
|
/// <returns>The index of <paramref name="connection"/> in <see cref="connections"/>.</returns>
|
||||||
|
private void addConnection(FollowPointConnection connection)
|
||||||
{
|
{
|
||||||
ClearInternal();
|
AddInternal(connection);
|
||||||
|
|
||||||
if (hitObjects == null)
|
// Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections
|
||||||
return;
|
int index = connections.AddInPlace(connection, Comparer<FollowPointConnection>.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value)));
|
||||||
|
|
||||||
OsuHitObject prevHitObject = null;
|
if (index < connections.Count - 1)
|
||||||
|
|
||||||
foreach (var currHitObject in hitObjects)
|
|
||||||
{
|
{
|
||||||
if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner))
|
// Update the connection's end point to the next connection's start point
|
||||||
{
|
// h1 -> -> -> h2
|
||||||
Vector2 startPosition = prevHitObject.EndPosition;
|
// connection nextGroup
|
||||||
Vector2 endPosition = currHitObject.Position;
|
|
||||||
double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime;
|
|
||||||
double endTime = currHitObject.StartTime;
|
|
||||||
|
|
||||||
Vector2 distanceVector = endPosition - startPosition;
|
FollowPointConnection nextConnection = connections[index + 1];
|
||||||
int distance = (int)distanceVector.Length;
|
connection.End = nextConnection.Start;
|
||||||
float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI));
|
|
||||||
double duration = endTime - startTime;
|
|
||||||
|
|
||||||
for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance)
|
|
||||||
{
|
|
||||||
float fraction = (float)d / distance;
|
|
||||||
Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector;
|
|
||||||
Vector2 pointEndPosition = startPosition + fraction * distanceVector;
|
|
||||||
double fadeOutTime = startTime + fraction * duration;
|
|
||||||
double fadeInTime = fadeOutTime - PreEmpt;
|
|
||||||
|
|
||||||
FollowPoint fp;
|
|
||||||
|
|
||||||
AddInternal(fp = new FollowPoint
|
|
||||||
{
|
|
||||||
Position = pointStartPosition,
|
|
||||||
Rotation = rotation,
|
|
||||||
Alpha = 0,
|
|
||||||
Scale = new Vector2(1.5f * currHitObject.Scale),
|
|
||||||
});
|
|
||||||
|
|
||||||
using (fp.BeginAbsoluteSequence(fadeInTime))
|
|
||||||
{
|
|
||||||
fp.FadeIn(currHitObject.TimeFadeIn);
|
|
||||||
fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out);
|
|
||||||
|
|
||||||
fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out);
|
|
||||||
|
|
||||||
fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn);
|
|
||||||
}
|
|
||||||
|
|
||||||
fp.Expire(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prevHitObject = currHitObject;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The end point may be non-null during re-ordering
|
||||||
|
connection.End = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
// Update the previous connection's end point to the current connection's start point
|
||||||
|
// h1 -> -> -> h2
|
||||||
|
// prevGroup connection
|
||||||
|
|
||||||
|
FollowPointConnection previousConnection = connections[index - 1];
|
||||||
|
previousConnection.End = connection.Start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a <see cref="FollowPointConnection"/> from this <see cref="FollowPointRenderer"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connection">The <see cref="FollowPointConnection"/> to remove.</param>
|
||||||
|
/// <returns>Whether <paramref name="connection"/> was removed.</returns>
|
||||||
|
private void removeGroup(FollowPointConnection connection)
|
||||||
|
{
|
||||||
|
RemoveInternal(connection);
|
||||||
|
|
||||||
|
int index = connections.IndexOf(connection);
|
||||||
|
|
||||||
|
if (index > 0)
|
||||||
|
{
|
||||||
|
// Update the previous connection's end point to the next connection's start point
|
||||||
|
// h1 -> -> -> h2 -> -> -> h3
|
||||||
|
// prevGroup connection nextGroup
|
||||||
|
// The current connection's end point is used since there may not be a next connection
|
||||||
|
FollowPointConnection previousConnection = connections[index - 1];
|
||||||
|
previousConnection.End = connection.End;
|
||||||
|
}
|
||||||
|
|
||||||
|
connections.Remove(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onStartTimeChanged(FollowPointConnection connection)
|
||||||
|
{
|
||||||
|
// Naive but can be improved if performance becomes an issue
|
||||||
|
removeGroup(connection);
|
||||||
|
addConnection(connection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,14 +24,12 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
||||||
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
||||||
|
|
||||||
public OsuAction? HitAction => hitArea.HitAction;
|
public OsuAction? HitAction => HitArea.HitAction;
|
||||||
|
|
||||||
|
public readonly HitReceptor HitArea;
|
||||||
|
public readonly SkinnableDrawable CirclePiece;
|
||||||
private readonly Container scaleContainer;
|
private readonly Container scaleContainer;
|
||||||
|
|
||||||
private readonly HitArea hitArea;
|
|
||||||
|
|
||||||
public SkinnableDrawable CirclePiece { get; }
|
|
||||||
|
|
||||||
public DrawableHitCircle(HitCircle h)
|
public DrawableHitCircle(HitCircle h)
|
||||||
: base(h)
|
: base(h)
|
||||||
{
|
{
|
||||||
@ -48,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
hitArea = new HitArea
|
HitArea = new HitReceptor
|
||||||
{
|
{
|
||||||
Hit = () =>
|
Hit = () =>
|
||||||
{
|
{
|
||||||
@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece(HitObject.IndexInCurrentCombo)),
|
CirclePiece = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.HitCircle), _ => new MainCirclePiece()),
|
||||||
ApproachCircle = new ApproachCircle
|
ApproachCircle = new ApproachCircle
|
||||||
{
|
{
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
@ -69,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Size = hitArea.DrawSize;
|
Size = HitArea.DrawSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -153,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Expire(true);
|
Expire(true);
|
||||||
|
|
||||||
hitArea.HitAction = null;
|
HitArea.HitAction = null;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ArmedState.Miss:
|
case ArmedState.Miss:
|
||||||
@ -172,7 +170,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public Drawable ProxiedLayer => ApproachCircle;
|
public Drawable ProxiedLayer => ApproachCircle;
|
||||||
|
|
||||||
private class HitArea : Drawable, IKeyBindingHandler<OsuAction>
|
public class HitReceptor : Drawable, IKeyBindingHandler<OsuAction>
|
||||||
{
|
{
|
||||||
// IsHovered is used
|
// IsHovered is used
|
||||||
public override bool HandlePositionalInput => true;
|
public override bool HandlePositionalInput => true;
|
||||||
@ -181,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public OsuAction? HitAction;
|
public OsuAction? HitAction;
|
||||||
|
|
||||||
public HitArea()
|
public HitReceptor()
|
||||||
{
|
{
|
||||||
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
Size = new Vector2(OsuHitObject.OBJECT_RADIUS * 2);
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ using osuTK;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -21,16 +20,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach
|
||||||
{
|
{
|
||||||
private readonly Slider slider;
|
public DrawableSliderHead HeadCircle => headContainer.Child;
|
||||||
private readonly List<Drawable> components = new List<Drawable>();
|
public DrawableSliderTail TailCircle => tailContainer.Child;
|
||||||
|
|
||||||
public readonly DrawableHitCircle HeadCircle;
|
|
||||||
public readonly DrawableSliderTail TailCircle;
|
|
||||||
|
|
||||||
public readonly SnakingSliderBody Body;
|
public readonly SnakingSliderBody Body;
|
||||||
public readonly SliderBall Ball;
|
public readonly SliderBall Ball;
|
||||||
|
|
||||||
|
private readonly Container<DrawableSliderHead> headContainer;
|
||||||
|
private readonly Container<DrawableSliderTail> tailContainer;
|
||||||
|
private readonly Container<DrawableSliderTick> tickContainer;
|
||||||
|
private readonly Container<DrawableRepeatPoint> repeatContainer;
|
||||||
|
|
||||||
|
private readonly Slider slider;
|
||||||
|
|
||||||
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
private readonly IBindable<Vector2> positionBindable = new Bindable<Vector2>();
|
||||||
|
private readonly IBindable<int> stackHeightBindable = new Bindable<int>();
|
||||||
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
private readonly IBindable<float> scaleBindable = new Bindable<float>();
|
||||||
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
private readonly IBindable<SliderPath> pathBindable = new Bindable<SliderPath>();
|
||||||
|
|
||||||
@ -44,14 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Position = s.StackedPosition;
|
Position = s.StackedPosition;
|
||||||
|
|
||||||
Container<DrawableSliderTick> ticks;
|
|
||||||
Container<DrawableRepeatPoint> repeatPoints;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
Body = new SnakingSliderBody(s),
|
Body = new SnakingSliderBody(s),
|
||||||
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
tickContainer = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
|
||||||
repeatPoints = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
repeatContainer = new Container<DrawableRepeatPoint> { RelativeSizeAxes = Axes.Both },
|
||||||
Ball = new SliderBall(s, this)
|
Ball = new SliderBall(s, this)
|
||||||
{
|
{
|
||||||
GetInitialHitAction = () => HeadCircle.HitAction,
|
GetInitialHitAction = () => HeadCircle.HitAction,
|
||||||
@ -60,45 +61,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
},
|
},
|
||||||
HeadCircle = new DrawableSliderHead(s, s.HeadCircle)
|
headContainer = new Container<DrawableSliderHead> { RelativeSizeAxes = Axes.Both },
|
||||||
{
|
tailContainer = new Container<DrawableSliderTail> { RelativeSizeAxes = Axes.Both },
|
||||||
OnShake = Shake
|
|
||||||
},
|
|
||||||
TailCircle = new DrawableSliderTail(s, s.TailCircle)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
components.Add(Body);
|
|
||||||
components.Add(Ball);
|
|
||||||
|
|
||||||
AddNested(HeadCircle);
|
|
||||||
|
|
||||||
AddNested(TailCircle);
|
|
||||||
components.Add(TailCircle);
|
|
||||||
|
|
||||||
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
|
|
||||||
{
|
|
||||||
var drawableTick = new DrawableSliderTick(tick) { Position = tick.Position - s.Position };
|
|
||||||
|
|
||||||
ticks.Add(drawableTick);
|
|
||||||
components.Add(drawableTick);
|
|
||||||
AddNested(drawableTick);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
|
|
||||||
{
|
|
||||||
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { Position = repeatPoint.Position - s.Position };
|
|
||||||
|
|
||||||
repeatPoints.Add(drawableRepeatPoint);
|
|
||||||
components.Add(drawableRepeatPoint);
|
|
||||||
AddNested(drawableRepeatPoint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
|
||||||
{
|
|
||||||
base.UpdateInitialTransforms();
|
|
||||||
|
|
||||||
Body.FadeInFromZero(HitObject.TimeFadeIn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -108,6 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
|
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, Body.SnakingOut);
|
||||||
|
|
||||||
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
positionBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||||
|
stackHeightBindable.BindValueChanged(_ => Position = HitObject.StackedPosition);
|
||||||
scaleBindable.BindValueChanged(scale =>
|
scaleBindable.BindValueChanged(scale =>
|
||||||
{
|
{
|
||||||
updatePathRadius();
|
updatePathRadius();
|
||||||
@ -115,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
});
|
});
|
||||||
|
|
||||||
positionBindable.BindTo(HitObject.PositionBindable);
|
positionBindable.BindTo(HitObject.PositionBindable);
|
||||||
|
stackHeightBindable.BindTo(HitObject.StackHeightBindable);
|
||||||
scaleBindable.BindTo(HitObject.ScaleBindable);
|
scaleBindable.BindTo(HitObject.ScaleBindable);
|
||||||
pathBindable.BindTo(slider.PathBindable);
|
pathBindable.BindTo(slider.PathBindable);
|
||||||
|
|
||||||
@ -129,6 +96,67 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
base.AddNestedHitObject(hitObject);
|
||||||
|
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case DrawableSliderHead head:
|
||||||
|
headContainer.Child = head;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSliderTail tail:
|
||||||
|
tailContainer.Child = tail;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableSliderTick tick:
|
||||||
|
tickContainer.Add(tick);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DrawableRepeatPoint repeat:
|
||||||
|
repeatContainer.Add(repeat);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ClearNestedHitObjects()
|
||||||
|
{
|
||||||
|
base.ClearNestedHitObjects();
|
||||||
|
|
||||||
|
headContainer.Clear();
|
||||||
|
tailContainer.Clear();
|
||||||
|
repeatContainer.Clear();
|
||||||
|
tickContainer.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
switch (hitObject)
|
||||||
|
{
|
||||||
|
case SliderTailCircle tail:
|
||||||
|
return new DrawableSliderTail(slider, tail);
|
||||||
|
|
||||||
|
case HitCircle head:
|
||||||
|
return new DrawableSliderHead(slider, head) { OnShake = Shake };
|
||||||
|
|
||||||
|
case SliderTick tick:
|
||||||
|
return new DrawableSliderTick(tick) { Position = tick.Position - slider.Position };
|
||||||
|
|
||||||
|
case RepeatPoint repeat:
|
||||||
|
return new DrawableRepeatPoint(repeat, this) { Position = repeat.Position - slider.Position };
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.CreateNestedHitObject(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void UpdateInitialTransforms()
|
||||||
|
{
|
||||||
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
|
Body.FadeInFromZero(HitObject.TimeFadeIn);
|
||||||
|
}
|
||||||
|
|
||||||
public readonly Bindable<bool> Tracking = new Bindable<bool>();
|
public readonly Bindable<bool> Tracking = new Bindable<bool>();
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -139,9 +167,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1);
|
||||||
|
|
||||||
foreach (var c in components.OfType<ISliderProgress>()) c.UpdateProgress(completionProgress);
|
Ball.UpdateProgress(completionProgress);
|
||||||
foreach (var c in components.OfType<ITrackSnaking>()) c.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
|
Body.UpdateProgress(completionProgress);
|
||||||
foreach (var t in components.OfType<IRequireTracking>()) t.Tracking = Ball.Tracking;
|
|
||||||
|
foreach (DrawableHitObject hitObject in NestedHitObjects)
|
||||||
|
{
|
||||||
|
if (hitObject is ITrackSnaking s) s.UpdateSnakingPosition(slider.Path.PositionAt(Body.SnakedStart ?? 0), slider.Path.PositionAt(Body.SnakedEnd ?? 0));
|
||||||
|
if (hitObject is IRequireTracking t) t.Tracking = Ball.Tracking;
|
||||||
|
}
|
||||||
|
|
||||||
Size = Body.Size;
|
Size = Body.Size;
|
||||||
OriginPosition = Body.PathOffset;
|
OriginPosition = Body.PathOffset;
|
||||||
@ -173,6 +206,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
|
Body.AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? AccentColour.Value;
|
||||||
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
Body.BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
||||||
|
|
||||||
|
bool allowBallTint = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
|
||||||
|
Ball.Colour = allowBallTint ? AccentColour.Value : Color4.White;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
|
private void updatePathRadius() => Body.PathRadius = slider.Scale * sliderPathRadius;
|
||||||
@ -184,7 +220,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
ApplyResult(r =>
|
ApplyResult(r =>
|
||||||
{
|
{
|
||||||
var judgementsCount = NestedHitObjects.Count();
|
var judgementsCount = NestedHitObjects.Count;
|
||||||
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
|
var judgementsHit = NestedHitObjects.Count(h => h.IsHit);
|
||||||
|
|
||||||
var hitFraction = (double)judgementsHit / judgementsCount;
|
var hitFraction = (double)judgementsHit / judgementsCount;
|
||||||
@ -225,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
|
public Drawable ProxiedLayer => HeadCircle.ProxiedLayer;
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Body.ReceivePositionalInputAt(screenSpacePos);
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user