mirror of
https://github.com/ppy/osu.git
synced 2024-12-05 12:12:54 +08:00
Merge branch 'master' into catch-fail-on-banana
This commit is contained in:
commit
5bd037fe8f
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -13,10 +13,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -27,7 +27,7 @@ jobs:
|
||||
run: dotnet restore osu.Desktop.slnf
|
||||
|
||||
- name: Restore inspectcode cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ github.workspace }}/inspectcode
|
||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
||||
@ -70,10 +70,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -99,16 +99,16 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup JDK 11
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: microsoft
|
||||
java-version: 11
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
@ -126,10 +126,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
|
2
.github/workflows/diffcalc.yml
vendored
2
.github/workflows/diffcalc.yml
vendored
@ -140,7 +140,7 @@ jobs:
|
||||
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
||||
steps:
|
||||
- name: Checkout diffcalc-sheet-generator
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ${{ env.EXECUTION_ID }}
|
||||
repository: 'smoogipoo/diffcalc-sheet-generator'
|
||||
|
2
.github/workflows/report-nunit.yml
vendored
2
.github/workflows/report-nunit.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Annotate CI run with test results
|
||||
uses: dorny/test-reporter@v1.6.0
|
||||
uses: dorny/test-reporter@v1.8.0
|
||||
with:
|
||||
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||
|
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
10
.github/workflows/update-web-mod-definitions.yml
vendored
10
.github/workflows/update-web-mod-definitions.yml
vendored
@ -13,23 +13,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Checkout ppy/osu
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: osu
|
||||
|
||||
- name: Checkout ppy/osu-tools
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-tools
|
||||
path: osu-tools
|
||||
|
||||
- name: Checkout ppy/osu-web
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ppy/osu-web
|
||||
path: osu-web
|
||||
@ -43,7 +43,7 @@ jobs:
|
||||
working-directory: ./osu-tools
|
||||
|
||||
- name: Create pull request with changes
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
title: Update mod definitions
|
||||
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net6.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net8.0/osu.Game.Benchmarks.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net6.0/osu.Game.Rulesets.Catch.Tests.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Catch.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Catch.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net6.0/osu.Game.Rulesets.Mania.Tests.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Mania.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Mania.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net6.0/osu.Game.Rulesets.Osu.Tests.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Osu.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Osu.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net6.0/osu.Game.Rulesets.Taiko.Tests.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net8.0/osu.Game.Rulesets.Taiko.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Rulesets.Taiko.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0/osu!.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0/osu!.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--tournament" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tournament.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<browser url="http://localhost:5000" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0/osu!.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0/osu!.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net6.0/osu.Game.Tests.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Tests/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="osu! (Second Client)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0/osu!.dll" />
|
||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0/osu!.dll" />
|
||||
<option name="PROGRAM_PARAMETERS" value="--debug-client-id=1" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net6.0" />
|
||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Desktop/bin/Debug/net8.0" />
|
||||
<option name="PASS_PARENT_ENVS" value="1" />
|
||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||
<option name="USE_MONO" value="0" />
|
||||
@ -12,7 +12,7 @@
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0" />
|
||||
<option name="PROJECT_TFM" value="net8.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
|
18
.vscode/launch.json
vendored
18
.vscode/launch.json
vendored
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll"
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build osu! (Debug)",
|
||||
@ -19,7 +19,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll"
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build osu! (Release)",
|
||||
@ -31,7 +31,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Debug/net6.0/osu.Game.Tests.dll"
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Debug/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build tests (Debug)",
|
||||
@ -43,7 +43,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Release/net6.0/osu.Game.Tests.dll"
|
||||
"${workspaceRoot}/osu.Game.Tests/bin/Release/net8.0/osu.Game.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build tests (Release)",
|
||||
@ -55,7 +55,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll",
|
||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
@ -68,7 +68,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll",
|
||||
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
@ -81,7 +81,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll",
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
@ -94,7 +94,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net6.0/osu.Game.Tournament.Tests.dll",
|
||||
"${workspaceRoot}/osu.Game.Tournament.Tests/bin/Debug/net8.0/osu.Game.Tournament.Tests.dll",
|
||||
"--tournament"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
@ -105,7 +105,7 @@
|
||||
"name": "Benchmark",
|
||||
"type": "coreclr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net6.0/osu.Game.Benchmarks.dll",
|
||||
"program": "${workspaceRoot}/osu.Game.Benchmarks/bin/Release/net8.0/osu.Game.Benchmarks.dll",
|
||||
"args": [
|
||||
"--filter",
|
||||
"*"
|
||||
|
@ -35,7 +35,7 @@ If you are just looking to give the game a whirl, you can grab the latest releas
|
||||
|
||||
You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
|
||||
|
||||
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 unsupported or not listed above, there is still a chance you can run the release or manually build it by following the instructions below.
|
||||
|
||||
**For iOS/iPadOS users**: The iOS testflight link fills up very fast (Apple has a hard limit of 10,000 users). We reset it occasionally. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements. Our goal is to get the game on mobile app stores in early 2024.
|
||||
|
||||
@ -51,7 +51,7 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir
|
||||
|
||||
Please make sure you have the following prerequisites:
|
||||
|
||||
- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed.
|
||||
- A desktop platform with the [.NET 8.0 SDK](https://dotnet.microsoft.com/download) installed.
|
||||
|
||||
When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/), or [Visual Studio Code](https://code.visualstudio.com/) with the [EditorConfig](https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig) and [C#](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csharp) plugin installed.
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.205.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.223.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -92,9 +92,10 @@ namespace osu.Desktop
|
||||
return;
|
||||
}
|
||||
|
||||
if (status.Value == UserStatus.Online && activity.Value != null)
|
||||
if (activity.Value != null)
|
||||
{
|
||||
bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited;
|
||||
bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb;
|
||||
|
||||
presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation));
|
||||
presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty);
|
||||
|
||||
|
@ -7,6 +7,7 @@ using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
using Microsoft.Win32;
|
||||
using osu.Desktop.Performance;
|
||||
using osu.Desktop.Security;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game;
|
||||
@ -15,9 +16,11 @@ using osu.Framework;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Updater;
|
||||
using osu.Desktop.Windows;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.IPC;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Performance;
|
||||
using osu.Game.Utils;
|
||||
using SDL2;
|
||||
|
||||
@ -28,6 +31,9 @@ namespace osu.Desktop
|
||||
private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
|
||||
private ArchiveImportIPCChannel? archiveImportIPCChannel;
|
||||
|
||||
[Cached(typeof(IHighPerformanceSessionManager))]
|
||||
private readonly HighPerformanceSessionManager highPerformanceSessionManager = new HighPerformanceSessionManager();
|
||||
|
||||
public OsuGameDesktop(string[]? args = null)
|
||||
: base(args)
|
||||
{
|
||||
@ -86,8 +92,8 @@ namespace osu.Desktop
|
||||
[SupportedOSPlatform("windows")]
|
||||
private string? getStableInstallPathFromRegistry()
|
||||
{
|
||||
using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu"))
|
||||
return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||
using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu!"))
|
||||
return key?.OpenSubKey(WindowsAssociationManager.SHELL_OPEN_COMMAND)?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||
}
|
||||
|
||||
protected override UpdateManager CreateUpdateManager()
|
||||
|
43
osu.Desktop/Performance/HighPerformanceSessionManager.cs
Normal file
43
osu.Desktop/Performance/HighPerformanceSessionManager.cs
Normal file
@ -0,0 +1,43 @@
|
||||
// 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.Runtime;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Performance;
|
||||
|
||||
namespace osu.Desktop.Performance
|
||||
{
|
||||
public class HighPerformanceSessionManager : IHighPerformanceSessionManager
|
||||
{
|
||||
private GCLatencyMode originalGCMode;
|
||||
|
||||
public IDisposable BeginSession()
|
||||
{
|
||||
enableHighPerformanceSession();
|
||||
return new InvokeOnDisposal<HighPerformanceSessionManager>(this, static m => m.disableHighPerformanceSession());
|
||||
}
|
||||
|
||||
private void enableHighPerformanceSession()
|
||||
{
|
||||
Logger.Log("Starting high performance session");
|
||||
|
||||
originalGCMode = GCSettings.LatencyMode;
|
||||
GCSettings.LatencyMode = GCLatencyMode.LowLatency;
|
||||
|
||||
// Without doing this, the new GC mode won't kick in until the next GC, which could be at a more noticeable point in time.
|
||||
GC.Collect(0);
|
||||
}
|
||||
|
||||
private void disableHighPerformanceSession()
|
||||
{
|
||||
Logger.Log("Ending high performance session");
|
||||
|
||||
if (GCSettings.LatencyMode == GCLatencyMode.LowLatency)
|
||||
GCSettings.LatencyMode = originalGCMode;
|
||||
|
||||
// No GC.Collect() as we were already collecting at a higher frequency in the old mode.
|
||||
}
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using osu.Desktop.LegacyIpc;
|
||||
using osu.Desktop.Windows;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.Logging;
|
||||
@ -47,8 +48,8 @@ namespace osu.Desktop
|
||||
{
|
||||
var windowsVersion = Environment.OSVersion.Version;
|
||||
|
||||
// While .NET 6 still supports Windows 7 and above, we are limited by realm currently, as they choose to only support 8.1 and higher.
|
||||
// See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms
|
||||
// While .NET 8 only supports Windows 10 and above, running on Windows 7/8.1 may still work. We are limited by realm currently, as they choose to only support 8.1 and higher.
|
||||
// See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/
|
||||
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
|
||||
{
|
||||
// If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider
|
||||
@ -173,13 +174,16 @@ namespace osu.Desktop
|
||||
{
|
||||
tools.CreateShortcutForThisExe();
|
||||
tools.CreateUninstallerRegistryEntry();
|
||||
WindowsAssociationManager.InstallAssociations();
|
||||
}, onAppUpdate: (_, tools) =>
|
||||
{
|
||||
tools.CreateUninstallerRegistryEntry();
|
||||
WindowsAssociationManager.UpdateAssociations();
|
||||
}, onAppUninstall: (_, tools) =>
|
||||
{
|
||||
tools.RemoveShortcutForThisExe();
|
||||
tools.RemoveUninstallerRegistryEntry();
|
||||
WindowsAssociationManager.UninstallAssociations();
|
||||
}, onEveryRun: (_, _, _) =>
|
||||
{
|
||||
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
|
||||
|
17
osu.Desktop/Windows/Icons.cs
Normal file
17
osu.Desktop/Windows/Icons.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 System.IO;
|
||||
|
||||
namespace osu.Desktop.Windows
|
||||
{
|
||||
public static class Icons
|
||||
{
|
||||
/// <summary>
|
||||
/// Fully qualified path to the directory that contains icons (in the installation folder).
|
||||
/// </summary>
|
||||
private static readonly string icon_directory = Path.GetDirectoryName(typeof(Icons).Assembly.Location)!;
|
||||
|
||||
public static string Lazer => Path.Join(icon_directory, "lazer.ico");
|
||||
}
|
||||
}
|
292
osu.Desktop/Windows/WindowsAssociationManager.cs
Normal file
292
osu.Desktop/Windows/WindowsAssociationManager.cs
Normal file
@ -0,0 +1,292 @@
|
||||
// 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.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using Microsoft.Win32;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Desktop.Windows
|
||||
{
|
||||
[SupportedOSPlatform("windows")]
|
||||
public static class WindowsAssociationManager
|
||||
{
|
||||
private const string software_classes = @"Software\Classes";
|
||||
|
||||
/// <summary>
|
||||
/// Sub key for setting the icon.
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/com/defaulticon
|
||||
/// </summary>
|
||||
private const string default_icon = @"DefaultIcon";
|
||||
|
||||
/// <summary>
|
||||
/// Sub key for setting the command line that the shell invokes.
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/com/shell
|
||||
/// </summary>
|
||||
internal const string SHELL_OPEN_COMMAND = @"Shell\Open\Command";
|
||||
|
||||
private static readonly string exe_path = Path.ChangeExtension(typeof(WindowsAssociationManager).Assembly.Location, ".exe").Replace('/', '\\');
|
||||
|
||||
/// <summary>
|
||||
/// Program ID prefix used for file associations. Should be relatively short since the full program ID has a 39 character limit,
|
||||
/// see https://learn.microsoft.com/en-us/windows/win32/com/-progid--key.
|
||||
/// </summary>
|
||||
private const string program_id_prefix = "osu.File";
|
||||
|
||||
private static readonly FileAssociation[] file_associations =
|
||||
{
|
||||
new FileAssociation(@".osz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer),
|
||||
new FileAssociation(@".olz", WindowsAssociationManagerStrings.OsuBeatmap, Icons.Lazer),
|
||||
new FileAssociation(@".osr", WindowsAssociationManagerStrings.OsuReplay, Icons.Lazer),
|
||||
new FileAssociation(@".osk", WindowsAssociationManagerStrings.OsuSkin, Icons.Lazer),
|
||||
};
|
||||
|
||||
private static readonly UriAssociation[] uri_associations =
|
||||
{
|
||||
new UriAssociation(@"osu", WindowsAssociationManagerStrings.OsuProtocol, Icons.Lazer),
|
||||
new UriAssociation(@"osump", WindowsAssociationManagerStrings.OsuMultiplayer, Icons.Lazer),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Installs file and URI associations.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call <see cref="UpdateDescriptions"/> in a timely fashion to keep descriptions up-to-date and localised.
|
||||
/// </remarks>
|
||||
public static void InstallAssociations()
|
||||
{
|
||||
try
|
||||
{
|
||||
updateAssociations();
|
||||
updateDescriptions(null); // write default descriptions in case `UpdateDescriptions()` is not called.
|
||||
NotifyShellUpdate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, @$"Failed to install file and URI associations: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates associations with latest definitions.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Call <see cref="UpdateDescriptions"/> in a timely fashion to keep descriptions up-to-date and localised.
|
||||
/// </remarks>
|
||||
public static void UpdateAssociations()
|
||||
{
|
||||
try
|
||||
{
|
||||
updateAssociations();
|
||||
|
||||
// TODO: Remove once UpdateDescriptions() is called as specified in the xmldoc.
|
||||
updateDescriptions(null); // always write default descriptions, in case of updating from an older version in which file associations were not implemented/installed
|
||||
|
||||
NotifyShellUpdate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, @"Failed to update file and URI associations.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateDescriptions(LocalisationManager localisationManager)
|
||||
{
|
||||
try
|
||||
{
|
||||
updateDescriptions(localisationManager);
|
||||
NotifyShellUpdate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, @"Failed to update file and URI association descriptions.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void UninstallAssociations()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var association in file_associations)
|
||||
association.Uninstall();
|
||||
|
||||
foreach (var association in uri_associations)
|
||||
association.Uninstall();
|
||||
|
||||
NotifyShellUpdate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, @"Failed to uninstall file and URI associations.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void NotifyShellUpdate() => SHChangeNotify(EventId.SHCNE_ASSOCCHANGED, Flags.SHCNF_IDLIST, IntPtr.Zero, IntPtr.Zero);
|
||||
|
||||
/// <summary>
|
||||
/// Installs or updates associations.
|
||||
/// </summary>
|
||||
private static void updateAssociations()
|
||||
{
|
||||
foreach (var association in file_associations)
|
||||
association.Install();
|
||||
|
||||
foreach (var association in uri_associations)
|
||||
association.Install();
|
||||
}
|
||||
|
||||
private static void updateDescriptions(LocalisationManager? localisation)
|
||||
{
|
||||
foreach (var association in file_associations)
|
||||
association.UpdateDescription(getLocalisedString(association.Description));
|
||||
|
||||
foreach (var association in uri_associations)
|
||||
association.UpdateDescription(getLocalisedString(association.Description));
|
||||
|
||||
string getLocalisedString(LocalisableString s)
|
||||
{
|
||||
if (localisation == null)
|
||||
return s.ToString();
|
||||
|
||||
var b = localisation.GetLocalisedBindableString(s);
|
||||
b.UnbindAll();
|
||||
return b.Value;
|
||||
}
|
||||
}
|
||||
|
||||
#region Native interop
|
||||
|
||||
[DllImport("Shell32.dll")]
|
||||
private static extern void SHChangeNotify(EventId wEventId, Flags uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||
|
||||
private enum EventId
|
||||
{
|
||||
/// <summary>
|
||||
/// A file type association has changed. <see cref="Flags.SHCNF_IDLIST"/> must be specified in the uFlags parameter.
|
||||
/// dwItem1 and dwItem2 are not used and must be <see cref="IntPtr.Zero"/>. This event should also be sent for registered protocols.
|
||||
/// </summary>
|
||||
SHCNE_ASSOCCHANGED = 0x08000000
|
||||
}
|
||||
|
||||
private enum Flags : uint
|
||||
{
|
||||
SHCNF_IDLIST = 0x0000
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private record FileAssociation(string Extension, LocalisableString Description, string IconPath)
|
||||
{
|
||||
private string programId => $@"{program_id_prefix}{Extension}";
|
||||
|
||||
/// <summary>
|
||||
/// Installs a file extension association in accordance with https://learn.microsoft.com/en-us/windows/win32/com/-progid--key
|
||||
/// </summary>
|
||||
public void Install()
|
||||
{
|
||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||
if (classes == null) return;
|
||||
|
||||
// register a program id for the given extension
|
||||
using (var programKey = classes.CreateSubKey(programId))
|
||||
{
|
||||
using (var defaultIconKey = programKey.CreateSubKey(default_icon))
|
||||
defaultIconKey.SetValue(null, IconPath);
|
||||
|
||||
using (var openCommandKey = programKey.CreateSubKey(SHELL_OPEN_COMMAND))
|
||||
openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1""");
|
||||
}
|
||||
|
||||
using (var extensionKey = classes.CreateSubKey(Extension))
|
||||
{
|
||||
// set ourselves as the default program
|
||||
extensionKey.SetValue(null, programId);
|
||||
|
||||
// add to the open with dialog
|
||||
// https://learn.microsoft.com/en-us/windows/win32/shell/how-to-include-an-application-on-the-open-with-dialog-box
|
||||
using (var openWithKey = extensionKey.CreateSubKey(@"OpenWithProgIds"))
|
||||
openWithKey.SetValue(programId, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDescription(string description)
|
||||
{
|
||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||
if (classes == null) return;
|
||||
|
||||
using (var programKey = classes.OpenSubKey(programId, true))
|
||||
programKey?.SetValue(null, description);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uninstalls the file extension association in accordance with https://learn.microsoft.com/en-us/windows/win32/shell/fa-file-types#deleting-registry-information-during-uninstallation
|
||||
/// </summary>
|
||||
public void Uninstall()
|
||||
{
|
||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||
if (classes == null) return;
|
||||
|
||||
using (var extensionKey = classes.OpenSubKey(Extension, true))
|
||||
{
|
||||
// clear our default association so that Explorer doesn't show the raw programId to users
|
||||
// the null/(Default) entry is used for both ProdID association and as a fallback friendly name, for legacy reasons
|
||||
if (extensionKey?.GetValue(null) is string s && s == programId)
|
||||
extensionKey.SetValue(null, string.Empty);
|
||||
|
||||
using (var openWithKey = extensionKey?.CreateSubKey(@"OpenWithProgIds"))
|
||||
openWithKey?.DeleteValue(programId, throwOnMissingValue: false);
|
||||
}
|
||||
|
||||
classes.DeleteSubKeyTree(programId, throwOnMissingSubKey: false);
|
||||
}
|
||||
}
|
||||
|
||||
private record UriAssociation(string Protocol, LocalisableString Description, string IconPath)
|
||||
{
|
||||
/// <summary>
|
||||
/// "The <c>URL Protocol</c> string value indicates that this key declares a custom pluggable protocol handler."
|
||||
/// See https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85).
|
||||
/// </summary>
|
||||
public const string URL_PROTOCOL = @"URL Protocol";
|
||||
|
||||
/// <summary>
|
||||
/// Registers an URI protocol handler in accordance with https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/aa767914(v=vs.85).
|
||||
/// </summary>
|
||||
public void Install()
|
||||
{
|
||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||
if (classes == null) return;
|
||||
|
||||
using (var protocolKey = classes.CreateSubKey(Protocol))
|
||||
{
|
||||
protocolKey.SetValue(URL_PROTOCOL, string.Empty);
|
||||
|
||||
using (var defaultIconKey = protocolKey.CreateSubKey(default_icon))
|
||||
defaultIconKey.SetValue(null, IconPath);
|
||||
|
||||
using (var openCommandKey = protocolKey.CreateSubKey(SHELL_OPEN_COMMAND))
|
||||
openCommandKey.SetValue(null, $@"""{exe_path}"" ""%1""");
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDescription(string description)
|
||||
{
|
||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||
if (classes == null) return;
|
||||
|
||||
using (var protocolKey = classes.OpenSubKey(Protocol, true))
|
||||
protocolKey?.SetValue(null, $@"URL:{description}");
|
||||
}
|
||||
|
||||
public void Uninstall()
|
||||
{
|
||||
using var classes = Registry.CurrentUser.OpenSubKey(software_classes, true);
|
||||
classes?.DeleteSubKeyTree(Protocol, throwOnMissingSubKey: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 75 KiB |
@ -31,4 +31,7 @@
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Windows Icons">
|
||||
<Content Include="*.ico" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -20,6 +20,7 @@
|
||||
<file src="**.dll" target="lib\net45\"/>
|
||||
<file src="**.config" target="lib\net45\"/>
|
||||
<file src="**.json" target="lib\net45\"/>
|
||||
<file src="**.ico" target="lib\net45\"/>
|
||||
<file src="icon.png" target=""/>
|
||||
</files>
|
||||
</package>
|
||||
|
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
48
osu.Game.Benchmarks/BenchmarkStringComparison.cs
Normal file
@ -0,0 +1,48 @@
|
||||
// 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 BenchmarkDotNet.Attributes;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkStringComparison
|
||||
{
|
||||
private string[] strings = null!;
|
||||
|
||||
[GlobalSetup]
|
||||
public void GlobalSetUp()
|
||||
{
|
||||
strings = new string[10000];
|
||||
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
strings[i] = Guid.NewGuid().ToString();
|
||||
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
{
|
||||
if (i % 2 == 0)
|
||||
strings[i] = strings[i].ToUpperInvariant();
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void OrdinalIgnoreCase() => compare(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
[Benchmark]
|
||||
public void OrdinalSortByCase() => compare(OrdinalSortByCaseStringComparer.DEFAULT);
|
||||
|
||||
[Benchmark]
|
||||
public void InvariantCulture() => compare(StringComparer.InvariantCulture);
|
||||
|
||||
private void compare(IComparer<string> comparer)
|
||||
{
|
||||
for (int i = 0; i < strings.Length; ++i)
|
||||
{
|
||||
for (int j = i + 1; j < strings.Length; ++j)
|
||||
_ = comparer.Compare(strings[i], strings[j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -1,111 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Catch.Skinning;
|
||||
using osu.Game.Rulesets.Catch.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Visual;
|
||||
using Direction = osu.Game.Rulesets.Catch.UI.Direction;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests
|
||||
{
|
||||
public partial class TestSceneCatchSkinConfiguration : OsuTestScene
|
||||
{
|
||||
private Catcher catcher;
|
||||
|
||||
private readonly Container container;
|
||||
|
||||
public TestSceneCatchSkinConfiguration()
|
||||
{
|
||||
Add(container = new Container { RelativeSizeAxes = Axes.Both });
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestCatcherPlateFlipping(bool flip)
|
||||
{
|
||||
AddStep("setup catcher", () =>
|
||||
{
|
||||
var skin = new TestSkin { FlipCatcherPlate = flip };
|
||||
container.Child = new SkinProvidingContainer(skin)
|
||||
{
|
||||
Child = catcher = new Catcher(new DroppedObjectContainer())
|
||||
{
|
||||
Anchor = Anchor.Centre
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
Fruit fruit = new Fruit();
|
||||
|
||||
AddStep("catch fruit", () => catchFruit(fruit, 20));
|
||||
|
||||
float position = 0;
|
||||
|
||||
AddStep("record fruit position", () => position = getCaughtObjectPosition(fruit));
|
||||
|
||||
AddStep("face left", () => catcher.VisualDirection = Direction.Left);
|
||||
|
||||
if (flip)
|
||||
AddAssert("fruit position changed", () => !Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
|
||||
else
|
||||
AddAssert("fruit position unchanged", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
|
||||
|
||||
AddStep("face right", () => catcher.VisualDirection = Direction.Right);
|
||||
|
||||
AddAssert("fruit position restored", () => Precision.AlmostEquals(getCaughtObjectPosition(fruit), position));
|
||||
}
|
||||
|
||||
private float getCaughtObjectPosition(Fruit fruit)
|
||||
{
|
||||
var caughtObject = catcher.ChildrenOfType<CaughtObject>().Single(c => c.HitObject == fruit);
|
||||
return caughtObject.Parent!.ToSpaceOfOtherDrawable(caughtObject.Position, catcher).X;
|
||||
}
|
||||
|
||||
private void catchFruit(Fruit fruit, float x)
|
||||
{
|
||||
fruit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
var drawableFruit = new DrawableFruit(fruit) { X = x };
|
||||
var judgement = fruit.CreateJudgement();
|
||||
catcher.OnNewResult(drawableFruit, new CatchJudgementResult(fruit, judgement)
|
||||
{
|
||||
Type = judgement.MaxResult
|
||||
});
|
||||
}
|
||||
|
||||
private class TestSkin : TrianglesSkin
|
||||
{
|
||||
public bool FlipCatcherPlate { get; set; }
|
||||
|
||||
public TestSkin()
|
||||
: base(null!)
|
||||
{
|
||||
}
|
||||
|
||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
if (lookup is CatchSkinConfiguration config)
|
||||
{
|
||||
if (config == CatchSkinConfiguration.FlipCatcherPlate)
|
||||
return SkinUtils.As<TValue>(new Bindable<bool>(FlipCatcherPlate));
|
||||
}
|
||||
|
||||
return base.GetConfig<TLookup, TValue>(lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -293,7 +293,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
private JudgementResult createResult(CatchHitObject hitObject)
|
||||
{
|
||||
return new CatchJudgementResult(hitObject, hitObject.CreateJudgement())
|
||||
return new CatchJudgementResult(hitObject, hitObject.Judgement)
|
||||
{
|
||||
Type = catcher.CanCatch(hitObject) ? HitResult.Great : HitResult.Miss
|
||||
};
|
||||
|
@ -21,7 +21,6 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<CatchHitObject> drawableRuleset)
|
||||
{
|
||||
drawableRuleset.PlayfieldAdjustmentContainer.Scale = new Vector2(1, -1);
|
||||
drawableRuleset.PlayfieldAdjustmentContainer.Y = 1 - drawableRuleset.PlayfieldAdjustmentContainer.Y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +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.
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning
|
||||
{
|
||||
public enum CatchSkinConfiguration
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed.
|
||||
/// </summary>
|
||||
FlipCatcherPlate
|
||||
}
|
||||
}
|
@ -122,19 +122,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
result.Value = LegacyColourCompatibility.DisallowZeroAlpha(result.Value);
|
||||
return (IBindable<TValue>)result;
|
||||
|
||||
case CatchSkinConfiguration config:
|
||||
switch (config)
|
||||
{
|
||||
case CatchSkinConfiguration.FlipCatcherPlate:
|
||||
// Don't flip catcher plate contents if the catcher is provided by this legacy skin.
|
||||
if (GetDrawableComponent(new CatchSkinComponentLookup(CatchSkinComponents.Catcher)) != null)
|
||||
return (IBindable<TValue>)new Bindable<bool>();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return base.GetConfig<TLookup, TValue>(lookup);
|
||||
|
@ -17,24 +17,36 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
public CatchPlayfieldAdjustmentContainer()
|
||||
{
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
const float base_game_width = 1024f;
|
||||
const float base_game_height = 768f;
|
||||
|
||||
// playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable.
|
||||
// we can match that in lazer by using relative coordinates for Y and considering window height to be 1, and playfield height to be 0.8.
|
||||
RelativePositionAxes = Axes.Y;
|
||||
Y = (1 - playfield_size_adjust) / 4 * 3;
|
||||
// extra bottom space for the catcher to not get cut off at tall resolutions lower than 4:3 (e.g. 5:4). number chosen based on testing with maximum catcher scale (i.e. CS 0).
|
||||
const float extra_bottom_space = 200f;
|
||||
|
||||
Size = new Vector2(playfield_size_adjust);
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
// This container limits vertical visibility of the playfield to ensure fairness between wide and tall resolutions (i.e. tall resolutions should not see more fruits).
|
||||
// Note that the container still extends across the screen horizontally, so that hit explosions at the sides of the playfield do not get cut off.
|
||||
Name = "Visible area",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fit,
|
||||
FillAspectRatio = 4f / 3,
|
||||
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both, }
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = base_game_height + extra_bottom_space,
|
||||
Y = extra_bottom_space / 2,
|
||||
Masking = true,
|
||||
Child = new Container
|
||||
{
|
||||
Name = "Playable area",
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
// playfields in stable are positioned vertically at three fourths the difference between the playfield height and the window height in stable.
|
||||
Y = base_game_height * ((1 - playfield_size_adjust) / 4 * 3),
|
||||
Size = new Vector2(base_game_width, base_game_height) * playfield_size_adjust,
|
||||
Child = content = new ScalingContainer { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -112,11 +112,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
public Vector2 BodyScale => Scale * body.Scale;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the contents of the catcher plate should be visually flipped when the catcher direction is changed.
|
||||
/// </summary>
|
||||
private bool flipCatcherPlate;
|
||||
|
||||
/// <summary>
|
||||
/// Width of the area that can be used to attempt catches during gameplay.
|
||||
/// </summary>
|
||||
@ -339,8 +334,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
skin.GetConfig<CatchSkinColour, Color4>(CatchSkinColour.HyperDash)?.Value ??
|
||||
DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
flipCatcherPlate = skin.GetConfig<CatchSkinConfiguration, bool>(CatchSkinConfiguration.FlipCatcherPlate)?.Value ?? true;
|
||||
|
||||
runHyperDashStateTransition(HyperDashing);
|
||||
}
|
||||
|
||||
@ -352,8 +345,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
|
||||
body.Scale = scaleFromDirection;
|
||||
// Inverse of catcher scale is applied here, as catcher gets scaled by circle size and so do the incoming fruit.
|
||||
caughtObjectContainer.Scale = (1 / Scale.X) * (flipCatcherPlate ? scaleFromDirection : Vector2.One);
|
||||
hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
||||
caughtObjectContainer.Scale = new Vector2(1 / Scale.X);
|
||||
|
||||
// Correct overshooting.
|
||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Mania.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Mania.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Mania.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Mania.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -1,8 +1,18 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
@ -11,9 +21,80 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(0.1f)]
|
||||
[TestCase(0.7f)]
|
||||
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModFadeIn { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||
[Test]
|
||||
public void TestMinCoverageFullWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMinCoverageHalfWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
||||
});
|
||||
|
||||
AddStep("set playfield width to 0.5", () => Player.Width = 0.5f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaxCoverageFullWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
||||
});
|
||||
|
||||
AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaxCoverageHalfWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
||||
});
|
||||
|
||||
AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480);
|
||||
AddStep("set playfield width to 0.5", () => Player.Width = 0.5f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoCoverageDuringBreak()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(),
|
||||
Breaks = { new BreakPeriod(2000, 28000) }
|
||||
},
|
||||
PassCondition = () => Player.IsBreakTime.Value && checkCoverage(0)
|
||||
});
|
||||
}
|
||||
|
||||
private bool checkCoverage(float expected)
|
||||
{
|
||||
Drawable? cover = this.ChildrenOfType<PlayfieldCoveringWrapper>().FirstOrDefault();
|
||||
Drawable? filledArea = cover?.ChildrenOfType<Box>().LastOrDefault();
|
||||
|
||||
if (filledArea == null)
|
||||
return false;
|
||||
|
||||
float scale = cover!.DrawHeight / (768 - Stage.HIT_TARGET_POSITION);
|
||||
|
||||
// A bit of lenience because the test may end up hitting hitobjects before any assertions.
|
||||
return Precision.AlmostEquals(filledArea.DrawHeight / scale, expected, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,18 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
@ -11,9 +21,80 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
[TestCase(0.5f)]
|
||||
[TestCase(0.2f)]
|
||||
[TestCase(0.8f)]
|
||||
public void TestCoverage(float coverage) => CreateModTest(new ModTestData { Mod = new ManiaModHidden { Coverage = { Value = coverage } }, PassCondition = () => true });
|
||||
[Test]
|
||||
public void TestMinCoverageFullWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMinCoverageHalfWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MIN_COVERAGE)
|
||||
});
|
||||
|
||||
AddStep("set playfield width to 0.5", () => Player.Width = 0.5f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaxCoverageFullWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
||||
});
|
||||
|
||||
AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMaxCoverageHalfWidth()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
PassCondition = () => checkCoverage(ManiaModHidden.MAX_COVERAGE)
|
||||
});
|
||||
|
||||
AddStep("set combo to 480", () => Player.ScoreProcessor.Combo.Value = 480);
|
||||
AddStep("set playfield width to 0.5", () => Player.Width = 0.5f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoCoverageDuringBreak()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new ManiaModHidden(),
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = Enumerable.Range(1, 100).Select(i => (HitObject)new Note { StartTime = 1000 + 200 * i }).ToList(),
|
||||
Breaks = { new BreakPeriod(2000, 28000) }
|
||||
},
|
||||
PassCondition = () => Player.IsBreakTime.Value && checkCoverage(0)
|
||||
});
|
||||
}
|
||||
|
||||
private bool checkCoverage(float expected)
|
||||
{
|
||||
Drawable? cover = this.ChildrenOfType<PlayfieldCoveringWrapper>().FirstOrDefault();
|
||||
Drawable? filledArea = cover?.ChildrenOfType<Box>().LastOrDefault();
|
||||
|
||||
if (filledArea == null)
|
||||
return false;
|
||||
|
||||
float scale = cover!.DrawHeight / (768 - Stage.HIT_TARGET_POSITION);
|
||||
|
||||
// A bit of lenience because the test may end up hitting hitobjects before any assertions.
|
||||
return Precision.AlmostEquals(filledArea.DrawHeight / scale, expected, 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,18 +39,18 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public void TestScrollingDownwards()
|
||||
{
|
||||
AddStep("set down scroll", () => scrollingContainer.Direction = ScrollingDirection.Down);
|
||||
AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
|
||||
AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
|
||||
AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
|
||||
AddStep("set coverage = 0.5", () => cover.Coverage.Value = 0.5f);
|
||||
AddStep("set coverage = 0.8f", () => cover.Coverage.Value = 0.8f);
|
||||
AddStep("set coverage = 0.2f", () => cover.Coverage.Value = 0.2f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollingUpwards()
|
||||
{
|
||||
AddStep("set up scroll", () => scrollingContainer.Direction = ScrollingDirection.Up);
|
||||
AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
|
||||
AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
|
||||
AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
|
||||
AddStep("set coverage = 0.5", () => cover.Coverage.Value = 0.5f);
|
||||
AddStep("set coverage = 0.8f", () => cover.Coverage.Value = 0.8f);
|
||||
AddStep("set coverage = 0.2f", () => cover.Coverage.Value = 0.2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,16 +34,21 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("setup hierarchy", () => Child = new Container
|
||||
AddStep("setup hierarchy", () =>
|
||||
{
|
||||
Clock = new FramedClock(clock = new ManualClock()),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
Child = new Container
|
||||
{
|
||||
drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
|
||||
}
|
||||
Clock = new FramedClock(clock = new ManualClock()),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new[]
|
||||
{
|
||||
drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
|
||||
}
|
||||
};
|
||||
|
||||
drawableRuleset.AllowBackwardsSeeks = true;
|
||||
});
|
||||
AddStep("retrieve config bindable", () =>
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
@ -17,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
[Resolved]
|
||||
private Playfield playfield { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IScrollingInfo scrollingInfo { get; set; } = null!;
|
||||
|
||||
protected ScrollingHitObjectContainer HitObjectContainer => ((ManiaPlayfield)playfield).GetColumn(HitObject.Column).HitObjectContainer;
|
||||
|
||||
protected ManiaSelectionBlueprint(T hitObject)
|
||||
@ -28,14 +26,31 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
|
||||
RelativeSizeAxes = Axes.None;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
private readonly IBindable<ScrollingDirection> directionBindable = new Bindable<ScrollingDirection>();
|
||||
|
||||
var anchor = scrollingInfo.Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
directionBindable.BindTo(scrollingInfo.Direction);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
directionBindable.BindValueChanged(onDirectionChanged, true);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
var anchor = direction.NewValue == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre;
|
||||
Anchor = Origin = anchor;
|
||||
foreach (var child in InternalChildren)
|
||||
child.Anchor = child.Origin = anchor;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Position = Parent!.ToLocalSpace(HitObjectContainer.ScreenSpacePositionAtTime(HitObject.StartTime)) - AnchorPosition;
|
||||
Width = HitObjectContainer.DrawWidth;
|
||||
|
@ -247,7 +247,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
new ManiaModHardRock(),
|
||||
new MultiMod(new ManiaModSuddenDeath(), new ManiaModPerfect()),
|
||||
new MultiMod(new ManiaModDoubleTime(), new ManiaModNightcore()),
|
||||
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden()),
|
||||
new MultiMod(new ManiaModFadeIn(), new ManiaModHidden(), new ManiaModCover()),
|
||||
new ManiaModFlashlight(),
|
||||
new ModAccuracyChallenge(),
|
||||
};
|
||||
|
44
osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs
Normal file
44
osu.Game.Rulesets.Mania/Mods/ManiaModCover.cs
Normal file
@ -0,0 +1,44 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModCover : ManiaModWithPlayfieldCover
|
||||
{
|
||||
public override string Name => "Cover";
|
||||
public override string Acronym => "CO";
|
||||
|
||||
public override LocalisableString Description => @"Decrease the playfield's viewing area.";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
protected override CoverExpandDirection ExpandDirection => Direction.Value;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
||||
typeof(ManiaModHidden),
|
||||
typeof(ManiaModFadeIn)
|
||||
}).ToArray();
|
||||
|
||||
public override bool Ranked => false;
|
||||
|
||||
[SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")]
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0.2f,
|
||||
MaxValue = 0.8f,
|
||||
Default = 0.5f,
|
||||
};
|
||||
|
||||
[SettingSource("Direction", "The direction on which the cover is applied")]
|
||||
public Bindable<CoverExpandDirection> Direction { get; } = new Bindable<CoverExpandDirection>();
|
||||
}
|
||||
}
|
@ -3,29 +3,24 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModFadeIn : ManiaModPlayfieldCover
|
||||
public class ManiaModFadeIn : ManiaModHidden
|
||||
{
|
||||
public override string Name => "Fade In";
|
||||
public override string Acronym => "FI";
|
||||
public override LocalisableString Description => @"Keys appear out of nowhere!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModHidden)).ToArray();
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
||||
typeof(ManiaModHidden),
|
||||
typeof(ManiaModCover)
|
||||
}).ToArray();
|
||||
|
||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
|
||||
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0.1f,
|
||||
MaxValue = 0.7f,
|
||||
Default = 0.5f,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -3,27 +3,104 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Mania.Skinning;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModHidden : ManiaModPlayfieldCover
|
||||
public partial class ManiaModHidden : ManiaModWithPlayfieldCover, IApplicableToPlayer, IUpdatableByPlayfield
|
||||
{
|
||||
/// <summary>
|
||||
/// osu!stable is referenced to 768px.
|
||||
/// </summary>
|
||||
private const float reference_playfield_height = 768;
|
||||
|
||||
public const float MIN_COVERAGE = 160f;
|
||||
public const float MAX_COVERAGE = 400f;
|
||||
private const float coverage_increase_per_combo = 0.5f;
|
||||
|
||||
public override LocalisableString Description => @"Keys fade out before you hit them!";
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(0.5f)
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[]
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 0.2f,
|
||||
MaxValue = 0.8f,
|
||||
Default = 0.5f,
|
||||
};
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ManiaModFadeIn)).ToArray();
|
||||
typeof(ManiaModFadeIn),
|
||||
typeof(ManiaModCover)
|
||||
}).ToArray();
|
||||
|
||||
public override BindableNumber<float> Coverage { get; } = new BindableFloat(MIN_COVERAGE);
|
||||
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
|
||||
|
||||
private readonly IBindable<bool> isBreakTime = new Bindable<bool>();
|
||||
private readonly BindableInt combo = new BindableInt();
|
||||
|
||||
public override void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||
{
|
||||
base.ApplyToScoreProcessor(scoreProcessor);
|
||||
|
||||
combo.UnbindAll();
|
||||
combo.BindTo(scoreProcessor.Combo);
|
||||
}
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
{
|
||||
isBreakTime.UnbindAll();
|
||||
isBreakTime.BindTo(player.IsBreakTime);
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
Coverage.Value = isBreakTime.Value
|
||||
? 0
|
||||
: Math.Min(MAX_COVERAGE, MIN_COVERAGE + combo.Value * coverage_increase_per_combo) / reference_playfield_height;
|
||||
}
|
||||
|
||||
protected override PlayfieldCoveringWrapper CreateCover(Drawable content) => new LegacyPlayfieldCover(content);
|
||||
|
||||
private partial class LegacyPlayfieldCover : PlayfieldCoveringWrapper
|
||||
{
|
||||
[Resolved]
|
||||
private ISkinSource skin { get; set; } = null!;
|
||||
|
||||
private IBindable<float>? hitPosition;
|
||||
|
||||
public LegacyPlayfieldCover(Drawable content)
|
||||
: base(content)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
skin.SourceChanged += onSkinChanged;
|
||||
onSkinChanged();
|
||||
}
|
||||
|
||||
private void onSkinChanged()
|
||||
{
|
||||
hitPosition = skin.GetManiaSkinConfig<float>(LegacyManiaSkinConfigurationLookups.HitPosition);
|
||||
}
|
||||
|
||||
protected override float GetHeight(float coverage)
|
||||
{
|
||||
// In osu!stable, the cover is applied in absolute (x768) coordinates from the hit position.
|
||||
float availablePlayfieldHeight = Math.Abs(reference_playfield_height - (hitPosition?.Value ?? Stage.HIT_TARGET_POSITION));
|
||||
|
||||
if (availablePlayfieldHeight == 0)
|
||||
return base.GetHeight(coverage);
|
||||
|
||||
return base.GetHeight(coverage) * reference_playfield_height / availablePlayfieldHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -15,7 +14,7 @@ using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public abstract class ManiaModPlayfieldCover : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||
public abstract class ManiaModWithPlayfieldCover : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||
{
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
|
||||
|
||||
@ -24,7 +23,9 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
/// </summary>
|
||||
protected abstract CoverExpandDirection ExpandDirection { get; }
|
||||
|
||||
[SettingSource("Coverage", "The proportion of playfield height that notes will be hidden for.")]
|
||||
/// <summary>
|
||||
/// The relative area that should be completely covered. This does not include the fade.
|
||||
/// </summary>
|
||||
public abstract BindableNumber<float> Coverage { get; }
|
||||
|
||||
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||
@ -37,15 +38,17 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
Container hocParent = (Container)hoc.Parent!;
|
||||
|
||||
hocParent.Remove(hoc, false);
|
||||
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
|
||||
hocParent.Add(CreateCover(hoc).With(c =>
|
||||
{
|
||||
c.RelativeSizeAxes = Axes.Both;
|
||||
c.Direction = ExpandDirection;
|
||||
c.Coverage = Coverage.Value;
|
||||
c.Coverage.BindTo(Coverage);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual PlayfieldCoveringWrapper CreateCover(Drawable content) => new PlayfieldCoveringWrapper(content);
|
||||
|
||||
protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state)
|
||||
{
|
||||
}
|
@ -243,7 +243,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
|
||||
bodySprite.FillMode = FillMode.Stretch;
|
||||
// i dunno this looks about right??
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
||||
// the guard against zero draw height is intended for zero-length hold notes. yes, such cases have been spotted in the wild.
|
||||
if (sprite.DrawHeight > 0)
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
@ -149,7 +148,18 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <summary>
|
||||
/// Retrieves the total amount of columns across all stages in this playfield.
|
||||
/// </summary>
|
||||
public int TotalColumns => stages.Sum(s => s.Columns.Length);
|
||||
public int TotalColumns
|
||||
{
|
||||
get
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
foreach (var stage in stages)
|
||||
sum += stage.Columns.Length;
|
||||
|
||||
return sum;
|
||||
}
|
||||
}
|
||||
|
||||
private Stage getStageByColumn(int column)
|
||||
{
|
||||
|
@ -1,6 +1,8 @@
|
||||
// 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.ComponentModel;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -8,17 +10,24 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="Container"/> that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield.
|
||||
/// A <see cref="Framework.Graphics.Containers.Container"/> that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield.
|
||||
/// </summary>
|
||||
public partial class PlayfieldCoveringWrapper : CompositeDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The relative area that should be completely covered. This does not include the fade.
|
||||
/// </summary>
|
||||
public readonly BindableFloat Coverage = new BindableFloat();
|
||||
|
||||
/// <summary>
|
||||
/// The complete cover, including gradient and fill.
|
||||
/// </summary>
|
||||
@ -36,6 +45,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
private readonly IBindable<ScrollingDirection> scrollDirection = new Bindable<ScrollingDirection>();
|
||||
|
||||
private float currentCoverageHeight;
|
||||
|
||||
public PlayfieldCoveringWrapper(Drawable content)
|
||||
{
|
||||
InternalChild = new BufferedContainer
|
||||
@ -94,21 +105,46 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
scrollDirection.BindValueChanged(onScrollDirectionChanged, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateCoverSize(true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
updateCoverSize(false);
|
||||
}
|
||||
|
||||
private void updateCoverSize(bool instant)
|
||||
{
|
||||
float targetCoverage;
|
||||
float targetAlpha;
|
||||
|
||||
if (instant)
|
||||
{
|
||||
targetCoverage = Coverage.Value;
|
||||
targetAlpha = Coverage.Value > 0 ? 1 : 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetCoverage = (float)Interpolation.DampContinuously(currentCoverageHeight, Coverage.Value, 25, Math.Abs(Time.Elapsed));
|
||||
targetAlpha = (float)Interpolation.DampContinuously(gradient.Alpha, Coverage.Value > 0 ? 1 : 0, 25, Math.Abs(Time.Elapsed));
|
||||
}
|
||||
|
||||
filled.Height = GetHeight(targetCoverage);
|
||||
gradient.Y = -GetHeight(targetCoverage);
|
||||
gradient.Alpha = targetAlpha;
|
||||
|
||||
currentCoverageHeight = targetCoverage;
|
||||
}
|
||||
|
||||
protected virtual float GetHeight(float coverage) => coverage;
|
||||
|
||||
private void onScrollDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
=> cover.Rotation = direction.NewValue == ScrollingDirection.Up ? 0 : 180f;
|
||||
|
||||
/// <summary>
|
||||
/// The relative area that should be completely covered. This does not include the fade.
|
||||
/// </summary>
|
||||
public float Coverage
|
||||
{
|
||||
set
|
||||
{
|
||||
filled.Height = value;
|
||||
gradient.Y = -value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The direction in which the cover expands.
|
||||
/// </summary>
|
||||
@ -123,11 +159,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
/// <summary>
|
||||
/// The cover expands along the scrolling direction.
|
||||
/// </summary>
|
||||
[Description("Along scroll")]
|
||||
AlongScroll,
|
||||
|
||||
/// <summary>
|
||||
/// The cover expands against the scrolling direction.
|
||||
/// </summary>
|
||||
[Description("Against scroll")]
|
||||
AgainstScroll
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Osu.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Osu.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Osu.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Osu.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
@ -32,6 +33,27 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestComboBasedSize([Values] bool comboBasedSize) => CreateModTest(new ModTestData { Mod = new OsuModFlashlight { ComboBasedSize = { Value = comboBasedSize } }, PassCondition = () => true });
|
||||
|
||||
[Test]
|
||||
public void TestPlayfieldBasedSize()
|
||||
{
|
||||
ModFlashlight mod = new OsuModFlashlight();
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = mod,
|
||||
PassCondition = () =>
|
||||
{
|
||||
var flashlightOverlay = Player.DrawableRuleset.Overlays
|
||||
.OfType<ModFlashlight<OsuHitObject>.Flashlight>()
|
||||
.First();
|
||||
|
||||
return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize());
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("adjust playfield scale", () =>
|
||||
Player.DrawableRuleset.Playfield.Scale = new Vector2(.5f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderDimsOnlyAfterStartTime()
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
|
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
// multipled by 2 to nullify the score multiplier. (autoplay mod selected)
|
||||
long totalScore = scoreProcessor.TotalScore.Value * 2;
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().CreateJudgement().MaxResult);
|
||||
return totalScore == (int)(drawableSpinner.Result.TotalRotation / 360) * scoreProcessor.GetBaseScoreForResult(new SpinnerTick().Judgement.MaxResult);
|
||||
});
|
||||
|
||||
addSeekStep(0);
|
||||
|
@ -232,7 +232,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing
|
||||
|
||||
IList<HitObject> nestedObjects = slider.NestedHitObjects;
|
||||
|
||||
SliderTick? lastRealTick = slider.NestedHitObjects.OfType<SliderTick>().LastOrDefault();
|
||||
SliderTick? lastRealTick = null;
|
||||
|
||||
foreach (var hitobject in slider.NestedHitObjects)
|
||||
{
|
||||
if (hitobject is SliderTick tick)
|
||||
lastRealTick = tick;
|
||||
}
|
||||
|
||||
if (lastRealTick?.StartTime > trackingEndTime)
|
||||
{
|
||||
|
@ -78,9 +78,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
|
||||
Scale = new Vector2(hitObject.Scale);
|
||||
|
||||
if (hitObject is IHasComboInformation combo)
|
||||
ring.BorderColour = combo.GetComboColour(skin);
|
||||
|
||||
double editorTime = editorClock.CurrentTime;
|
||||
double hitObjectTime = hitObject.StartTime;
|
||||
bool hasReachedObject = editorTime >= hitObjectTime;
|
||||
@ -92,6 +89,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
|
||||
|
||||
ring.Scale = new Vector2(1 + 0.1f * ringScale);
|
||||
content.Alpha = 0.9f * (1 - alpha);
|
||||
|
||||
// TODO: should only update colour on skin/combo/object change.
|
||||
if (hitObject is IHasComboInformation combo && content.Alpha > 0)
|
||||
ring.BorderColour = combo.GetComboColour(skin);
|
||||
}
|
||||
else
|
||||
content.Alpha = 0;
|
||||
|
@ -416,8 +416,22 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
||||
DrawableObject.SliderBody?.ToScreenSpace(DrawableObject.SliderBody.PathEndOffset) ?? BodyPiece.ToScreenSpace(BodyPiece.PathEndLocation)
|
||||
};
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
BodyPiece.ReceivePositionalInputAt(screenSpacePos) || ControlPointVisualiser?.Pieces.Any(p => p.ReceivePositionalInputAt(screenSpacePos)) == true;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
if (BodyPiece.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
|
||||
if (ControlPointVisualiser == null)
|
||||
return false;
|
||||
|
||||
foreach (var p in ControlPointVisualiser.Pieces)
|
||||
{
|
||||
if (p.ReceivePositionalInputAt(screenSpacePos))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual SliderCircleOverlay CreateCircleOverlay(Slider slider, SliderPosition position) => new SliderCircleOverlay(slider, position);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
@ -38,12 +38,18 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
private ReplayState<OsuAction> state = null!;
|
||||
private double lastStateChangeTime;
|
||||
|
||||
private DrawableOsuRuleset ruleset = null!;
|
||||
private IPressHandler pressHandler = null!;
|
||||
|
||||
private bool hasReplay;
|
||||
private bool legacyReplay;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = (DrawableOsuRuleset)drawableRuleset;
|
||||
|
||||
// grab the input manager for future use.
|
||||
osuInputManager = ((DrawableOsuRuleset)drawableRuleset).KeyBindingInputManager;
|
||||
osuInputManager = ruleset.KeyBindingInputManager;
|
||||
}
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
@ -51,15 +57,22 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
if (osuInputManager.ReplayInputHandler != null)
|
||||
{
|
||||
hasReplay = true;
|
||||
|
||||
Debug.Assert(ruleset.ReplayScore != null);
|
||||
legacyReplay = ruleset.ReplayScore.ScoreInfo.IsLegacyScore;
|
||||
|
||||
pressHandler = legacyReplay ? new LegacyReplayPressHandler(this) : new PressHandler(this);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
pressHandler = new PressHandler(this);
|
||||
osuInputManager.AllowGameplayInputs = false;
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
if (hasReplay)
|
||||
if (hasReplay && !legacyReplay)
|
||||
return;
|
||||
|
||||
bool requiresHold = false;
|
||||
@ -132,11 +145,62 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
if (down)
|
||||
{
|
||||
state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton);
|
||||
pressHandler.HandlePress(wasLeft);
|
||||
wasLeft = !wasLeft;
|
||||
}
|
||||
else
|
||||
{
|
||||
pressHandler.HandleRelease(wasLeft);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.Apply(osuInputManager.CurrentState, osuInputManager);
|
||||
private interface IPressHandler
|
||||
{
|
||||
void HandlePress(bool wasLeft);
|
||||
void HandleRelease(bool wasLeft);
|
||||
}
|
||||
|
||||
private class PressHandler : IPressHandler
|
||||
{
|
||||
private readonly OsuModRelax mod;
|
||||
|
||||
public PressHandler(OsuModRelax mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public void HandlePress(bool wasLeft)
|
||||
{
|
||||
mod.state.PressedActions.Add(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton);
|
||||
mod.state.Apply(mod.osuInputManager.CurrentState, mod.osuInputManager);
|
||||
}
|
||||
|
||||
public void HandleRelease(bool wasLeft)
|
||||
{
|
||||
mod.state.Apply(mod.osuInputManager.CurrentState, mod.osuInputManager);
|
||||
}
|
||||
}
|
||||
|
||||
// legacy replays do not contain key-presses with Relax mod, so they need to be triggered by themselves.
|
||||
private class LegacyReplayPressHandler : IPressHandler
|
||||
{
|
||||
private readonly OsuModRelax mod;
|
||||
|
||||
public LegacyReplayPressHandler(OsuModRelax mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public void HandlePress(bool wasLeft)
|
||||
{
|
||||
mod.osuInputManager.KeyBindingContainer.TriggerPressed(wasLeft ? OsuAction.LeftButton : OsuAction.RightButton);
|
||||
}
|
||||
|
||||
public void HandleRelease(bool wasLeft)
|
||||
{
|
||||
// this intentionally releases right when `wasLeft` is true because `wasLeft` is set at point of press and not at point of release
|
||||
mod.osuInputManager.KeyBindingContainer.TriggerReleased(wasLeft ? OsuAction.RightButton : OsuAction.LeftButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,11 +80,29 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
base.UpdateInitialTransforms();
|
||||
|
||||
foreach (var piece in DimmablePieces)
|
||||
{
|
||||
// if the specified dimmable piece is a DHO, it is generally not safe to tack transforms onto it directly
|
||||
// as they may be cleared via the `updateState()` DHO flow,
|
||||
// so use `ApplyCustomUpdateState` instead. which does not have this pitfall.
|
||||
if (piece is DrawableHitObject drawableObjectPiece)
|
||||
{
|
||||
// this method can be called multiple times, and we don't want to subscribe to the event more than once,
|
||||
// so this is what it is going to have to be...
|
||||
drawableObjectPiece.ApplyCustomUpdateState -= applyDimToDrawableHitObject;
|
||||
drawableObjectPiece.ApplyCustomUpdateState += applyDimToDrawableHitObject;
|
||||
}
|
||||
else
|
||||
applyDim(piece);
|
||||
}
|
||||
|
||||
void applyDim(Drawable piece)
|
||||
{
|
||||
piece.FadeColour(new Color4(195, 195, 195, 255));
|
||||
using (piece.BeginDelayedSequence(InitialLifetimeOffset - OsuHitWindows.MISS_WINDOW))
|
||||
piece.FadeColour(Color4.White, 100);
|
||||
}
|
||||
|
||||
void applyDimToDrawableHitObject(DrawableHitObject dho, ArmedState _) => applyDim(dho);
|
||||
}
|
||||
|
||||
protected sealed override double InitialLifetimeOffset => HitObject.TimePreempt;
|
||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected override IEnumerable<Drawable> DimmablePieces => new Drawable[]
|
||||
{
|
||||
HeadCircle,
|
||||
// HeadCircle should not be added to this list, as it handles dimming itself
|
||||
TailCircle,
|
||||
repeatContainer,
|
||||
Body,
|
||||
|
@ -279,10 +279,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
if (HandleUserInput)
|
||||
{
|
||||
bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime;
|
||||
bool correctButtonPressed = (OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
|
||||
|
||||
RotationTracker.Tracking = !Result.HasResult
|
||||
&& correctButtonPressed
|
||||
&& correctButtonPressed()
|
||||
&& isValidSpinningTime;
|
||||
}
|
||||
|
||||
@ -292,11 +291,34 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
// Ticks can theoretically be judged at any point in the spinner's duration.
|
||||
// A tick must be alive to correctly play back samples,
|
||||
// but for performance reasons, we only want to keep the next tick alive.
|
||||
var next = NestedHitObjects.FirstOrDefault(h => !h.Judged);
|
||||
DrawableHitObject nextTick = null;
|
||||
|
||||
foreach (var nested in NestedHitObjects)
|
||||
{
|
||||
if (!nested.Judged)
|
||||
{
|
||||
nextTick = nested;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// See default `LifetimeStart` as set in `DrawableSpinnerTick`.
|
||||
if (next?.LifetimeStart == double.MaxValue)
|
||||
next.LifetimeStart = HitObject.StartTime;
|
||||
if (nextTick?.LifetimeStart == double.MaxValue)
|
||||
nextTick.LifetimeStart = HitObject.StartTime;
|
||||
}
|
||||
|
||||
private bool correctButtonPressed()
|
||||
{
|
||||
if (OsuActionInputManager == null)
|
||||
return false;
|
||||
|
||||
foreach (var action in OsuActionInputManager.PressedActions)
|
||||
{
|
||||
if (action == OsuAction.LeftButton || action == OsuAction.RightButton)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
@ -149,8 +148,11 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
StackHeightBindable.BindValueChanged(height =>
|
||||
{
|
||||
foreach (var nested in NestedHitObjects.OfType<OsuHitObject>())
|
||||
nested.StackHeight = height.NewValue;
|
||||
foreach (var nested in NestedHitObjects)
|
||||
{
|
||||
if (nested is OsuHitObject osuHitObject)
|
||||
osuHitObject.StackHeight = height.NewValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -252,18 +252,25 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
|
||||
protected void UpdateNestedSamples()
|
||||
{
|
||||
var firstSample = Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)
|
||||
?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||
var sampleList = new List<HitSampleInfo>();
|
||||
// TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933)
|
||||
HitSampleInfo tickSample = (Samples.FirstOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault())?.With("slidertick");
|
||||
|
||||
if (firstSample != null)
|
||||
sampleList.Add(firstSample.With("slidertick"));
|
||||
foreach (var nested in NestedHitObjects)
|
||||
{
|
||||
switch (nested)
|
||||
{
|
||||
case SliderTick tick:
|
||||
tick.SamplesBindable.Clear();
|
||||
|
||||
foreach (var tick in NestedHitObjects.OfType<SliderTick>())
|
||||
tick.Samples = sampleList;
|
||||
if (tickSample != null)
|
||||
tick.SamplesBindable.Add(tickSample);
|
||||
break;
|
||||
|
||||
foreach (var repeat in NestedHitObjects.OfType<SliderRepeat>())
|
||||
repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1);
|
||||
case SliderRepeat repeat:
|
||||
repeat.Samples = this.GetNodeSamples(repeat.RepeatIndex + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (HeadCircle != null)
|
||||
HeadCircle.Samples = this.GetNodeSamples(0);
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
{
|
||||
public partial class OsuInputManager : RulesetInputManager<OsuAction>
|
||||
{
|
||||
public IEnumerable<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
public SlimReadOnlyListWrapper<OsuAction> PressedActions => KeyBindingContainer.PressedActions;
|
||||
|
||||
/// <summary>
|
||||
/// Whether gameplay input buttons should be allowed.
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Globalization;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
@ -111,42 +110,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||
}, true);
|
||||
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||
fadeCounterOnTimeStart();
|
||||
updateSpmAlpha();
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
fadeCounterOnTimeStart();
|
||||
}
|
||||
|
||||
private void fadeCounterOnTimeStart()
|
||||
private void updateSpmAlpha()
|
||||
{
|
||||
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||
{
|
||||
using (BeginAbsoluteSequence(startTime))
|
||||
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableSpinner.IsNotNull())
|
||||
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1);
|
||||
else
|
||||
spmContainer.Alpha = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,8 +25,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
Texture = textures.Get(@"Gameplay/osu/approachcircle").WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||
|
||||
// account for the sprite being used for the default approach circle being taken from stable,
|
||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||
// In triangles and argon skins, we expanded hitcircles to take up the full 128 px which are clickable,
|
||||
// but still use the old approach circle sprite. To make it feel correct (ie. disappear as it collides
|
||||
// with the hitcircle, *not when it overlaps the border*) we need to expand it slightly.
|
||||
Scale = new Vector2(128 / 118f);
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Globalization;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
@ -117,42 +116,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
{
|
||||
spmCounter.Text = Math.Truncate(spm.NewValue).ToString(@"#0");
|
||||
}, true);
|
||||
|
||||
drawableSpinner.ApplyCustomUpdateState += updateStateTransforms;
|
||||
updateStateTransforms(drawableSpinner, drawableSpinner.State.Value);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!spmContainer.IsPresent && drawableSpinner.Result?.TimeStarted != null)
|
||||
fadeCounterOnTimeStart();
|
||||
updateSpmAlpha();
|
||||
}
|
||||
|
||||
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
|
||||
{
|
||||
if (!(drawableHitObject is DrawableSpinner))
|
||||
return;
|
||||
|
||||
fadeCounterOnTimeStart();
|
||||
}
|
||||
|
||||
private void fadeCounterOnTimeStart()
|
||||
private void updateSpmAlpha()
|
||||
{
|
||||
if (drawableSpinner.Result?.TimeStarted is double startTime)
|
||||
{
|
||||
using (BeginAbsoluteSequence(startTime))
|
||||
spmContainer.FadeIn(drawableSpinner.HitObject.TimeFadeIn);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableSpinner.IsNotNull())
|
||||
drawableSpinner.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
spmContainer.Alpha = (float)Math.Clamp((Clock.CurrentTime - startTime) / drawableSpinner.HitObject.TimeFadeIn, 0, 1);
|
||||
else
|
||||
spmContainer.Alpha = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
SnakingOut.BindTo(configSnakingOut);
|
||||
|
||||
BorderSize = skin.GetConfig<OsuSkinConfiguration, float>(OsuSkinConfiguration.SliderBorderSize)?.Value ?? 1;
|
||||
BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@ -33,14 +32,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
drawableSpinner.HitObjectApplied += resetState;
|
||||
}
|
||||
|
||||
private RotationRecord lastRecord;
|
||||
|
||||
public void SetRotation(float currentRotation)
|
||||
{
|
||||
// If we've gone back in time, it's fine to work with a fresh set of records for now
|
||||
if (records.Count > 0 && Time.Current < records.Last().Time)
|
||||
if (records.Count > 0 && Time.Current < lastRecord.Time)
|
||||
records.Clear();
|
||||
|
||||
// Never calculate SPM by same time of record to avoid 0 / 0 = NaN or X / 0 = Infinity result.
|
||||
if (records.Count > 0 && Precision.AlmostEquals(Time.Current, records.Last().Time))
|
||||
if (records.Count > 0 && Precision.AlmostEquals(Time.Current, lastRecord.Time))
|
||||
return;
|
||||
|
||||
if (records.Count > 0)
|
||||
@ -52,11 +53,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
result.Value = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
|
||||
}
|
||||
|
||||
records.Enqueue(new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||
records.Enqueue(lastRecord = new RotationRecord { Rotation = currentRotation, Time = Time.Current });
|
||||
}
|
||||
|
||||
private void resetState(DrawableHitObject hitObject)
|
||||
{
|
||||
lastRecord = default;
|
||||
result.Value = 0;
|
||||
records.Clear();
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
@ -26,10 +25,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
var texture = skin.GetTexture(@"approachcircle");
|
||||
Debug.Assert(texture != null);
|
||||
Texture = texture.WithMaximumSize(OsuHitObject.OBJECT_DIMENSIONS * 2);
|
||||
|
||||
// account for the sprite being used for the default approach circle being taken from stable,
|
||||
// when hitcircles have 5px padding on each size. this should be removed if we update the sprite.
|
||||
Scale = new Vector2(128 / 118f);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
// Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour.
|
||||
// This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this).
|
||||
this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
this.ScaleTo(1f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out)
|
||||
.FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime));
|
||||
}
|
||||
|
||||
|
@ -92,8 +92,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
|
||||
this.FadeOut();
|
||||
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn / 2);
|
||||
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
|
||||
this.FadeInFromZero(spinner.TimeFadeIn);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -23,29 +23,35 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
private partial class LegacyDrawableSliderPath : DrawableSliderPath
|
||||
{
|
||||
private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
|
||||
|
||||
protected new float CalculatedBorderPortion
|
||||
// Roughly matches osu!stable's slider border portions.
|
||||
=> base.CalculatedBorderPortion * 0.77f;
|
||||
|
||||
protected override Color4 ColourAt(float position)
|
||||
{
|
||||
float realBorderPortion = shadow_portion + CalculatedBorderPortion;
|
||||
float realGradientPortion = 1 - realBorderPortion;
|
||||
|
||||
if (position <= shadow_portion)
|
||||
return new Color4(0f, 0f, 0f, 0.25f * position / shadow_portion);
|
||||
|
||||
if (position <= realBorderPortion)
|
||||
return BorderColour;
|
||||
|
||||
position -= realBorderPortion;
|
||||
// https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Renderers/MmSliderRendererGL.cs#L99
|
||||
// float aaWidth = Math.Min(Math.Max(0.5f / PathRadius, 3.0f / 256.0f), 1.0f / 16.0f);
|
||||
// applying the aa_width constant from stable makes sliders blurry, especially on CS>5. set to zero for now.
|
||||
// this might be related to SmoothPath applying AA internally, but disabling that does not seem to have much of an effect.
|
||||
const float aa_width = 0f;
|
||||
|
||||
Color4 shadow = new Color4(0, 0, 0, 0.25f);
|
||||
Color4 outerColour = AccentColour.Darken(0.1f);
|
||||
Color4 innerColour = lighten(AccentColour, 0.5f);
|
||||
|
||||
return LegacyUtils.InterpolateNonLinear(position / realGradientPortion, outerColour, innerColour, 0, 1);
|
||||
// https://github.com/peppy/osu-stable-reference/blob/3ea48705eb67172c430371dcfc8a16a002ed0d3d/osu!/Graphics/Renderers/MmSliderRendererGL.cs#L59-L70
|
||||
const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
|
||||
const float border_portion = 0.1875f;
|
||||
|
||||
if (position <= shadow_portion - aa_width)
|
||||
return LegacyUtils.InterpolateNonLinear(position, Color4.Black.Opacity(0f), shadow, 0, shadow_portion - aa_width);
|
||||
|
||||
if (position <= shadow_portion + aa_width)
|
||||
return LegacyUtils.InterpolateNonLinear(position, shadow, BorderColour, shadow_portion - aa_width, shadow_portion + aa_width);
|
||||
|
||||
if (position <= border_portion - aa_width)
|
||||
return BorderColour;
|
||||
|
||||
if (position <= border_portion + aa_width)
|
||||
return LegacyUtils.InterpolateNonLinear(position, BorderColour, outerColour, border_portion - aa_width, border_portion + aa_width);
|
||||
|
||||
return LegacyUtils.InterpolateNonLinear(position, outerColour, innerColour, border_portion + aa_width, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -5,7 +5,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
{
|
||||
public enum OsuSkinConfiguration
|
||||
{
|
||||
SliderBorderSize,
|
||||
SliderPathRadius,
|
||||
CursorCentre,
|
||||
CursorExpand,
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -228,7 +227,9 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer());
|
||||
|
||||
points.Clear();
|
||||
points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex));
|
||||
|
||||
for (int i = firstVisiblePointIndex; i < futurePointIndex; i++)
|
||||
points.Add(Source.SmokePoints[i]);
|
||||
}
|
||||
|
||||
protected sealed override void Draw(IRenderer renderer)
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
|
||||
@ -39,6 +40,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||
{
|
||||
if ((Clock as IGameplayClock)?.IsRewinding == true)
|
||||
return false;
|
||||
|
||||
if (showRipples.Value)
|
||||
{
|
||||
AddInternal(ripplePool.Get(r =>
|
||||
|
@ -7,7 +7,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Debug/net6.0/osu.Game.Rulesets.Taiko.Tests.dll"
|
||||
"${workspaceRoot}/bin/Debug/net8.0/osu.Game.Rulesets.Taiko.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Debug)",
|
||||
@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"program": "dotnet",
|
||||
"args": [
|
||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Taiko.Tests.dll"
|
||||
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Taiko.Tests.dll"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"preLaunchTask": "Build (Release)",
|
||||
|
@ -126,11 +126,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
foreach (var nested in beatmap.HitObjects[0].NestedHitObjects)
|
||||
{
|
||||
var nestedJudgement = nested.CreateJudgement();
|
||||
var nestedJudgement = nested.Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult });
|
||||
}
|
||||
|
||||
var judgement = beatmap.HitObjects[0].CreateJudgement();
|
||||
var judgement = beatmap.HitObjects[0].Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult });
|
||||
|
||||
Assert.Multiple(() =>
|
||||
@ -159,11 +159,11 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
foreach (var nested in beatmap.HitObjects[0].NestedHitObjects)
|
||||
{
|
||||
var nestedJudgement = nested.CreateJudgement();
|
||||
var nestedJudgement = nested.Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(nested, nestedJudgement) { Type = nestedJudgement.MaxResult });
|
||||
}
|
||||
|
||||
var judgement = beatmap.HitObjects[0].CreateJudgement();
|
||||
var judgement = beatmap.HitObjects[0].Judgement;
|
||||
healthProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], judgement) { Type = judgement.MaxResult });
|
||||
|
||||
Assert.Multiple(() =>
|
||||
|
@ -0,0 +1,57 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Skinning.Legacy;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
public partial class TestSceneTaikoPlayerScroller : LegacySkinPlayerTestScene
|
||||
{
|
||||
private Storyboard? currentStoryboard;
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
[Test]
|
||||
public void TestForegroundSpritesHidesScroller()
|
||||
{
|
||||
AddStep("load storyboard", () =>
|
||||
{
|
||||
currentStoryboard = new Storyboard();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
currentStoryboard.GetLayer("Foreground").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero));
|
||||
});
|
||||
|
||||
CreateTest();
|
||||
AddAssert("taiko scroller not present", () => !this.ChildrenOfType<LegacyTaikoScroller>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOverlaySpritesKeepsScroller()
|
||||
{
|
||||
AddStep("load storyboard", () =>
|
||||
{
|
||||
currentStoryboard = new Storyboard();
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
currentStoryboard.GetLayer("Overlay").Add(new StoryboardSprite($"test{i}", Anchor.Centre, Vector2.Zero));
|
||||
});
|
||||
|
||||
CreateTest();
|
||||
AddAssert("taiko scroller present", () => this.ChildrenOfType<LegacyTaikoScroller>().Single().IsPresent);
|
||||
}
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset();
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||
=> base.CreateWorkingBeatmap(beatmap, currentStoryboard ?? storyboard);
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@ -22,7 +24,9 @@ using osu.Game.Rulesets.Timing;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.UI
|
||||
@ -39,6 +43,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
protected override bool UserScrollSpeedAdjustment => false;
|
||||
|
||||
[CanBeNull]
|
||||
private SkinnableDrawable scroller;
|
||||
|
||||
public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
@ -48,16 +53,24 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
VisualisationMethod = ScrollVisualisationMethod.Overlapping;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] GameplayState gameplayState)
|
||||
{
|
||||
new BarLineGenerator<BarLine>(Beatmap).BarLines.ForEach(bar => Playfield.Add(bar));
|
||||
|
||||
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
var spriteElements = gameplayState?.Storyboard.Layers.Where(l => l.Name != @"Overlay")
|
||||
.SelectMany(l => l.Elements)
|
||||
.OfType<StoryboardSprite>()
|
||||
.DistinctBy(e => e.Path) ?? Enumerable.Empty<StoryboardSprite>();
|
||||
|
||||
if (spriteElements.Count() < 10)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MaxValue
|
||||
});
|
||||
FrameStableComponents.Add(scroller = new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.Scroller), _ => Empty())
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Depth = float.MaxValue,
|
||||
});
|
||||
}
|
||||
|
||||
KeyBindingInputManager.Add(new DrumTouchInputArea());
|
||||
}
|
||||
@ -76,7 +89,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
var playfieldScreen = Playfield.ScreenSpaceDrawQuad;
|
||||
scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y;
|
||||
|
||||
if (scroller != null)
|
||||
scroller.Height = ToLocalSpace(playfieldScreen.TopLeft + new Vector2(0, playfieldScreen.Height / 20)).Y;
|
||||
}
|
||||
|
||||
public MultiplierControlPoint ControlPointAt(double time)
|
||||
|
@ -31,4 +31,22 @@
|
||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
</ItemGroup>
|
||||
<!-- osu.Framework.Android depends on https://www.nuget.org/packages/Xamarin.AndroidX.Window,
|
||||
which - via a chain of transitive dependencies - also includes https://www.nuget.org/packages/Xamarin.Jetbrains.Annotations,
|
||||
which causes compile failures such as:
|
||||
|
||||
The type 'NotNullAttribute' exists in both 'JetBrains.Annotations, Version=4242.42.42.42, Culture=neutral, PublicKeyToken=1010a0d8d6380325'
|
||||
and 'Xamarin.Jetbrains.Annotations, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' [D:\a\osu\osu\osu.Game.Tests.Android\osu.Game.Tests.Android.csproj]
|
||||
|
||||
We cannot easily change the source files, because of how this project works
|
||||
(all source files are basically symlinked from the desktop test project,
|
||||
so changing anything there just for the sake of mobile would be strange).
|
||||
Thus, apply the following "interesting" workaround as borrowed from https://stackoverflow.com/a/65127159 instead. -->
|
||||
<Target Name="AddPackageAliases" BeforeTargets="FindReferenceAssembliesForReferences;ResolveReferences">
|
||||
<ItemGroup>
|
||||
<ReferencePath Condition="%(Filename) == 'Xamarin.Jetbrains.Annotations'">
|
||||
<Aliases>XamarinJetbrainsAnnotations</Aliases>
|
||||
</ReferencePath>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
@ -17,6 +18,7 @@ using osu.Game.Rulesets.Osu.Edit;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Editing
|
||||
{
|
||||
@ -228,6 +230,28 @@ namespace osu.Game.Tests.Editing
|
||||
assertSnappedDistance(400, 400);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUseCurrentSnap()
|
||||
{
|
||||
AddStep("add objects to beatmap", () =>
|
||||
{
|
||||
editorBeatmap.Add(new HitCircle { StartTime = 1000 });
|
||||
editorBeatmap.Add(new HitCircle { Position = new Vector2(100), StartTime = 2000 });
|
||||
});
|
||||
|
||||
AddStep("hover use current snap button", () => InputManager.MoveMouseTo(composer.ChildrenOfType<ExpandableButton>().Single()));
|
||||
AddUntilStep("use current snap expanded", () => composer.ChildrenOfType<ExpandableButton>().Single().Expanded.Value, () => Is.True);
|
||||
|
||||
AddStep("seek before first object", () => EditorClock.Seek(0));
|
||||
AddUntilStep("use current snap not available", () => composer.ChildrenOfType<ExpandableButton>().Single().Enabled.Value, () => Is.False);
|
||||
|
||||
AddStep("seek to between objects", () => EditorClock.Seek(1500));
|
||||
AddUntilStep("use current snap available", () => composer.ChildrenOfType<ExpandableButton>().Single().Enabled.Value, () => Is.True);
|
||||
|
||||
AddStep("seek after last object", () => EditorClock.Seek(2500));
|
||||
AddUntilStep("use current snap not available", () => composer.ChildrenOfType<ExpandableButton>().Single().Enabled.Value, () => Is.False);
|
||||
}
|
||||
|
||||
private void assertSnapDistance(float expectedDistance, HitObject? referenceObject, bool includeSliderVelocity)
|
||||
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistanceAt(referenceObject ?? new HitObject(), includeSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
|
@ -175,7 +175,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
var hitObject = new HitObject { StartTime = Time.Current };
|
||||
lifetimeEntry = new HitObjectLifetimeEntry(hitObject)
|
||||
{
|
||||
Result = new JudgementResult(hitObject, hitObject.CreateJudgement())
|
||||
Result = new JudgementResult(hitObject, hitObject.Judgement)
|
||||
{
|
||||
Type = HitResult.Great
|
||||
}
|
||||
|
@ -129,10 +129,10 @@ namespace osu.Game.Tests.Gameplay
|
||||
var scoreProcessor = new ScoreProcessor(new OsuRuleset());
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.LargeTickHit });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].Judgement) { Type = HitResult.SmallTickMiss });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].Judgement) { Type = HitResult.SmallBonus });
|
||||
|
||||
var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo };
|
||||
scoreProcessor.FailScore(score);
|
||||
@ -169,8 +169,8 @@ namespace osu.Game.Tests.Gameplay
|
||||
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0));
|
||||
Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1));
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Ok });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.Great });
|
||||
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo((double)(100 + 300) / (2 * 300)).Within(Precision.DOUBLE_EPSILON));
|
||||
Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo((double)(100 + 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON));
|
||||
|
@ -454,5 +454,111 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly object[] correct_date_query_examples =
|
||||
{
|
||||
new object[] { "600" },
|
||||
new object[] { "0.5s" },
|
||||
new object[] { "120m" },
|
||||
new object[] { "48h120s" },
|
||||
new object[] { "10y24M" },
|
||||
new object[] { "10y60d120s" },
|
||||
new object[] { "0y0M2d" },
|
||||
new object[] { "1y1M2d" }
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(correct_date_query_examples))]
|
||||
public void TestValidDateQueries(string dateQuery)
|
||||
{
|
||||
string query = $"played<{dateQuery} time";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||
}
|
||||
|
||||
private static readonly object[] incorrect_date_query_examples =
|
||||
{
|
||||
new object[] { ".5s" },
|
||||
new object[] { "7m27" },
|
||||
new object[] { "7m7m7m" },
|
||||
new object[] { "5s6m" },
|
||||
new object[] { "7d7y" },
|
||||
new object[] { "0:3:6" },
|
||||
new object[] { "0:3:" },
|
||||
new object[] { "\"three days\"" },
|
||||
new object[] { "0.1y0.1M2d" },
|
||||
new object[] { "0.99y0.99M2d" },
|
||||
new object[] { string.Empty }
|
||||
};
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(incorrect_date_query_examples))]
|
||||
public void TestInvalidDateQueries(string dateQuery)
|
||||
{
|
||||
string query = $"played<{dateQuery} time";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGreaterDateQuery()
|
||||
{
|
||||
const string query = "played>50";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
||||
Assert.That(filterCriteria.LastPlayed.Min, Is.Null);
|
||||
// the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
|
||||
// (irrelevant in proportion to the actual filter proscribed).
|
||||
Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLowerDateQuery()
|
||||
{
|
||||
const string query = "played<50";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.LastPlayed.Max, Is.Null);
|
||||
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
||||
// the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
|
||||
// (irrelevant in proportion to the actual filter proscribed).
|
||||
Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddDays(-50)).Within(TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBothSidesDateQuery()
|
||||
{
|
||||
const string query = "played>3M played<1y6M";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.That(filterCriteria.LastPlayed.Min, Is.Not.Null);
|
||||
Assert.That(filterCriteria.LastPlayed.Max, Is.Not.Null);
|
||||
// the parser internally references `DateTimeOffset.Now`, so to not make things too annoying for tests, just assume some tolerance
|
||||
// (irrelevant in proportion to the actual filter proscribed).
|
||||
Assert.That(filterCriteria.LastPlayed.Min, Is.EqualTo(DateTimeOffset.Now.AddMonths(-6).AddYears(-1)).Within(TimeSpan.FromSeconds(5)));
|
||||
Assert.That(filterCriteria.LastPlayed.Max, Is.EqualTo(DateTimeOffset.Now.AddMonths(-3)).Within(TimeSpan.FromSeconds(5)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEqualDateQuery()
|
||||
{
|
||||
const string query = "played=50";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(false, filterCriteria.LastPlayed.HasFilter);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOutOfRangeDateQuery()
|
||||
{
|
||||
const string query = "played<10000y";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(true, filterCriteria.LastPlayed.HasFilter);
|
||||
Assert.AreEqual(DateTimeOffset.MinValue.AddMilliseconds(1), filterCriteria.LastPlayed.Min);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
public override Container FrameStableComponents { get; }
|
||||
public override IFrameStableClock FrameStableClock { get; }
|
||||
internal override bool FrameStablePlayback { get; set; }
|
||||
public override bool AllowBackwardsSeeks { get; set; }
|
||||
public override IReadOnlyList<Mod> Mods { get; }
|
||||
|
||||
public override double GameplayStartTime { get; }
|
||||
|
@ -112,7 +112,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], fourObjectBeatmap.HitObjects[i].CreateJudgement())
|
||||
var judgementResult = new JudgementResult(fourObjectBeatmap.HitObjects[i], fourObjectBeatmap.HitObjects[i].Judgement)
|
||||
{
|
||||
Type = i == 2 ? minResult : hitResult
|
||||
};
|
||||
@ -141,7 +141,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
|
||||
for (int i = 0; i < object_count; ++i)
|
||||
{
|
||||
var judgementResult = new JudgementResult(largeBeatmap.HitObjects[i], largeBeatmap.HitObjects[i].CreateJudgement())
|
||||
var judgementResult = new JudgementResult(largeBeatmap.HitObjects[i], largeBeatmap.HitObjects[i].Judgement)
|
||||
{
|
||||
Type = HitResult.Great
|
||||
};
|
||||
@ -325,11 +325,11 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
scoreProcessor = new TestScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(beatmap);
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Great });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].Judgement) { Type = HitResult.Great });
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
||||
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.ComboBreak });
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].Judgement) { Type = HitResult.ComboBreak });
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||
Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1));
|
||||
}
|
||||
@ -350,7 +350,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
|
||||
for (int i = 0; i < beatmap.HitObjects.Count; i++)
|
||||
{
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], beatmap.HitObjects[i].CreateJudgement())
|
||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[i], beatmap.HitObjects[i].Judgement)
|
||||
{
|
||||
Type = i == 0 ? HitResult.Miss : HitResult.Great
|
||||
});
|
||||
@ -441,10 +441,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
private readonly HitResult maxResult;
|
||||
private readonly HitResult? minResult;
|
||||
|
||||
public override Judgement CreateJudgement()
|
||||
{
|
||||
return new TestJudgement(maxResult, minResult);
|
||||
}
|
||||
public override Judgement CreateJudgement() => new TestJudgement(maxResult, minResult);
|
||||
|
||||
public TestHitObject(HitResult maxResult, HitResult? minResult = null)
|
||||
{
|
||||
|
@ -43,13 +43,13 @@ namespace osu.Game.Tests.Rulesets
|
||||
|
||||
AddStep("setup provider", () =>
|
||||
{
|
||||
var rulesetSkinProvider = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin);
|
||||
|
||||
rulesetSkinProvider.Add(requester = new SkinRequester());
|
||||
|
||||
requester = new SkinRequester();
|
||||
requester.OnLoadAsync += () => textureOnLoad = requester.GetTexture("test-image");
|
||||
|
||||
Child = rulesetSkinProvider;
|
||||
Child = new RulesetSkinProvidingContainer(Ruleset.Value.CreateInstance(), Beatmap.Value.Beatmap, Beatmap.Value.Skin)
|
||||
{
|
||||
Child = requester
|
||||
};
|
||||
});
|
||||
|
||||
AddAssert("requester got correct initial texture", () => textureOnLoad != null);
|
||||
|
@ -349,8 +349,9 @@ namespace osu.Game.Tests.Visual.Background
|
||||
private partial class FadeAccessibleResults : ResultsScreen
|
||||
{
|
||||
public FadeAccessibleResults(ScoreInfo score)
|
||||
: base(score, true)
|
||||
: base(score)
|
||||
{
|
||||
AllowRetry = true;
|
||||
}
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new FadeAccessibleBackground(Beatmap.Value);
|
||||
|
60
osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs
Normal file
60
osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultyIcon.cs
Normal file
@ -0,0 +1,60 @@
|
||||
// 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.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public partial class TestSceneDifficultyIcon : OsuTestScene
|
||||
{
|
||||
private FillFlowContainer<DifficultyIcon> fill = null!;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Child = fill = new FillFlowContainer<DifficultyIcon>
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 300,
|
||||
Direction = FillDirection.Full,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDifficultyIcon()
|
||||
{
|
||||
AddRepeatStep("create difficulty icon", () =>
|
||||
{
|
||||
var rulesetInfo = new OsuRuleset().RulesetInfo;
|
||||
var beatmapInfo = new TestBeatmap(rulesetInfo).BeatmapInfo;
|
||||
|
||||
beatmapInfo.Difficulty.ApproachRate = RNG.Next(0, 10);
|
||||
beatmapInfo.Difficulty.CircleSize = RNG.Next(0, 10);
|
||||
beatmapInfo.Difficulty.OverallDifficulty = RNG.Next(0, 10);
|
||||
beatmapInfo.Difficulty.DrainRate = RNG.Next(0, 10);
|
||||
beatmapInfo.StarRating = RNG.NextSingle(0, 10);
|
||||
beatmapInfo.BPM = RNG.Next(60, 300);
|
||||
|
||||
fill.Add(new DifficultyIcon(beatmapInfo, rulesetInfo)
|
||||
{
|
||||
Scale = new Vector2(2),
|
||||
});
|
||||
}, 10);
|
||||
|
||||
AddStep("no tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.None));
|
||||
AddStep("basic tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.StarRating));
|
||||
AddStep("extended tooltip", () => fill.ForEach(icon => icon.TooltipType = DifficultyIconTooltipType.Extended));
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
protected override bool AllowFail => false;
|
||||
|
||||
protected override bool AllowBackwardsSeeks => true;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
|
@ -130,8 +130,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
private void createStabilityContainer(double gameplayStartTime = double.MinValue) => AddStep("create container", () =>
|
||||
{
|
||||
mainContainer.Child = new FrameStabilityContainer(gameplayStartTime)
|
||||
.WithChild(consumer = new ClockConsumingChild()));
|
||||
{
|
||||
AllowBackwardsSeeks = true,
|
||||
}.WithChild(consumer = new ClockConsumingChild());
|
||||
});
|
||||
|
||||
private void seekManualTo(double time) => AddStep($"seek manual clock to {time}", () => manualClock.CurrentTime = time);
|
||||
|
||||
|
@ -1,9 +1,6 @@
|
||||
// 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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -21,10 +18,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Description("player pause/fail screens")]
|
||||
public partial class TestSceneGameplayMenuOverlay : OsuManualInputManagerTestScene
|
||||
{
|
||||
private FailOverlay failOverlay;
|
||||
private PauseOverlay pauseOverlay;
|
||||
private FailOverlay failOverlay = null!;
|
||||
private PauseOverlay pauseOverlay = null!;
|
||||
|
||||
private GlobalActionContainer globalActionContainer;
|
||||
private GlobalActionContainer globalActionContainer = null!;
|
||||
|
||||
private bool triggeredRetryButton;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game)
|
||||
@ -35,12 +34,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
triggeredRetryButton = false;
|
||||
|
||||
globalActionContainer.Children = new Drawable[]
|
||||
{
|
||||
pauseOverlay = new PauseOverlay
|
||||
{
|
||||
OnResume = () => Logger.Log(@"Resume"),
|
||||
OnRetry = () => Logger.Log(@"Retry"),
|
||||
OnRetry = () =>
|
||||
{
|
||||
Logger.Log(@"Retry");
|
||||
triggeredRetryButton = true;
|
||||
},
|
||||
OnQuit = () => Logger.Log(@"Quit"),
|
||||
},
|
||||
failOverlay = new FailOverlay
|
||||
@ -224,17 +229,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
showOverlay();
|
||||
|
||||
bool triggered = false;
|
||||
AddStep("Click retry button", () =>
|
||||
{
|
||||
var lastAction = pauseOverlay.OnRetry;
|
||||
pauseOverlay.OnRetry = () => triggered = true;
|
||||
AddStep("Click retry button", () => getButton(1).TriggerClick());
|
||||
|
||||
getButton(1).TriggerClick();
|
||||
pauseOverlay.OnRetry = lastAction;
|
||||
});
|
||||
|
||||
AddAssert("Action was triggered", () => triggered);
|
||||
AddAssert("Retry was triggered", () => triggeredRetryButton);
|
||||
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
@ -252,25 +249,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
InputManager.Key(Key.Down);
|
||||
});
|
||||
|
||||
bool triggered = false;
|
||||
Action lastAction = null;
|
||||
AddStep("Press enter", () =>
|
||||
{
|
||||
lastAction = pauseOverlay.OnRetry;
|
||||
pauseOverlay.OnRetry = () => triggered = true;
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddAssert("Action was triggered", () =>
|
||||
{
|
||||
if (lastAction != null)
|
||||
{
|
||||
pauseOverlay.OnRetry = lastAction;
|
||||
lastAction = null;
|
||||
}
|
||||
|
||||
return triggered;
|
||||
});
|
||||
AddAssert("Retry was triggered", () => triggeredRetryButton);
|
||||
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneGameplaySamplePlayback : PlayerTestScene
|
||||
{
|
||||
protected override bool AllowBackwardsSeeks => true;
|
||||
|
||||
[Test]
|
||||
public void TestAllSamplesStopDuringSeek()
|
||||
{
|
||||
|
@ -28,6 +28,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene
|
||||
{
|
||||
protected override bool AllowBackwardsSeeks => true;
|
||||
|
||||
private TestGameplaySampleTriggerSource sampleTriggerSource = null!;
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
|
@ -288,6 +288,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public override Container FrameStableComponents { get; }
|
||||
public override IFrameStableClock FrameStableClock { get; }
|
||||
internal override bool FrameStablePlayback { get; set; }
|
||||
public override bool AllowBackwardsSeeks { get; set; }
|
||||
public override IReadOnlyList<Mod> Mods { get; }
|
||||
|
||||
public override double GameplayStartTime { get; }
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user