Merge branch 'master' into undo_sliderpoint_curvetype_change
@ -3,13 +3,13 @@
|
|||||||
"isRoot": true,
|
"isRoot": true,
|
||||||
"tools": {
|
"tools": {
|
||||||
"jetbrains.resharper.globaltools": {
|
"jetbrains.resharper.globaltools": {
|
||||||
"version": "2022.2.3",
|
"version": "2023.3.3",
|
||||||
"commands": [
|
"commands": [
|
||||||
"jb"
|
"jb"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"nvika": {
|
"nvika": {
|
||||||
"version": "2.2.0",
|
"version": "3.0.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"nvika"
|
"nvika"
|
||||||
]
|
]
|
||||||
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2023.1117.0",
|
"version": "2024.517.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
46
.github/workflows/ci.yml
vendored
@ -13,19 +13,12 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
- name: Install .NET 8.0.x
|
||||||
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
uses: actions/setup-dotnet@v4
|
||||||
- name: Install .NET 3.1.x LTS
|
|
||||||
uses: actions/setup-dotnet@v3
|
|
||||||
with:
|
with:
|
||||||
dotnet-version: "3.1.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
|
||||||
uses: actions/setup-dotnet@v3
|
|
||||||
with:
|
|
||||||
dotnet-version: "6.0.x"
|
|
||||||
|
|
||||||
- name: Restore Tools
|
- name: Restore Tools
|
||||||
run: dotnet tool restore
|
run: dotnet tool restore
|
||||||
@ -34,7 +27,7 @@ jobs:
|
|||||||
run: dotnet restore osu.Desktop.slnf
|
run: dotnet restore osu.Desktop.slnf
|
||||||
|
|
||||||
- name: Restore inspectcode cache
|
- name: Restore inspectcode cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ github.workspace }}/inspectcode
|
path: ${{ github.workspace }}/inspectcode
|
||||||
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
key: inspectcode-${{ hashFiles('.config/dotnet-tools.json', '.github/workflows/ci.yml', 'osu.sln*', 'osu*.slnf', '.editorconfig', '.globalconfig', 'CodeAnalysis/*', '**/*.csproj', '**/*.props') }}
|
||||||
@ -77,12 +70,12 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Compile
|
- name: Compile
|
||||||
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
run: dotnet build -c Debug -warnaserror osu.Desktop.slnf
|
||||||
@ -106,18 +99,18 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup JDK 11
|
- name: Setup JDK 11
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: microsoft
|
distribution: microsoft
|
||||||
java-version: 11
|
java-version: 11
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Install .NET workloads
|
- name: Install .NET workloads
|
||||||
run: dotnet workload install maui-android
|
run: dotnet workload install maui-android
|
||||||
@ -133,15 +126,18 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Install .NET Workloads
|
- name: Install .NET Workloads
|
||||||
run: dotnet workload install maui-ios
|
run: dotnet workload install maui-ios
|
||||||
|
|
||||||
|
- name: Select Xcode 15.2
|
||||||
|
run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build -c Debug osu.iOS
|
run: dotnet build -c Debug osu.iOS
|
||||||
|
26
.github/workflows/diffcalc.yml
vendored
@ -110,10 +110,14 @@ jobs:
|
|||||||
if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }}
|
if: ${{ github.event_name == 'workflow_dispatch' || contains(github.event.comment.body, '!diffcalc') }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check permissions
|
- name: Check permissions
|
||||||
if: ${{ github.event_name != 'workflow_dispatch' }}
|
run: |
|
||||||
uses: actions-cool/check-user-permission@a0668c9aec87f3875fc56170b6452a453e9dd819 # v2.2.0
|
ALLOWED_USERS=(smoogipoo peppy bdach)
|
||||||
with:
|
for i in "${ALLOWED_USERS[@]}"; do
|
||||||
require: 'write'
|
if [[ "${{ github.actor }}" == "$i" ]]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
|
||||||
create-comment:
|
create-comment:
|
||||||
name: Create PR comment
|
name: Create PR comment
|
||||||
@ -122,7 +126,7 @@ jobs:
|
|||||||
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
if: ${{ github.event_name == 'issue_comment' && github.event.issue.pull_request }}
|
||||||
steps:
|
steps:
|
||||||
- name: Create comment
|
- name: Create comment
|
||||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||||
with:
|
with:
|
||||||
comment_tag: ${{ env.EXECUTION_ID }}
|
comment_tag: ${{ env.EXECUTION_ID }}
|
||||||
message: |
|
message: |
|
||||||
@ -140,7 +144,7 @@ jobs:
|
|||||||
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
GOOGLE_CREDS_FILE: ${{ steps.set-outputs.outputs.GOOGLE_CREDS_FILE }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout diffcalc-sheet-generator
|
- name: Checkout diffcalc-sheet-generator
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: ${{ env.EXECUTION_ID }}
|
path: ${{ env.EXECUTION_ID }}
|
||||||
repository: 'smoogipoo/diffcalc-sheet-generator'
|
repository: 'smoogipoo/diffcalc-sheet-generator'
|
||||||
@ -249,7 +253,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Restore cache
|
- name: Restore cache
|
||||||
id: restore-cache
|
id: restore-cache
|
||||||
uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1
|
uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
|
path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
|
||||||
key: ${{ steps.query.outputs.DATA_NAME }}
|
key: ${{ steps.query.outputs.DATA_NAME }}
|
||||||
@ -280,7 +284,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Restore cache
|
- name: Restore cache
|
||||||
id: restore-cache
|
id: restore-cache
|
||||||
uses: maxnowack/local-cache@038cc090b52e4f205fbc468bf5b0756df6f68775 # v1
|
uses: maxnowack/local-cache@720e69c948191660a90aa1cf6a42fc4d2dacdf30 # v2
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
|
path: ${{ steps.query.outputs.DATA_NAME }}.tar.bz2
|
||||||
key: ${{ steps.query.outputs.DATA_NAME }}
|
key: ${{ steps.query.outputs.DATA_NAME }}
|
||||||
@ -354,7 +358,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Update comment on success
|
- name: Update comment on success
|
||||||
if: ${{ needs.generator.result == 'success' }}
|
if: ${{ needs.generator.result == 'success' }}
|
||||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||||
with:
|
with:
|
||||||
comment_tag: ${{ env.EXECUTION_ID }}
|
comment_tag: ${{ env.EXECUTION_ID }}
|
||||||
mode: upsert
|
mode: upsert
|
||||||
@ -365,7 +369,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update comment on failure
|
- name: Update comment on failure
|
||||||
if: ${{ needs.generator.result == 'failure' }}
|
if: ${{ needs.generator.result == 'failure' }}
|
||||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||||
with:
|
with:
|
||||||
comment_tag: ${{ env.EXECUTION_ID }}
|
comment_tag: ${{ env.EXECUTION_ID }}
|
||||||
mode: upsert
|
mode: upsert
|
||||||
@ -375,7 +379,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Update comment on cancellation
|
- name: Update comment on cancellation
|
||||||
if: ${{ needs.generator.result == 'cancelled' }}
|
if: ${{ needs.generator.result == 'cancelled' }}
|
||||||
uses: thollander/actions-comment-pull-request@363c6f6eae92cc5c3a66e95ba016fc771bb38943 # v2.4.2
|
uses: thollander/actions-comment-pull-request@fabd468d3a1a0b97feee5f6b9e499eab0dd903f6 # v2.5.0
|
||||||
with:
|
with:
|
||||||
comment_tag: ${{ env.EXECUTION_ID }}
|
comment_tag: ${{ env.EXECUTION_ID }}
|
||||||
mode: delete
|
mode: delete
|
||||||
|
2
.github/workflows/report-nunit.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
|||||||
timeout-minutes: 5
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- name: Annotate CI run with test results
|
- name: Annotate CI run with test results
|
||||||
uses: dorny/test-reporter@v1.6.0
|
uses: dorny/test-reporter@v1.8.0
|
||||||
with:
|
with:
|
||||||
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}})
|
||||||
|
2
.github/workflows/sentry-release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
14
.github/workflows/update-web-mod-definitions.yml
vendored
@ -12,24 +12,24 @@ jobs:
|
|||||||
name: Update osu-web mod definitions
|
name: Update osu-web mod definitions
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 8.0.x
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "8.0.x"
|
||||||
|
|
||||||
- name: Checkout ppy/osu
|
- name: Checkout ppy/osu
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: osu
|
path: osu
|
||||||
|
|
||||||
- name: Checkout ppy/osu-tools
|
- name: Checkout ppy/osu-tools
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-tools
|
repository: ppy/osu-tools
|
||||||
path: osu-tools
|
path: osu-tools
|
||||||
|
|
||||||
- name: Checkout ppy/osu-web
|
- name: Checkout ppy/osu-web
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-web
|
repository: ppy/osu-web
|
||||||
path: osu-web
|
path: osu-web
|
||||||
@ -43,7 +43,7 @@ jobs:
|
|||||||
working-directory: ./osu-tools
|
working-directory: ./osu-tools
|
||||||
|
|
||||||
- name: Create pull request with changes
|
- name: Create pull request with changes
|
||||||
uses: peter-evans/create-pull-request@v5
|
uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
title: Update mod definitions
|
title: Update mod definitions
|
||||||
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
|
||||||
|
1
.gitignore
vendored
@ -341,3 +341,4 @@ inspectcode
|
|||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
|
|
||||||
.idea/.idea.osu.Desktop/.idea/misc.xml
|
.idea/.idea.osu.Desktop/.idea/misc.xml
|
||||||
|
.idea/.idea.osu.Android/.idea/deploymentTargetDropDown.xml
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
is_global = true
|
|
||||||
|
|
||||||
# .NET Code Style
|
# .NET Code Style
|
||||||
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
|
# IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="CatchRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="ManiaRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="OsuRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="TaikoRuleset (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Ruleset" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Tournament" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Tournament (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="Tournament" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<browser url="http://localhost:5000" />
|
<browser url="http://localhost:5000" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu!" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu! (Tests)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="osu! (Second Client)" type="DotNetProject" factoryName=".NET Project" folderName="osu!" activateToolWindowBeforeRun="false">
|
<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="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="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -12,7 +12,7 @@
|
|||||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net6.0" />
|
<option name="PROJECT_TFM" value="net8.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
|
18
.vscode/launch.json
vendored
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll"
|
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Debug)",
|
"preLaunchTask": "Build osu! (Debug)",
|
||||||
@ -19,7 +19,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll"
|
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build osu! (Release)",
|
"preLaunchTask": "Build osu! (Release)",
|
||||||
@ -31,7 +31,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tests (Debug)",
|
"preLaunchTask": "Build tests (Debug)",
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build tests (Release)",
|
"preLaunchTask": "Build tests (Release)",
|
||||||
@ -55,7 +55,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Debug/net6.0/osu!.dll",
|
"${workspaceRoot}/osu.Desktop/bin/Debug/net8.0/osu!.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -68,7 +68,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/osu.Desktop/bin/Release/net6.0/osu!.dll",
|
"${workspaceRoot}/osu.Desktop/bin/Release/net8.0/osu!.dll",
|
||||||
"--tournament"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -81,7 +81,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -94,7 +94,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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"
|
"--tournament"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
@ -105,7 +105,7 @@
|
|||||||
"name": "Benchmark",
|
"name": "Benchmark",
|
||||||
"type": "coreclr",
|
"type": "coreclr",
|
||||||
"request": "launch",
|
"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": [
|
"args": [
|
||||||
"--filter",
|
"--filter",
|
||||||
"*"
|
"*"
|
||||||
|
@ -68,6 +68,7 @@ Aside from the above, below is a brief checklist of things to watch out when you
|
|||||||
- Please do not make code changes via the GitHub web interface.
|
- Please do not make code changes via the GitHub web interface.
|
||||||
- Please add tests for your changes. We expect most new features and bugfixes to have test coverage, unless the effort of adding them is prohibitive. The visual testing methodology we use is described in more detail [here](https://github.com/ppy/osu-framework/wiki/Development-and-Testing).
|
- Please add tests for your changes. We expect most new features and bugfixes to have test coverage, unless the effort of adding them is prohibitive. The visual testing methodology we use is described in more detail [here](https://github.com/ppy/osu-framework/wiki/Development-and-Testing).
|
||||||
- Please run tests and code style analysis (via `InspectCode.{ps1,sh}` scripts in the root of this repository) before opening the PR. This is particularly important if you're a first-time contributor, as CI will not run for your PR until we allow it to do so.
|
- Please run tests and code style analysis (via `InspectCode.{ps1,sh}` scripts in the root of this repository) before opening the PR. This is particularly important if you're a first-time contributor, as CI will not run for your PR until we allow it to do so.
|
||||||
|
- **Do not run the game in release configuration at any point during your testing** (the sole exception to this being benchmarks). Using release is an unnecessary and harmful practice, and can even lead to you losing your local realm database if you start making changes to the schema. The debug configuration has a completely separated full-stack environment, including a development website instance at https://dev.ppy.sh/. It is permitted to register an account on that development instance for testing purposes and not worry about multi-accounting infractions.
|
||||||
|
|
||||||
After you're done with your changes and you wish to open the PR, please observe the following recommendations:
|
After you're done with your changes and you wish to open the PR, please observe the following recommendations:
|
||||||
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
<!-- Contains required properties for osu!framework projects. -->
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="C#">
|
<PropertyGroup Label="C#">
|
||||||
<LangVersion>10.0</LangVersion>
|
<LangVersion>12.0</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
@ -35,7 +34,7 @@
|
|||||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||||
<Company>ppy Pty Ltd</Company>
|
<Company>ppy Pty Ltd</Company>
|
||||||
<Copyright>Copyright (c) 2022 ppy Pty Ltd</Copyright>
|
<Copyright>Copyright (c) 2024 ppy Pty Ltd</Copyright>
|
||||||
<PackageTags>osu game</PackageTags>
|
<PackageTags>osu game</PackageTags>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
2
LICENCE
@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2022 ppy Pty Ltd <contact@ppy.sh>.
|
Copyright (c) 2024 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
10
README.md
@ -22,7 +22,7 @@ A few resources are available as starting points to getting involved and underst
|
|||||||
|
|
||||||
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
|
||||||
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
|
||||||
- Track our current efforts [towards full "ranked play" support](https://github.com/orgs/ppy/projects/13?query=is%3Aopen+sort%3Aupdated-desc).
|
- Track our current efforts [towards improving the game](https://github.com/orgs/ppy/projects/7/views/6).
|
||||||
|
|
||||||
## Running osu!
|
## Running osu!
|
||||||
|
|
||||||
@ -30,12 +30,12 @@ If you are just looking to give the game a whirl, you can grab the latest releas
|
|||||||
|
|
||||||
### Latest release:
|
### Latest release:
|
||||||
|
|
||||||
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
|
| [Windows 10+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 12+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
|
||||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
|--------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ------------- | ------------- | ------------- |
|
||||||
|
|
||||||
You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download).
|
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.
|
**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:
|
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.
|
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.
|
||||||
|
|
||||||
|
10
Templates/Rulesets/ruleset-empty/Directory.Build.props
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Label="Documentation">
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
46
Templates/Rulesets/ruleset-empty/app.manifest
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="MyNewProject" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
<applicationRequestMinimum>
|
||||||
|
<defaultAssemblyRequest permissionSetReference="Custom" />
|
||||||
|
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
|
||||||
|
</applicationRequestMinimum>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows Vista -->
|
||||||
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||||
|
<dpiAware>true</dpiAware>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
</asmv1:assembly>
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
|
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.EmptyFreeformRuleset.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RootNamespace>osu.Game.Rulesets.EmptyFreeform.Tests</RootNamespace>
|
<RootNamespace>osu.Game.Rulesets.EmptyFreeform.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.EmptyFreeform.Objects.Drawables
|
|||||||
{
|
{
|
||||||
if (timeOffset >= 0)
|
if (timeOffset >= 0)
|
||||||
// todo: implement judgement logic
|
// todo: implement judgement logic
|
||||||
ApplyResult(r => r.Type = HitResult.Perfect);
|
ApplyResult(HitResult.Perfect);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AssemblyTitle>osu.Game.Rulesets.EmptyFreeform</AssemblyTitle>
|
<AssemblyTitle>osu.Game.Rulesets.EmptyFreeform</AssemblyTitle>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
10
Templates/Rulesets/ruleset-example/Directory.Build.props
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Label="Documentation">
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
46
Templates/Rulesets/ruleset-example/app.manifest
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="MyNewProject" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
<applicationRequestMinimum>
|
||||||
|
<defaultAssemblyRequest permissionSetReference="Custom" />
|
||||||
|
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
|
||||||
|
</applicationRequestMinimum>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows Vista -->
|
||||||
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||||
|
<dpiAware>true</dpiAware>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
</asmv1:assembly>
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RootNamespace>osu.Game.Rulesets.Pippidon.Tests</RootNamespace>
|
<RootNamespace>osu.Game.Rulesets.Pippidon.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -49,7 +48,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
|
|||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (timeOffset >= 0)
|
if (timeOffset >= 0)
|
||||||
ApplyResult(r => r.Type = IsHovered ? HitResult.Perfect : HitResult.Miss);
|
{
|
||||||
|
if (IsHovered)
|
||||||
|
ApplyMaxResult();
|
||||||
|
else
|
||||||
|
ApplyMinResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double InitialLifetimeOffset => time_preempt;
|
protected override double InitialLifetimeOffset => time_preempt;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AssemblyTitle>osu.Game.Rulesets.Pippidon</AssemblyTitle>
|
<AssemblyTitle>osu.Game.Rulesets.Pippidon</AssemblyTitle>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Label="Documentation">
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
46
Templates/Rulesets/ruleset-scrolling-empty/app.manifest
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="MyNewProject" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
<applicationRequestMinimum>
|
||||||
|
<defaultAssemblyRequest permissionSetReference="Custom" />
|
||||||
|
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
|
||||||
|
</applicationRequestMinimum>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows Vista -->
|
||||||
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||||
|
<dpiAware>true</dpiAware>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
</asmv1:assembly>
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
|
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.EmptyScrolling.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RootNamespace>osu.Game.Rulesets.EmptyScrolling.Tests</RootNamespace>
|
<RootNamespace>osu.Game.Rulesets.EmptyScrolling.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -24,7 +23,7 @@ namespace osu.Game.Rulesets.EmptyScrolling.Objects.Drawables
|
|||||||
{
|
{
|
||||||
if (timeOffset >= 0)
|
if (timeOffset >= 0)
|
||||||
// todo: implement judgement logic
|
// todo: implement judgement logic
|
||||||
ApplyResult(r => r.Type = HitResult.Perfect);
|
ApplyMaxResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AssemblyTitle>osu.Game.Rulesets.EmptyScrolling</AssemblyTitle>
|
<AssemblyTitle>osu.Game.Rulesets.EmptyScrolling</AssemblyTitle>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
<!-- Contains required properties for osu!framework projects. -->
|
||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<ApplicationManifest>$(MSBuildThisFileDirectory)app.manifest</ApplicationManifest>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Label="Documentation">
|
||||||
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<NoWarn>$(NoWarn);CS1591</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
46
Templates/Rulesets/ruleset-scrolling-example/app.manifest
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<assemblyIdentity version="1.0.0.0" name="MyNewProject" />
|
||||||
|
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||||
|
<security>
|
||||||
|
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||||
|
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||||
|
</requestedPrivileges>
|
||||||
|
<applicationRequestMinimum>
|
||||||
|
<defaultAssemblyRequest permissionSetReference="Custom" />
|
||||||
|
<PermissionSet class="System.Security.PermissionSet" version="1" Unrestricted="true" ID="Custom" SameSite="site" />
|
||||||
|
</applicationRequestMinimum>
|
||||||
|
</security>
|
||||||
|
</trustInfo>
|
||||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||||
|
<application>
|
||||||
|
<!-- Windows Vista -->
|
||||||
|
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
|
||||||
|
<!-- Windows 7 -->
|
||||||
|
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
|
||||||
|
<!-- Windows 8 -->
|
||||||
|
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
|
||||||
|
<!-- Windows 8.1 -->
|
||||||
|
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
|
||||||
|
<!-- Windows 10 -->
|
||||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
|
||||||
|
</application>
|
||||||
|
</compatibility>
|
||||||
|
<asmv3:application>
|
||||||
|
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||||
|
<dpiAware>true</dpiAware>
|
||||||
|
</asmv3:windowsSettings>
|
||||||
|
</asmv3:application>
|
||||||
|
<dependency>
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity
|
||||||
|
type="win32"
|
||||||
|
name="Microsoft.Windows.Common-Controls"
|
||||||
|
version="6.0.0.0"
|
||||||
|
processorArchitecture="*"
|
||||||
|
publicKeyToken="6595b64144ccf1df"
|
||||||
|
language="*"
|
||||||
|
/>
|
||||||
|
</dependentAssembly>
|
||||||
|
</dependency>
|
||||||
|
</asmv1:assembly>
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Pippidon.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Pippidon.Tests
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu", new HostOptions { BindIPC = true }))
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(@"osu"))
|
||||||
{
|
{
|
||||||
host.Run(new OsuTestBrowser());
|
host.Run(new OsuTestBrowser());
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.2" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.3" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -18,7 +18,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<RootNamespace>osu.Game.Rulesets.Pippidon.Tests</RootNamespace>
|
<RootNamespace>osu.Game.Rulesets.Pippidon.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Textures;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Pippidon.UI;
|
using osu.Game.Rulesets.Pippidon.UI;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -49,7 +48,12 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
|
|||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (timeOffset >= 0)
|
if (timeOffset >= 0)
|
||||||
ApplyResult(r => r.Type = currentLane.Value == HitObject.Lane ? HitResult.Perfect : HitResult.Miss);
|
{
|
||||||
|
if (currentLane.Value == HitObject.Lane)
|
||||||
|
ApplyMaxResult();
|
||||||
|
else
|
||||||
|
ApplyMinResult();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<AssemblyTitle>osu.Game.Rulesets.Pippidon</AssemblyTitle>
|
<AssemblyTitle>osu.Game.Rulesets.Pippidon</AssemblyTitle>
|
||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageType>Template</PackageType>
|
<PackageType>Template</PackageType>
|
||||||
<PackageId>ppy.osu.Game.Templates</PackageId>
|
<PackageId>ppy.osu.Game.Templates</PackageId>
|
||||||
@ -8,7 +8,7 @@
|
|||||||
<PackageProjectUrl>https://github.com/ppy/osu/blob/master/Templates</PackageProjectUrl>
|
<PackageProjectUrl>https://github.com/ppy/osu/blob/master/Templates</PackageProjectUrl>
|
||||||
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
<RepositoryUrl>https://github.com/ppy/osu</RepositoryUrl>
|
||||||
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
<PackageReleaseNotes>Automated release.</PackageReleaseNotes>
|
||||||
<copyright>Copyright (c) 2022 ppy Pty Ltd</copyright>
|
<copyright>Copyright (c) 2024 ppy Pty Ltd</copyright>
|
||||||
<Description>Templates to use when creating a ruleset for consumption in osu!.</Description>
|
<Description>Templates to use when creating a ruleset for consumption in osu!.</Description>
|
||||||
<PackageTags>dotnet-new;templates;osu</PackageTags>
|
<PackageTags>dotnet-new;templates;osu</PackageTags>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
BIN
assets/lazer.png
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 438 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "6.0.100",
|
"version": "8.0.100",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestFeature",
|
||||||
|
"allowPrerelease": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.1227.1" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.523.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Android.Input;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Localisation;
|
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
|
|
||||||
namespace osu.Android
|
|
||||||
{
|
|
||||||
public partial class AndroidJoystickSettings : SettingsSubsection
|
|
||||||
{
|
|
||||||
protected override LocalisableString Header => JoystickSettingsStrings.JoystickGamepad;
|
|
||||||
|
|
||||||
private readonly AndroidJoystickHandler joystickHandler;
|
|
||||||
|
|
||||||
private readonly Bindable<bool> enabled = new BindableBool(true);
|
|
||||||
|
|
||||||
private SettingsSlider<float> deadzoneSlider = null!;
|
|
||||||
|
|
||||||
private Bindable<float> handlerDeadzone = null!;
|
|
||||||
|
|
||||||
private Bindable<float> localDeadzone = null!;
|
|
||||||
|
|
||||||
public AndroidJoystickSettings(AndroidJoystickHandler joystickHandler)
|
|
||||||
{
|
|
||||||
this.joystickHandler = joystickHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
// use local bindable to avoid changing enabled state of game host's bindable.
|
|
||||||
handlerDeadzone = joystickHandler.DeadzoneThreshold.GetBoundCopy();
|
|
||||||
localDeadzone = handlerDeadzone.GetUnboundCopy();
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = CommonStrings.Enabled,
|
|
||||||
Current = enabled
|
|
||||||
},
|
|
||||||
deadzoneSlider = new SettingsSlider<float>
|
|
||||||
{
|
|
||||||
LabelText = JoystickSettingsStrings.DeadzoneThreshold,
|
|
||||||
KeyboardStep = 0.01f,
|
|
||||||
DisplayAsPercentage = true,
|
|
||||||
Current = localDeadzone,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
enabled.BindTo(joystickHandler.Enabled);
|
|
||||||
enabled.BindValueChanged(e => deadzoneSlider.Current.Disabled = !e.NewValue, true);
|
|
||||||
|
|
||||||
handlerDeadzone.BindValueChanged(val =>
|
|
||||||
{
|
|
||||||
bool disabled = localDeadzone.Disabled;
|
|
||||||
|
|
||||||
localDeadzone.Disabled = false;
|
|
||||||
localDeadzone.Value = val.NewValue;
|
|
||||||
localDeadzone.Disabled = disabled;
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
localDeadzone.BindValueChanged(val => handlerDeadzone.Value = val.NewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +1,11 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="sh.ppy.osulazer" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!" android:icon="@drawable/lazer" />
|
<application android:allowBackup="true"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:label="osu!"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher" />
|
||||||
<!-- for editor usage -->
|
<!-- for editor usage -->
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using Android.OS;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Android.Input;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Localisation;
|
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Localisation;
|
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Game.Overlays.Settings.Sections.Input;
|
|
||||||
|
|
||||||
namespace osu.Android
|
|
||||||
{
|
|
||||||
public partial class AndroidMouseSettings : SettingsSubsection
|
|
||||||
{
|
|
||||||
private readonly AndroidMouseHandler mouseHandler;
|
|
||||||
|
|
||||||
protected override LocalisableString Header => MouseSettingsStrings.Mouse;
|
|
||||||
|
|
||||||
private Bindable<double> handlerSensitivity = null!;
|
|
||||||
|
|
||||||
private Bindable<double> localSensitivity = null!;
|
|
||||||
|
|
||||||
private Bindable<bool> relativeMode = null!;
|
|
||||||
|
|
||||||
public AndroidMouseSettings(AndroidMouseHandler mouseHandler)
|
|
||||||
{
|
|
||||||
this.mouseHandler = mouseHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuConfigManager osuConfig)
|
|
||||||
{
|
|
||||||
// use local bindable to avoid changing enabled state of game host's bindable.
|
|
||||||
handlerSensitivity = mouseHandler.Sensitivity.GetBoundCopy();
|
|
||||||
localSensitivity = handlerSensitivity.GetUnboundCopy();
|
|
||||||
|
|
||||||
relativeMode = mouseHandler.UseRelativeMode.GetBoundCopy();
|
|
||||||
|
|
||||||
// High precision/pointer capture is only available on Android 8.0 and up
|
|
||||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
|
||||||
{
|
|
||||||
AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = MouseSettingsStrings.HighPrecisionMouse,
|
|
||||||
TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip,
|
|
||||||
Current = relativeMode,
|
|
||||||
Keywords = new[] { @"raw", @"input", @"relative", @"cursor", @"captured", @"pointer" },
|
|
||||||
},
|
|
||||||
new MouseSettings.SensitivitySetting
|
|
||||||
{
|
|
||||||
LabelText = MouseSettingsStrings.CursorSensitivity,
|
|
||||||
Current = localSensitivity,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
AddRange(new Drawable[]
|
|
||||||
{
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = MouseSettingsStrings.DisableMouseWheelVolumeAdjust,
|
|
||||||
TooltipText = MouseSettingsStrings.DisableMouseWheelVolumeAdjustTooltip,
|
|
||||||
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableWheel),
|
|
||||||
},
|
|
||||||
new SettingsCheckbox
|
|
||||||
{
|
|
||||||
LabelText = MouseSettingsStrings.DisableClicksDuringGameplay,
|
|
||||||
Current = osuConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
relativeMode.BindValueChanged(relative => localSensitivity.Disabled = !relative.NewValue, true);
|
|
||||||
|
|
||||||
handlerSensitivity.BindValueChanged(val =>
|
|
||||||
{
|
|
||||||
bool disabled = localSensitivity.Disabled;
|
|
||||||
|
|
||||||
localSensitivity.Disabled = false;
|
|
||||||
localSensitivity.Value = val.NewValue;
|
|
||||||
localSensitivity.Disabled = disabled;
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
localSensitivity.BindValueChanged(val => handlerSensitivity.Value = val.NewValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -72,9 +72,9 @@ namespace osu.Android
|
|||||||
Debug.Assert(Resources?.DisplayMetrics != null);
|
Debug.Assert(Resources?.DisplayMetrics != null);
|
||||||
|
|
||||||
Point displaySize = new Point();
|
Point displaySize = new Point();
|
||||||
#pragma warning disable 618 // GetSize is deprecated
|
#pragma warning disable CA1422 // GetSize is deprecated
|
||||||
WindowManager.DefaultDisplay.GetSize(displaySize);
|
WindowManager.DefaultDisplay.GetSize(displaySize);
|
||||||
#pragma warning restore 618
|
#pragma warning restore CA1422
|
||||||
float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density;
|
float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density;
|
||||||
bool isTablet = smallestWidthDp >= 600f;
|
bool isTablet = smallestWidthDp >= 600f;
|
||||||
|
|
||||||
|
@ -5,13 +5,9 @@ using System;
|
|||||||
using Android.App;
|
using Android.App;
|
||||||
using Microsoft.Maui.Devices;
|
using Microsoft.Maui.Devices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Android.Input;
|
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Input.Handlers;
|
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.Overlays.Settings;
|
|
||||||
using osu.Game.Overlays.Settings.Sections.Input;
|
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
@ -88,24 +84,6 @@ namespace osu.Android
|
|||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new AndroidBatteryInfo();
|
||||||
|
|
||||||
public override SettingsSubsection CreateSettingsSubsectionFor(InputHandler handler)
|
|
||||||
{
|
|
||||||
switch (handler)
|
|
||||||
{
|
|
||||||
case AndroidMouseHandler mh:
|
|
||||||
return new AndroidMouseSettings(mh);
|
|
||||||
|
|
||||||
case AndroidJoystickHandler jh:
|
|
||||||
return new AndroidJoystickSettings(jh);
|
|
||||||
|
|
||||||
case AndroidTouchHandler th:
|
|
||||||
return new TouchSettings(th);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return base.CreateSettingsSubsectionFor(handler);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AndroidBatteryInfo : BatteryInfo
|
private class AndroidBatteryInfo : BatteryInfo
|
||||||
{
|
{
|
||||||
public override double? ChargeLevel => Battery.ChargeLevel;
|
public override double? ChargeLevel => Battery.ChargeLevel;
|
||||||
|
618
osu.Android/Resources/drawable/ic_launcher_background.xml
Normal file
@ -0,0 +1,618 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<group android:scaleX="0.67"
|
||||||
|
android:scaleY="0.67"
|
||||||
|
android:translateX="17.82"
|
||||||
|
android:translateY="17.82">
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M0,0h108v108h-108z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M109.48,-1.48H-1.48V109.48H109.48V-1.48Z"
|
||||||
|
android:fillColor="#404041"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M0,-0.31H108V107.69H0V-0.31Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M215.01,-78.1H-78.39V215.3H215.01V-78.1Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M149.56,96.97H96.68V149.85H149.56V96.97Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M149.56,96.97H96.68V149.85H149.56V96.97Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M100.57,98.05C100.34,98.05 100.15,98.24 100.15,98.47V102.61C100.15,102.84 100.34,103.03 100.57,103.03C100.79,103.03 100.98,102.84 100.98,102.61V98.47C100.98,98.24 100.79,98.05 100.57,98.05Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M101.86,99.22C101.63,99.22 101.44,99.41 101.44,99.63V101.45C101.44,101.67 101.63,101.86 101.86,101.86C102.08,101.86 102.27,101.67 102.27,101.45V99.63C102.27,99.41 102.08,99.22 101.86,99.22Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M104.44,100.54C104.44,101.06 104.33,101.57 104.13,102.05C103.94,102.51 103.65,102.92 103.3,103.28C102.95,103.63 102.53,103.91 102.07,104.11C101.59,104.31 101.09,104.41 100.57,104.41C100.04,104.41 99.54,104.31 99.06,104.11C98.6,103.91 98.19,103.63 97.83,103.28C97.48,102.92 97.2,102.51 97,102.05C96.8,101.57 96.7,101.06 96.7,100.54C96.7,100.02 96.8,99.51 97,99.04C97.2,98.58 97.48,98.16 97.83,97.81C98.19,97.45 98.6,97.18 99.06,96.98C99.54,96.78 100.04,96.67 100.57,96.67C101.09,96.67 101.59,96.77 102.07,96.98C102.53,97.17 102.95,97.45 103.3,97.81C103.65,98.16 103.93,98.58 104.13,99.04C104.33,99.51 104.44,100.02 104.44,100.54ZM103.78,100.54C103.78,98.77 102.34,97.33 100.57,97.33C98.8,97.33 97.36,98.77 97.36,100.54C97.36,102.31 98.8,103.75 100.57,103.75C102.34,103.75 103.78,102.31 103.78,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M99.28,101.86C99.51,101.86 99.69,101.67 99.69,101.45V99.64C99.69,99.41 99.51,99.23 99.28,99.23C99.05,99.23 98.87,99.41 98.87,99.64V101.45C98.87,101.68 99.05,101.86 99.28,101.86Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M149.56,44.09H96.68V96.97H149.56V44.09Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M149.56,44.09H96.68V96.97H149.56V44.09Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M104.44,100.54C104.44,101.06 104.33,101.57 104.13,102.05C103.94,102.51 103.65,102.92 103.3,103.28C102.95,103.63 102.53,103.91 102.07,104.11C101.59,104.31 101.09,104.41 100.57,104.41C100.04,104.41 99.54,104.31 99.06,104.11C98.6,103.91 98.19,103.63 97.83,103.28C97.48,102.92 97.2,102.51 97,102.05C96.8,101.57 96.7,101.06 96.7,100.54C96.7,100.02 96.8,99.51 97,99.04C97.2,98.58 97.48,98.16 97.83,97.81C98.19,97.45 98.6,97.18 99.06,96.98C99.54,96.78 100.04,96.67 100.57,96.67C101.09,96.67 101.59,96.77 102.07,96.98C102.53,97.17 102.95,97.45 103.3,97.81C103.65,98.16 103.93,98.58 104.13,99.04C104.33,99.51 104.44,100.02 104.44,100.54ZM103.78,100.54C103.78,98.77 102.34,97.33 100.57,97.33C98.8,97.33 97.36,98.77 97.36,100.54C97.36,102.31 98.8,103.75 100.57,103.75C102.34,103.75 103.78,102.31 103.78,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M100.57,70.97C100.34,70.97 100.15,71.16 100.15,71.38V75.53C100.15,75.76 100.34,75.94 100.57,75.94C100.79,75.94 100.98,75.76 100.98,75.53V71.38C100.98,71.15 100.79,70.97 100.57,70.97Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M101.86,72.14C101.63,72.14 101.44,72.33 101.44,72.55V74.36C101.44,74.59 101.63,74.77 101.86,74.77C102.08,74.77 102.27,74.59 102.27,74.36V72.55C102.27,72.32 102.08,72.14 101.86,72.14Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M99.28,74.78C99.51,74.78 99.69,74.59 99.69,74.37V72.55C99.69,72.33 99.51,72.14 99.28,72.14C99.05,72.14 98.87,72.33 98.87,72.55V74.37C98.87,74.59 99.05,74.78 99.28,74.78Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M100.57,45.18C100.34,45.18 100.15,45.37 100.15,45.59V49.74C100.15,49.97 100.34,50.15 100.57,50.15C100.79,50.15 100.98,49.97 100.98,49.74V45.59C100.98,45.36 100.79,45.18 100.57,45.18Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M101.86,46.35C101.63,46.35 101.44,46.53 101.44,46.76V48.57C101.44,48.8 101.63,48.98 101.86,48.98C102.08,48.98 102.27,48.79 102.27,48.57V46.76C102.27,46.53 102.08,46.35 101.86,46.35Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M104.44,47.67C104.44,48.19 104.33,48.69 104.13,49.17C103.94,49.63 103.65,50.04 103.3,50.4C102.95,50.75 102.53,51.03 102.07,51.23C101.59,51.43 101.09,51.53 100.57,51.53C100.04,51.53 99.54,51.43 99.06,51.23C98.6,51.03 98.19,50.75 97.83,50.4C97.48,50.04 97.2,49.63 97,49.17C96.8,48.69 96.7,48.19 96.7,47.67C96.7,47.14 96.8,46.64 97,46.16C97.2,45.7 97.48,45.29 97.83,44.93C98.19,44.58 98.6,44.3 99.06,44.1C99.54,43.9 100.04,43.8 100.57,43.8C101.09,43.8 101.59,43.9 102.07,44.1C102.53,44.3 102.95,44.58 103.3,44.93C103.65,45.29 103.93,45.7 104.13,46.16C104.33,46.64 104.44,47.14 104.44,47.67ZM103.78,47.67C103.78,45.89 102.34,44.46 100.57,44.46C98.8,44.46 97.36,45.89 97.36,47.67C97.36,49.44 98.8,50.87 100.57,50.87C102.34,50.87 103.78,49.44 103.78,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M104.44,73.46C104.44,73.98 104.33,74.49 104.13,74.96C103.94,75.42 103.65,75.84 103.3,76.19C102.95,76.55 102.53,76.83 102.07,77.02C101.59,77.22 101.09,77.33 100.57,77.33C100.04,77.33 99.54,77.23 99.06,77.02C98.6,76.83 98.19,76.55 97.83,76.19C97.48,75.84 97.2,75.42 97,74.96C96.8,74.49 96.7,73.98 96.7,73.46C96.7,72.94 96.8,72.43 97,71.95C97.2,71.49 97.48,71.08 97.83,70.72C98.19,70.37 98.6,70.09 99.06,69.89C99.54,69.69 100.04,69.59 100.57,69.59C101.09,69.59 101.59,69.69 102.07,69.89C102.53,70.09 102.95,70.37 103.3,70.72C103.65,71.08 103.93,71.49 104.13,71.95C104.33,72.43 104.44,72.94 104.44,73.46ZM103.78,73.46C103.78,71.68 102.34,70.25 100.57,70.25C98.8,70.25 97.36,71.69 97.36,73.46C97.36,75.23 98.8,76.67 100.57,76.67C102.34,76.67 103.78,75.23 103.78,73.46Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M99.28,48.98C99.51,48.98 99.69,48.8 99.69,48.57V46.76C99.69,46.53 99.51,46.35 99.28,46.35C99.05,46.35 98.87,46.54 98.87,46.76V48.57C98.87,48.8 99.05,48.98 99.28,48.98Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M149.56,-8.78H96.68V44.09H149.56V-8.78Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M149.56,-8.78H96.68V44.09H149.56V-8.78Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M104.44,47.67C104.44,48.19 104.33,48.69 104.13,49.17C103.94,49.63 103.65,50.04 103.3,50.4C102.95,50.75 102.53,51.03 102.07,51.23C101.59,51.43 101.09,51.53 100.57,51.53C100.04,51.53 99.54,51.43 99.06,51.23C98.6,51.03 98.19,50.75 97.83,50.4C97.48,50.04 97.2,49.63 97,49.17C96.8,48.69 96.7,48.19 96.7,47.67C96.7,47.14 96.8,46.64 97,46.16C97.2,45.7 97.48,45.29 97.83,44.93C98.19,44.58 98.6,44.3 99.06,44.1C99.54,43.9 100.04,43.8 100.57,43.8C101.09,43.8 101.59,43.9 102.07,44.1C102.53,44.3 102.95,44.58 103.3,44.93C103.65,45.29 103.93,45.7 104.13,46.16C104.33,46.64 104.44,47.14 104.44,47.67ZM103.78,47.67C103.78,45.89 102.34,44.46 100.57,44.46C98.8,44.46 97.36,45.89 97.36,47.67C97.36,49.44 98.8,50.87 100.57,50.87C102.34,50.87 103.78,49.44 103.78,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M100.57,18.1C100.34,18.1 100.15,18.28 100.15,18.51V22.66C100.15,22.89 100.34,23.07 100.57,23.07C100.79,23.07 100.98,22.88 100.98,22.66V18.51C100.98,18.28 100.79,18.1 100.57,18.1Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M101.86,19.27C101.63,19.27 101.44,19.45 101.44,19.68V21.49C101.44,21.72 101.63,21.9 101.86,21.9C102.08,21.9 102.27,21.71 102.27,21.49V19.68C102.27,19.45 102.08,19.27 101.86,19.27Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M99.28,21.9C99.51,21.9 99.69,21.71 99.69,21.49V19.68C99.69,19.45 99.51,19.27 99.28,19.27C99.05,19.27 98.87,19.45 98.87,19.68V21.49C98.87,21.72 99.05,21.9 99.28,21.9Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M104.44,20.58C104.44,21.1 104.33,21.61 104.13,22.09C103.94,22.55 103.65,22.96 103.3,23.32C102.95,23.67 102.53,23.95 102.07,24.15C101.59,24.35 101.09,24.45 100.57,24.45C100.04,24.45 99.54,24.35 99.06,24.15C98.6,23.95 98.19,23.67 97.83,23.32C97.48,22.96 97.2,22.55 97,22.09C96.8,21.61 96.7,21.1 96.7,20.58C96.7,20.06 96.8,19.55 97,19.08C97.2,18.62 97.48,18.2 97.83,17.85C98.19,17.49 98.6,17.22 99.06,17.02C99.54,16.82 100.04,16.71 100.57,16.71C101.09,16.71 101.59,16.81 102.07,17.02C102.53,17.21 102.95,17.49 103.3,17.85C103.65,18.2 103.93,18.62 104.13,19.08C104.33,19.55 104.44,20.06 104.44,20.58ZM103.78,20.58C103.78,18.81 102.34,17.37 100.57,17.37C98.8,17.37 97.36,18.81 97.36,20.58C97.36,22.35 98.8,23.79 100.57,23.79C102.34,23.79 103.78,22.35 103.78,20.58Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M96.68,96.97H43.8V149.85H96.68V96.97Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M96.68,96.97H43.8V149.85H96.68V96.97Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M73.51,98.07C72.15,98.07 71.04,99.18 71.04,100.54C71.04,101.91 72.15,103.01 73.51,103.01C74.87,103.01 75.98,101.91 75.98,100.54C75.98,99.18 74.87,98.07 73.51,98.07Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77.38,100.54C77.38,101.06 77.28,101.57 77.07,102.05C76.88,102.51 76.6,102.92 76.24,103.28C75.89,103.63 75.48,103.91 75.02,104.11C74.54,104.31 74.03,104.41 73.51,104.41C72.99,104.41 72.48,104.31 72.01,104.11C71.55,103.91 71.13,103.63 70.78,103.28C70.42,102.92 70.14,102.51 69.95,102.05C69.75,101.57 69.64,101.06 69.64,100.54C69.64,100.02 69.74,99.51 69.95,99.04C70.14,98.58 70.42,98.16 70.78,97.81C71.13,97.45 71.55,97.18 72.01,96.98C72.48,96.78 72.99,96.67 73.51,96.67C74.03,96.67 74.54,96.77 75.02,96.98C75.48,97.17 75.89,97.45 76.24,97.81C76.6,98.16 76.88,98.58 77.07,99.04C77.28,99.51 77.38,100.02 77.38,100.54ZM76.72,100.54C76.72,98.77 75.28,97.33 73.51,97.33C71.74,97.33 70.3,98.77 70.3,100.54C70.3,102.31 71.74,103.75 73.51,103.75C75.28,103.75 76.72,102.31 76.72,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M47.69,98.05C47.46,98.05 47.28,98.24 47.28,98.47V102.61C47.28,102.84 47.46,103.03 47.69,103.03C47.92,103.03 48.11,102.84 48.11,102.61V98.47C48.11,98.24 47.92,98.05 47.69,98.05Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M48.98,99.22C48.75,99.22 48.57,99.41 48.57,99.63V101.45C48.57,101.67 48.75,101.86 48.98,101.86C49.21,101.86 49.4,101.67 49.4,101.45V99.63C49.4,99.41 49.21,99.22 48.98,99.22Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.56,100.54C51.56,101.06 51.46,101.57 51.26,102.05C51.06,102.51 50.78,102.92 50.43,103.28C50.07,103.63 49.66,103.91 49.2,104.11C48.72,104.31 48.21,104.41 47.69,104.41C47.17,104.41 46.67,104.31 46.19,104.11C45.73,103.91 45.31,103.63 44.96,103.28C44.6,102.92 44.33,102.51 44.13,102.05C43.93,101.57 43.82,101.06 43.82,100.54C43.82,100.02 43.93,99.51 44.13,99.04C44.32,98.58 44.6,98.16 44.96,97.81C45.31,97.45 45.73,97.18 46.19,96.98C46.67,96.78 47.17,96.67 47.69,96.67C48.21,96.67 48.72,96.77 49.2,96.98C49.66,97.17 50.07,97.45 50.43,97.81C50.78,98.16 51.06,98.58 51.26,99.04C51.46,99.51 51.56,100.02 51.56,100.54ZM50.9,100.54C50.9,98.77 49.46,97.33 47.69,97.33C45.92,97.33 44.48,98.77 44.48,100.54C44.48,102.31 45.92,103.75 47.69,103.75C49.46,103.75 50.9,102.31 50.9,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M46.4,101.86C46.63,101.86 46.82,101.67 46.82,101.45V99.64C46.82,99.41 46.63,99.23 46.4,99.23C46.17,99.23 45.99,99.41 45.99,99.64V101.45C45.99,101.68 46.17,101.86 46.4,101.86Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M96.68,44.09H43.8V96.97H96.68V44.09Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M96.68,44.09H43.8V96.97H96.68V44.09Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77.38,100.54C77.38,101.06 77.28,101.57 77.07,102.05C76.88,102.51 76.6,102.92 76.24,103.28C75.89,103.63 75.48,103.91 75.02,104.11C74.54,104.31 74.03,104.41 73.51,104.41C72.99,104.41 72.48,104.31 72.01,104.11C71.55,103.91 71.13,103.63 70.78,103.28C70.42,102.92 70.14,102.51 69.95,102.05C69.75,101.57 69.64,101.06 69.64,100.54C69.64,100.02 69.74,99.51 69.95,99.04C70.14,98.58 70.42,98.16 70.78,97.81C71.13,97.45 71.55,97.18 72.01,96.98C72.48,96.78 72.99,96.67 73.51,96.67C74.03,96.67 74.54,96.77 75.02,96.98C75.48,97.17 75.89,97.45 76.24,97.81C76.6,98.16 76.88,98.58 77.07,99.04C77.28,99.51 77.38,100.02 77.38,100.54ZM76.72,100.54C76.72,98.77 75.28,97.33 73.51,97.33C71.74,97.33 70.3,98.77 70.3,100.54C70.3,102.31 71.74,103.75 73.51,103.75C75.28,103.75 76.72,102.31 76.72,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.56,100.54C51.56,101.06 51.46,101.57 51.26,102.05C51.06,102.51 50.78,102.92 50.43,103.28C50.07,103.63 49.66,103.91 49.2,104.11C48.72,104.31 48.21,104.41 47.69,104.41C47.17,104.41 46.67,104.31 46.19,104.11C45.73,103.91 45.31,103.63 44.96,103.28C44.6,102.92 44.33,102.51 44.13,102.05C43.93,101.57 43.82,101.06 43.82,100.54C43.82,100.02 43.93,99.51 44.13,99.04C44.32,98.58 44.6,98.16 44.96,97.81C45.31,97.45 45.73,97.18 46.19,96.98C46.67,96.78 47.17,96.67 47.69,96.67C48.21,96.67 48.72,96.77 49.2,96.98C49.66,97.17 50.07,97.45 50.43,97.81C50.78,98.16 51.06,98.58 51.26,99.04C51.46,99.51 51.56,100.02 51.56,100.54ZM50.9,100.54C50.9,98.77 49.46,97.33 47.69,97.33C45.92,97.33 44.48,98.77 44.48,100.54C44.48,102.31 45.92,103.75 47.69,103.75C49.46,103.75 50.9,102.31 50.9,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M47.69,70.97C47.46,70.97 47.28,71.16 47.28,71.38V75.53C47.28,75.76 47.46,75.94 47.69,75.94C47.92,75.94 48.11,75.76 48.11,75.53V71.38C48.11,71.15 47.92,70.97 47.69,70.97Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M48.98,72.14C48.75,72.14 48.57,72.33 48.57,72.55V74.36C48.57,74.59 48.75,74.77 48.98,74.77C49.21,74.77 49.4,74.59 49.4,74.36V72.55C49.4,72.32 49.21,72.14 48.98,72.14Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M46.4,74.78C46.63,74.78 46.82,74.59 46.82,74.37V72.55C46.82,72.33 46.63,72.14 46.4,72.14C46.17,72.14 45.99,72.33 45.99,72.55V74.37C45.99,74.59 46.17,74.78 46.4,74.78Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M73.51,45.2C72.15,45.2 71.04,46.3 71.04,47.67C71.04,49.03 72.15,50.13 73.51,50.13C74.87,50.13 75.98,49.03 75.98,47.67C75.98,46.3 74.87,45.2 73.51,45.2Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77.38,47.67C77.38,48.19 77.28,48.69 77.07,49.17C76.88,49.63 76.6,50.04 76.24,50.4C75.89,50.75 75.48,51.03 75.02,51.23C74.54,51.43 74.03,51.53 73.51,51.53C72.99,51.53 72.48,51.43 72.01,51.23C71.55,51.03 71.13,50.75 70.78,50.4C70.42,50.04 70.14,49.63 69.95,49.17C69.75,48.69 69.64,48.19 69.64,47.67C69.64,47.14 69.74,46.64 69.95,46.16C70.14,45.7 70.42,45.29 70.78,44.93C71.13,44.58 71.55,44.3 72.01,44.1C72.48,43.9 72.99,43.8 73.51,43.8C74.03,43.8 74.54,43.9 75.02,44.1C75.48,44.3 75.89,44.58 76.24,44.93C76.6,45.29 76.88,45.7 77.07,46.16C77.28,46.64 77.38,47.14 77.38,47.67ZM76.72,47.67C76.72,45.89 75.28,44.46 73.51,44.46C71.74,44.46 70.3,45.89 70.3,47.67C70.3,49.44 71.74,50.87 73.51,50.87C75.28,50.87 76.72,49.44 76.72,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M90.28,60.56C90.28,61.08 90.17,61.59 89.97,62.07C89.78,62.53 89.5,62.94 89.14,63.3C88.79,63.65 88.37,63.93 87.91,64.13C87.43,64.33 86.93,64.43 86.41,64.43C85.88,64.43 85.38,64.33 84.9,64.13C84.44,63.93 84.03,63.65 83.67,63.3C83.32,62.94 83.04,62.53 82.84,62.07C82.64,61.59 82.54,61.08 82.54,60.56C82.54,60.04 82.64,59.54 82.84,59.06C83.04,58.6 83.32,58.18 83.67,57.83C84.03,57.47 84.44,57.2 84.9,57C85.38,56.8 85.88,56.69 86.41,56.69C86.93,56.69 87.43,56.8 87.91,57C88.37,57.19 88.79,57.47 89.14,57.83C89.5,58.18 89.77,58.6 89.97,59.06C90.17,59.54 90.28,60.04 90.28,60.56ZM89.62,60.56C89.62,58.79 88.18,57.35 86.41,57.35C84.64,57.35 83.2,58.79 83.2,60.56C83.2,62.33 84.64,63.77 86.41,63.77C88.18,63.77 89.62,62.33 89.62,60.56Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M87.38,59.95C87.03,59.95 86.76,60.22 86.76,60.56C86.76,60.9 87.04,61.18 87.38,61.18C87.71,61.18 87.99,60.9 87.99,60.56C87.99,60.22 87.71,59.95 87.38,59.95Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,58.69C85.59,58.69 85.32,58.97 85.32,59.31C85.32,59.65 85.59,59.92 85.93,59.92C86.27,59.92 86.55,59.65 86.55,59.31C86.55,58.97 86.27,58.69 85.93,58.69Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,61.2C85.59,61.2 85.32,61.48 85.32,61.82C85.32,62.16 85.59,62.43 85.93,62.43C86.27,62.43 86.55,62.16 86.55,61.82C86.55,61.48 86.27,61.2 85.93,61.2Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M73.51,70.99C72.15,70.99 71.04,72.09 71.04,73.46C71.04,74.82 72.15,75.93 73.51,75.93C74.87,75.93 75.98,74.82 75.98,73.46C75.98,72.09 74.87,70.99 73.51,70.99Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77.38,73.46C77.38,73.98 77.28,74.49 77.07,74.96C76.88,75.42 76.6,75.84 76.24,76.19C75.89,76.55 75.48,76.83 75.02,77.02C74.54,77.22 74.03,77.33 73.51,77.33C72.99,77.33 72.48,77.23 72.01,77.02C71.55,76.83 71.13,76.55 70.78,76.19C70.42,75.84 70.14,75.42 69.95,74.96C69.75,74.49 69.64,73.98 69.64,73.46C69.64,72.94 69.74,72.43 69.95,71.95C70.14,71.49 70.42,71.08 70.78,70.72C71.13,70.37 71.55,70.09 72.01,69.89C72.48,69.69 72.99,69.59 73.51,69.59C74.03,69.59 74.54,69.69 75.02,69.89C75.48,70.09 75.89,70.37 76.24,70.72C76.6,71.08 76.88,71.49 77.07,71.95C77.28,72.43 77.38,72.94 77.38,73.46ZM76.72,73.46C76.72,71.68 75.28,70.25 73.51,70.25C71.74,70.25 70.3,71.69 70.3,73.46C70.3,75.23 71.74,76.67 73.51,76.67C75.28,76.67 76.72,75.23 76.72,73.46Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M90.28,86.36C90.28,86.88 90.17,87.38 89.97,87.86C89.78,88.32 89.5,88.74 89.14,89.09C88.79,89.45 88.37,89.72 87.91,89.92C87.43,90.12 86.93,90.23 86.41,90.23C85.88,90.23 85.38,90.12 84.9,89.92C84.44,89.73 84.03,89.45 83.67,89.09C83.32,88.74 83.04,88.32 82.84,87.86C82.64,87.38 82.54,86.88 82.54,86.36C82.54,85.84 82.64,85.33 82.84,84.85C83.04,84.39 83.32,83.98 83.67,83.62C84.03,83.27 84.44,82.99 84.9,82.79C85.38,82.59 85.88,82.49 86.41,82.49C86.93,82.49 87.43,82.59 87.91,82.79C88.37,82.99 88.79,83.27 89.14,83.62C89.5,83.98 89.77,84.39 89.97,84.85C90.17,85.33 90.28,85.84 90.28,86.36ZM89.62,86.36C89.62,84.58 88.18,83.15 86.41,83.15C84.64,83.15 83.2,84.59 83.2,86.36C83.2,88.13 84.64,89.57 86.41,89.57C88.18,89.57 89.62,88.13 89.62,86.36Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M87.38,85.74C87.03,85.74 86.76,86.01 86.76,86.35C86.76,86.69 87.04,86.97 87.38,86.97C87.71,86.97 87.99,86.69 87.99,86.35C87.99,86.01 87.71,85.74 87.38,85.74Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,84.48C85.59,84.48 85.32,84.76 85.32,85.1C85.32,85.44 85.59,85.72 85.93,85.72C86.27,85.72 86.55,85.44 86.55,85.1C86.55,84.76 86.27,84.48 85.93,84.48Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,86.99C85.59,86.99 85.32,87.27 85.32,87.61C85.32,87.95 85.59,88.23 85.93,88.23C86.27,88.23 86.55,87.95 86.55,87.61C86.55,87.27 86.27,86.99 85.93,86.99Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M47.69,45.18C47.46,45.18 47.28,45.37 47.28,45.59V49.74C47.28,49.97 47.46,50.15 47.69,50.15C47.92,50.15 48.11,49.97 48.11,49.74V45.59C48.11,45.36 47.92,45.18 47.69,45.18Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M48.98,46.35C48.75,46.35 48.57,46.53 48.57,46.76V48.57C48.57,48.8 48.75,48.98 48.98,48.98C49.21,48.98 49.4,48.79 49.4,48.57V46.76C49.4,46.53 49.21,46.35 48.98,46.35Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.56,47.67C51.56,48.19 51.46,48.69 51.26,49.17C51.06,49.63 50.78,50.04 50.43,50.4C50.07,50.75 49.66,51.03 49.2,51.23C48.72,51.43 48.21,51.53 47.69,51.53C47.17,51.53 46.67,51.43 46.19,51.23C45.73,51.03 45.31,50.75 44.96,50.4C44.6,50.04 44.33,49.63 44.13,49.17C43.93,48.69 43.82,48.19 43.82,47.67C43.82,47.14 43.93,46.64 44.13,46.16C44.32,45.7 44.6,45.29 44.96,44.93C45.31,44.58 45.73,44.3 46.19,44.1C46.67,43.9 47.17,43.8 47.69,43.8C48.21,43.8 48.72,43.9 49.2,44.1C49.66,44.3 50.07,44.58 50.43,44.93C50.78,45.29 51.06,45.7 51.26,46.16C51.46,46.64 51.56,47.14 51.56,47.67ZM50.9,47.67C50.9,45.89 49.46,44.46 47.69,44.46C45.92,44.46 44.48,45.89 44.48,47.67C44.48,49.44 45.92,50.87 47.69,50.87C49.46,50.87 50.9,49.44 50.9,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M63.06,60.56C63.06,61.93 61.96,63.03 60.59,63.03C59.23,63.03 58.12,61.93 58.12,60.56C58.12,59.2 59.23,58.09 60.59,58.09C61.96,58.09 63.06,59.2 63.06,60.56ZM60.18,62.16V58.97C59.48,59.15 58.94,59.8 58.94,60.56C58.94,61.33 59.48,61.97 60.18,62.16M62.24,60.56C62.24,59.8 61.7,59.15 61,58.97V62.16C61.7,61.97 62.24,61.33 62.24,60.56"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M64.46,60.56C64.46,61.08 64.36,61.59 64.15,62.07C63.96,62.53 63.68,62.94 63.32,63.3C62.97,63.65 62.55,63.93 62.09,64.13C61.62,64.33 61.11,64.43 60.59,64.43C60.07,64.43 59.56,64.33 59.08,64.13C58.62,63.93 58.21,63.65 57.85,63.3C57.5,62.94 57.22,62.53 57.02,62.07C56.82,61.59 56.72,61.08 56.72,60.56C56.72,60.04 56.82,59.54 57.02,59.06C57.22,58.6 57.5,58.18 57.85,57.83C58.21,57.47 58.62,57.2 59.08,57C59.56,56.8 60.07,56.69 60.59,56.69C61.11,56.69 61.62,56.8 62.09,57C62.55,57.19 62.97,57.47 63.32,57.83C63.68,58.18 63.95,58.6 64.15,59.06C64.35,59.54 64.46,60.04 64.46,60.56ZM63.8,60.56C63.8,58.79 62.36,57.35 60.59,57.35C58.82,57.35 57.38,58.79 57.38,60.56C57.38,62.33 58.82,63.77 60.59,63.77C62.36,63.77 63.8,62.33 63.8,60.56Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.56,73.46C51.56,73.98 51.46,74.49 51.26,74.96C51.06,75.42 50.78,75.84 50.43,76.19C50.07,76.55 49.66,76.83 49.2,77.02C48.72,77.22 48.21,77.33 47.69,77.33C47.17,77.33 46.67,77.23 46.19,77.02C45.73,76.83 45.31,76.55 44.96,76.19C44.6,75.84 44.33,75.42 44.13,74.96C43.93,74.49 43.82,73.98 43.82,73.46C43.82,72.94 43.93,72.43 44.13,71.95C44.32,71.49 44.6,71.08 44.96,70.72C45.31,70.37 45.73,70.09 46.19,69.89C46.67,69.69 47.17,69.59 47.69,69.59C48.21,69.59 48.72,69.69 49.2,69.89C49.66,70.09 50.07,70.37 50.43,70.72C50.78,71.08 51.06,71.49 51.26,71.95C51.46,72.43 51.56,72.94 51.56,73.46ZM50.9,73.46C50.9,71.68 49.46,70.25 47.69,70.25C45.92,70.25 44.48,71.69 44.48,73.46C44.48,75.23 45.92,76.67 47.69,76.67C49.46,76.67 50.9,75.23 50.9,73.46Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M63.06,86.36C63.06,87.72 61.96,88.83 60.59,88.83C59.23,88.83 58.12,87.72 58.12,86.36C58.12,84.99 59.23,83.89 60.59,83.89C61.96,83.89 63.06,84.99 63.06,86.36ZM60.18,87.95V84.76C59.48,84.94 58.94,85.59 58.94,86.36C58.94,87.12 59.48,87.77 60.18,87.95M62.24,86.36C62.24,85.59 61.7,84.95 61,84.76V87.95C61.7,87.77 62.24,87.12 62.24,86.35"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M64.46,86.36C64.46,86.88 64.36,87.38 64.15,87.86C63.96,88.32 63.68,88.74 63.32,89.09C62.97,89.45 62.55,89.72 62.09,89.92C61.62,90.12 61.11,90.23 60.59,90.23C60.07,90.23 59.56,90.12 59.08,89.92C58.62,89.73 58.21,89.45 57.85,89.09C57.5,88.74 57.22,88.32 57.02,87.86C56.82,87.38 56.72,86.88 56.72,86.36C56.72,85.84 56.82,85.33 57.02,84.85C57.22,84.39 57.5,83.98 57.85,83.62C58.21,83.27 58.62,82.99 59.08,82.79C59.56,82.59 60.07,82.49 60.59,82.49C61.11,82.49 61.62,82.59 62.09,82.79C62.55,82.99 62.97,83.27 63.32,83.62C63.68,83.98 63.95,84.39 64.15,84.85C64.35,85.33 64.46,85.84 64.46,86.36ZM63.8,86.36C63.8,84.58 62.36,83.15 60.59,83.15C58.82,83.15 57.38,84.59 57.38,86.36C57.38,88.13 58.82,89.57 60.59,89.57C62.36,89.57 63.8,88.13 63.8,86.36Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M46.4,48.98C46.63,48.98 46.82,48.8 46.82,48.57V46.76C46.82,46.53 46.63,46.35 46.4,46.35C46.17,46.35 45.99,46.54 45.99,46.76V48.57C45.99,48.8 46.17,48.98 46.4,48.98Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M96.68,-8.78H43.8V44.09H96.68V-8.78Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M96.68,-8.78H43.8V44.09H96.68V-8.78Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77.38,47.67C77.38,48.19 77.28,48.69 77.07,49.17C76.88,49.63 76.6,50.04 76.24,50.4C75.89,50.75 75.48,51.03 75.02,51.23C74.54,51.43 74.03,51.53 73.51,51.53C72.99,51.53 72.48,51.43 72.01,51.23C71.55,51.03 71.13,50.75 70.78,50.4C70.42,50.04 70.14,49.63 69.95,49.17C69.75,48.69 69.64,48.19 69.64,47.67C69.64,47.14 69.74,46.64 69.95,46.16C70.14,45.7 70.42,45.29 70.78,44.93C71.13,44.58 71.55,44.3 72.01,44.1C72.48,43.9 72.99,43.8 73.51,43.8C74.03,43.8 74.54,43.9 75.02,44.1C75.48,44.3 75.89,44.58 76.24,44.93C76.6,45.29 76.88,45.7 77.07,46.16C77.28,46.64 77.38,47.14 77.38,47.67ZM76.72,47.67C76.72,45.89 75.28,44.46 73.51,44.46C71.74,44.46 70.3,45.89 70.3,47.67C70.3,49.44 71.74,50.87 73.51,50.87C75.28,50.87 76.72,49.44 76.72,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.56,47.67C51.56,48.19 51.46,48.69 51.26,49.17C51.06,49.63 50.78,50.04 50.43,50.4C50.07,50.75 49.66,51.03 49.2,51.23C48.72,51.43 48.21,51.53 47.69,51.53C47.17,51.53 46.67,51.43 46.19,51.23C45.73,51.03 45.31,50.75 44.96,50.4C44.6,50.04 44.33,49.63 44.13,49.17C43.93,48.69 43.82,48.19 43.82,47.67C43.82,47.14 43.93,46.64 44.13,46.16C44.32,45.7 44.6,45.29 44.96,44.93C45.31,44.58 45.73,44.3 46.19,44.1C46.67,43.9 47.17,43.8 47.69,43.8C48.21,43.8 48.72,43.9 49.2,44.1C49.66,44.3 50.07,44.58 50.43,44.93C50.78,45.29 51.06,45.7 51.26,46.16C51.46,46.64 51.56,47.14 51.56,47.67ZM50.9,47.67C50.9,45.89 49.46,44.46 47.69,44.46C45.92,44.46 44.48,45.89 44.48,47.67C44.48,49.44 45.92,50.87 47.69,50.87C49.46,50.87 50.9,49.44 50.9,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M47.69,18.1C47.46,18.1 47.28,18.28 47.28,18.51V22.66C47.28,22.89 47.46,23.07 47.69,23.07C47.92,23.07 48.11,22.88 48.11,22.66V18.51C48.11,18.28 47.92,18.1 47.69,18.1Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M48.98,19.27C48.75,19.27 48.57,19.45 48.57,19.68V21.49C48.57,21.72 48.75,21.9 48.98,21.9C49.21,21.9 49.4,21.71 49.4,21.49V19.68C49.4,19.45 49.21,19.27 48.98,19.27Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M46.4,21.9C46.63,21.9 46.82,21.71 46.82,21.49V19.68C46.82,19.45 46.63,19.27 46.4,19.27C46.17,19.27 45.99,19.45 45.99,19.68V21.49C45.99,21.72 46.17,21.9 46.4,21.9Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M90.28,7.69C90.28,8.21 90.17,8.71 89.97,9.19C89.78,9.65 89.5,10.07 89.14,10.42C88.79,10.78 88.37,11.05 87.91,11.25C87.43,11.45 86.93,11.56 86.41,11.56C85.88,11.56 85.38,11.45 84.9,11.25C84.44,11.06 84.03,10.78 83.67,10.42C83.32,10.07 83.04,9.65 82.84,9.19C82.64,8.71 82.54,8.21 82.54,7.69C82.54,7.17 82.64,6.66 82.84,6.18C83.04,5.72 83.32,5.31 83.67,4.95C84.03,4.6 84.44,4.32 84.9,4.12C85.38,3.92 85.88,3.82 86.41,3.82C86.93,3.82 87.43,3.92 87.91,4.12C88.37,4.32 88.79,4.6 89.14,4.95C89.5,5.31 89.77,5.72 89.97,6.18C90.17,6.66 90.28,7.17 90.28,7.69ZM89.62,7.69C89.62,5.91 88.18,4.48 86.41,4.48C84.64,4.48 83.2,5.92 83.2,7.69C83.2,9.46 84.64,10.9 86.41,10.9C88.18,10.9 89.62,9.46 89.62,7.69Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M87.38,7.07C87.03,7.07 86.76,7.35 86.76,7.69C86.76,8.03 87.04,8.3 87.38,8.3C87.71,8.3 87.99,8.03 87.99,7.69C87.99,7.35 87.71,7.07 87.38,7.07Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,5.81C85.59,5.81 85.32,6.09 85.32,6.43C85.32,6.77 85.59,7.05 85.93,7.05C86.27,7.05 86.55,6.77 86.55,6.43C86.55,6.09 86.27,5.81 85.93,5.81Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,8.33C85.59,8.33 85.32,8.6 85.32,8.94C85.32,9.28 85.59,9.56 85.93,9.56C86.27,9.56 86.55,9.28 86.55,8.94C86.55,8.6 86.27,8.33 85.93,8.33Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M73.51,18.11C72.15,18.11 71.04,19.22 71.04,20.58C71.04,21.95 72.15,23.05 73.51,23.05C74.87,23.05 75.98,21.95 75.98,20.58C75.98,19.22 74.87,18.11 73.51,18.11Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M77.38,20.58C77.38,21.1 77.28,21.61 77.07,22.09C76.88,22.55 76.6,22.96 76.24,23.32C75.89,23.67 75.48,23.95 75.02,24.15C74.54,24.35 74.03,24.45 73.51,24.45C72.99,24.45 72.48,24.35 72.01,24.15C71.55,23.95 71.13,23.67 70.78,23.32C70.42,22.96 70.14,22.55 69.95,22.09C69.75,21.61 69.64,21.1 69.64,20.58C69.64,20.06 69.74,19.55 69.95,19.08C70.14,18.62 70.42,18.2 70.78,17.85C71.13,17.49 71.55,17.22 72.01,17.02C72.48,16.82 72.99,16.71 73.51,16.71C74.03,16.71 74.54,16.81 75.02,17.02C75.48,17.21 75.89,17.49 76.24,17.85C76.6,18.2 76.88,18.62 77.07,19.08C77.28,19.55 77.38,20.06 77.38,20.58ZM76.72,20.58C76.72,18.81 75.28,17.37 73.51,17.37C71.74,17.37 70.3,18.81 70.3,20.58C70.3,22.35 71.74,23.79 73.51,23.79C75.28,23.79 76.72,22.35 76.72,20.58Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M90.28,33.48C90.28,34 90.17,34.51 89.97,34.99C89.78,35.45 89.5,35.86 89.14,36.21C88.79,36.57 88.37,36.85 87.91,37.04C87.43,37.24 86.93,37.35 86.41,37.35C85.88,37.35 85.38,37.25 84.9,37.04C84.44,36.85 84.03,36.57 83.67,36.21C83.32,35.86 83.04,35.45 82.84,34.99C82.64,34.51 82.54,34 82.54,33.48C82.54,32.96 82.64,32.45 82.84,31.97C83.04,31.51 83.32,31.1 83.67,30.75C84.03,30.39 84.44,30.11 84.9,29.92C85.38,29.72 85.88,29.61 86.41,29.61C86.93,29.61 87.43,29.71 87.91,29.92C88.37,30.11 88.79,30.39 89.14,30.75C89.5,31.1 89.77,31.51 89.97,31.97C90.17,32.45 90.28,32.96 90.28,33.48ZM89.62,33.48C89.62,31.71 88.18,30.27 86.41,30.27C84.64,30.27 83.2,31.71 83.2,33.48C83.2,35.25 84.64,36.69 86.41,36.69C88.18,36.69 89.62,35.25 89.62,33.48Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M87.38,32.86C87.03,32.86 86.76,33.14 86.76,33.48C86.76,33.82 87.04,34.1 87.38,34.1C87.71,34.1 87.99,33.82 87.99,33.48C87.99,33.14 87.71,32.86 87.38,32.86Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,31.61C85.59,31.61 85.32,31.88 85.32,32.22C85.32,32.56 85.59,32.84 85.93,32.84C86.27,32.84 86.55,32.56 86.55,32.22C86.55,31.88 86.27,31.61 85.93,31.61Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M85.93,34.12C85.59,34.12 85.32,34.4 85.32,34.74C85.32,35.08 85.59,35.35 85.93,35.35C86.27,35.35 86.55,35.08 86.55,34.74C86.55,34.4 86.27,34.12 85.93,34.12Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M63.06,7.69C63.06,9.05 61.96,10.16 60.59,10.16C59.23,10.16 58.12,9.05 58.12,7.69C58.12,6.32 59.23,5.22 60.59,5.22C61.96,5.22 63.06,6.32 63.06,7.69ZM60.18,9.28V6.09C59.48,6.28 58.94,6.92 58.94,7.69C58.94,8.45 59.48,9.1 60.18,9.28M62.24,7.69C62.24,6.92 61.7,6.28 61,6.09V9.28C61.7,9.1 62.24,8.45 62.24,7.68"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M64.46,7.69C64.46,8.21 64.36,8.71 64.15,9.19C63.96,9.65 63.68,10.07 63.32,10.42C62.97,10.78 62.55,11.05 62.09,11.25C61.62,11.45 61.11,11.56 60.59,11.56C60.07,11.56 59.56,11.45 59.08,11.25C58.62,11.06 58.21,10.78 57.85,10.42C57.5,10.07 57.22,9.65 57.02,9.19C56.82,8.71 56.72,8.21 56.72,7.69C56.72,7.17 56.82,6.66 57.02,6.18C57.22,5.72 57.5,5.31 57.85,4.95C58.21,4.6 58.62,4.32 59.08,4.12C59.56,3.92 60.07,3.82 60.59,3.82C61.11,3.82 61.62,3.92 62.09,4.12C62.55,4.32 62.97,4.6 63.32,4.95C63.68,5.31 63.95,5.72 64.15,6.18C64.35,6.66 64.46,7.17 64.46,7.69ZM63.8,7.69C63.8,5.91 62.36,4.48 60.59,4.48C58.82,4.48 57.38,5.92 57.38,7.69C57.38,9.46 58.82,10.9 60.59,10.9C62.36,10.9 63.8,9.46 63.8,7.69Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M51.56,20.58C51.56,21.1 51.46,21.61 51.26,22.09C51.06,22.55 50.78,22.96 50.43,23.32C50.07,23.67 49.66,23.95 49.2,24.15C48.72,24.35 48.21,24.45 47.69,24.45C47.17,24.45 46.67,24.35 46.19,24.15C45.73,23.95 45.31,23.67 44.96,23.32C44.6,22.96 44.33,22.55 44.13,22.09C43.93,21.61 43.82,21.1 43.82,20.58C43.82,20.06 43.93,19.55 44.13,19.08C44.32,18.62 44.6,18.2 44.96,17.85C45.31,17.49 45.73,17.22 46.19,17.02C46.67,16.82 47.17,16.71 47.69,16.71C48.21,16.71 48.72,16.81 49.2,17.02C49.66,17.21 50.07,17.49 50.43,17.85C50.78,18.2 51.06,18.62 51.26,19.08C51.46,19.55 51.56,20.06 51.56,20.58ZM50.9,20.58C50.9,18.81 49.46,17.37 47.69,17.37C45.92,17.37 44.48,18.81 44.48,20.58C44.48,22.35 45.92,23.79 47.69,23.79C49.46,23.79 50.9,22.35 50.9,20.58Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M63.06,33.48C63.06,34.84 61.96,35.95 60.59,35.95C59.23,35.95 58.12,34.84 58.12,33.48C58.12,32.12 59.23,31.01 60.59,31.01C61.96,31.01 63.06,32.12 63.06,33.48ZM60.18,35.08V31.89C59.48,32.07 58.94,32.72 58.94,33.48C58.94,34.25 59.48,34.89 60.18,35.08M62.24,33.48C62.24,32.71 61.7,32.07 61,31.88V35.07C61.7,34.89 62.24,34.24 62.24,33.48"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M64.46,33.48C64.46,34 64.36,34.51 64.15,34.99C63.96,35.45 63.68,35.86 63.32,36.21C62.97,36.57 62.55,36.85 62.09,37.04C61.62,37.24 61.11,37.35 60.59,37.35C60.07,37.35 59.56,37.25 59.08,37.04C58.62,36.85 58.21,36.57 57.85,36.21C57.5,35.86 57.22,35.45 57.02,34.99C56.82,34.51 56.72,34 56.72,33.48C56.72,32.96 56.82,32.45 57.02,31.97C57.22,31.51 57.5,31.1 57.85,30.75C58.21,30.39 58.62,30.11 59.08,29.92C59.56,29.72 60.07,29.61 60.59,29.61C61.11,29.61 61.62,29.71 62.09,29.92C62.55,30.11 62.97,30.39 63.32,30.75C63.68,31.1 63.95,31.51 64.15,31.97C64.35,32.45 64.46,32.96 64.46,33.48ZM63.8,33.48C63.8,31.71 62.36,30.27 60.59,30.27C58.82,30.27 57.38,31.71 57.38,33.48C57.38,35.25 58.82,36.69 60.59,36.69C62.36,36.69 63.8,35.25 63.8,33.48Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M43.81,96.97H-9.07V149.85H43.81V96.97Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M43.81,96.97H-9.07V149.85H43.81V96.97Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M20.63,98.07C19.27,98.07 18.17,99.18 18.17,100.54C18.17,101.91 19.27,103.01 20.63,103.01C22,103.01 23.1,101.91 23.1,100.54C23.1,99.18 22,98.07 20.63,98.07Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24.5,100.54C24.5,101.06 24.4,101.57 24.2,102.05C24,102.51 23.72,102.92 23.37,103.28C23.01,103.63 22.6,103.91 22.14,104.11C21.66,104.31 21.16,104.41 20.63,104.41C20.11,104.41 19.61,104.31 19.13,104.11C18.67,103.91 18.25,103.63 17.9,103.28C17.55,102.92 17.27,102.51 17.07,102.05C16.87,101.57 16.76,101.06 16.76,100.54C16.76,100.02 16.87,99.51 17.07,99.04C17.26,98.58 17.55,98.16 17.9,97.81C18.25,97.45 18.67,97.18 19.13,96.98C19.61,96.78 20.11,96.67 20.63,96.67C21.16,96.67 21.66,96.77 22.14,96.98C22.6,97.17 23.01,97.45 23.37,97.81C23.72,98.16 24,98.58 24.2,99.04C24.4,99.51 24.5,100.02 24.5,100.54ZM23.85,100.54C23.85,98.77 22.41,97.33 20.64,97.33C18.87,97.33 17.43,98.77 17.43,100.54C17.43,102.31 18.87,103.75 20.64,103.75C22.41,103.75 23.85,102.31 23.85,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M43.81,44.09H-9.07V96.97H43.81V44.09Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M43.81,44.09H-9.07V96.97H43.81V44.09Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24.5,100.54C24.5,101.06 24.4,101.57 24.2,102.05C24,102.51 23.72,102.92 23.37,103.28C23.01,103.63 22.6,103.91 22.14,104.11C21.66,104.31 21.16,104.41 20.63,104.41C20.11,104.41 19.61,104.31 19.13,104.11C18.67,103.91 18.25,103.63 17.9,103.28C17.55,102.92 17.27,102.51 17.07,102.05C16.87,101.57 16.76,101.06 16.76,100.54C16.76,100.02 16.87,99.51 17.07,99.04C17.26,98.58 17.55,98.16 17.9,97.81C18.25,97.45 18.67,97.18 19.13,96.98C19.61,96.78 20.11,96.67 20.63,96.67C21.16,96.67 21.66,96.77 22.14,96.98C22.6,97.17 23.01,97.45 23.37,97.81C23.72,98.16 24,98.58 24.2,99.04C24.4,99.51 24.5,100.02 24.5,100.54ZM23.85,100.54C23.85,98.77 22.41,97.33 20.64,97.33C18.87,97.33 17.43,98.77 17.43,100.54C17.43,102.31 18.87,103.75 20.64,103.75C22.41,103.75 23.85,102.31 23.85,100.54Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M20.63,45.2C19.27,45.2 18.17,46.3 18.17,47.67C18.17,49.03 19.27,50.13 20.63,50.13C22,50.13 23.1,49.03 23.1,47.67C23.1,46.3 22,45.2 20.63,45.2Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24.5,47.67C24.5,48.19 24.4,48.69 24.2,49.17C24,49.63 23.72,50.04 23.37,50.4C23.01,50.75 22.6,51.03 22.14,51.23C21.66,51.43 21.16,51.53 20.63,51.53C20.11,51.53 19.61,51.43 19.13,51.23C18.67,51.03 18.25,50.75 17.9,50.4C17.55,50.04 17.27,49.63 17.07,49.17C16.87,48.69 16.76,48.19 16.76,47.67C16.76,47.14 16.87,46.64 17.07,46.16C17.26,45.7 17.55,45.29 17.9,44.93C18.25,44.58 18.67,44.3 19.13,44.1C19.61,43.9 20.11,43.8 20.63,43.8C21.16,43.8 21.66,43.9 22.14,44.1C22.6,44.3 23.01,44.58 23.37,44.93C23.72,45.29 24,45.7 24.2,46.16C24.4,46.64 24.5,47.14 24.5,47.67ZM23.85,47.67C23.85,45.89 22.41,44.46 20.64,44.46C18.87,44.46 17.43,45.89 17.43,47.67C17.43,49.44 18.87,50.87 20.64,50.87C22.41,50.87 23.85,49.44 23.85,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M37.4,60.56C37.4,61.08 37.3,61.59 37.1,62.07C36.9,62.53 36.62,62.94 36.27,63.3C35.91,63.65 35.5,63.93 35.04,64.13C34.56,64.33 34.05,64.43 33.53,64.43C33.01,64.43 32.5,64.33 32.03,64.13C31.57,63.93 31.15,63.65 30.8,63.3C30.44,62.94 30.17,62.53 29.97,62.07C29.77,61.59 29.66,61.08 29.66,60.56C29.66,60.04 29.76,59.54 29.97,59.06C30.16,58.6 30.44,58.18 30.8,57.83C31.15,57.47 31.57,57.2 32.03,57C32.5,56.8 33.01,56.69 33.53,56.69C34.05,56.69 34.56,56.8 35.04,57C35.5,57.19 35.91,57.47 36.27,57.83C36.62,58.18 36.9,58.6 37.1,59.06C37.3,59.54 37.4,60.04 37.4,60.56ZM36.74,60.56C36.74,58.79 35.3,57.35 33.53,57.35C31.76,57.35 30.32,58.79 30.32,60.56C30.32,62.33 31.76,63.77 33.53,63.77C35.3,63.77 36.74,62.33 36.74,60.56Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M34.5,59.95C34.16,59.95 33.88,60.22 33.88,60.56C33.88,60.9 34.16,61.18 34.5,61.18C34.84,61.18 35.12,60.9 35.12,60.56C35.12,60.22 34.84,59.95 34.5,59.95Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,58.69C32.71,58.69 32.44,58.97 32.44,59.31C32.44,59.65 32.72,59.92 33.06,59.92C33.4,59.92 33.67,59.65 33.67,59.31C33.67,58.97 33.4,58.69 33.06,58.69Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,61.2C32.71,61.2 32.44,61.48 32.44,61.82C32.44,62.16 32.72,62.43 33.06,62.43C33.4,62.43 33.67,62.16 33.67,61.82C33.67,61.48 33.4,61.2 33.06,61.2Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M20.63,70.99C19.27,70.99 18.17,72.09 18.17,73.46C18.17,74.82 19.27,75.93 20.63,75.93C22,75.93 23.1,74.82 23.1,73.46C23.1,72.09 22,70.99 20.63,70.99Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24.5,73.46C24.5,73.98 24.4,74.49 24.2,74.96C24,75.42 23.72,75.84 23.37,76.19C23.01,76.55 22.6,76.83 22.14,77.02C21.66,77.22 21.16,77.33 20.63,77.33C20.11,77.33 19.61,77.23 19.13,77.02C18.67,76.83 18.25,76.55 17.9,76.19C17.55,75.84 17.27,75.42 17.07,74.96C16.87,74.49 16.76,73.98 16.76,73.46C16.76,72.94 16.87,72.43 17.07,71.95C17.26,71.49 17.55,71.08 17.9,70.72C18.25,70.37 18.67,70.09 19.13,69.89C19.61,69.69 20.11,69.59 20.63,69.59C21.16,69.59 21.66,69.69 22.14,69.89C22.6,70.09 23.01,70.37 23.37,70.72C23.72,71.08 24,71.49 24.2,71.95C24.4,72.43 24.5,72.94 24.5,73.46ZM23.85,73.46C23.85,71.68 22.41,70.25 20.64,70.25C18.87,70.25 17.43,71.69 17.43,73.46C17.43,75.23 18.87,76.67 20.64,76.67C22.41,76.67 23.85,75.23 23.85,73.46Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M37.4,86.36C37.4,86.88 37.3,87.38 37.1,87.86C36.9,88.32 36.62,88.74 36.27,89.09C35.91,89.45 35.5,89.72 35.04,89.92C34.56,90.12 34.05,90.23 33.53,90.23C33.01,90.23 32.5,90.12 32.03,89.92C31.57,89.73 31.15,89.45 30.8,89.09C30.44,88.74 30.17,88.32 29.97,87.86C29.77,87.38 29.66,86.88 29.66,86.36C29.66,85.84 29.76,85.33 29.97,84.85C30.16,84.39 30.44,83.98 30.8,83.62C31.15,83.27 31.57,82.99 32.03,82.79C32.5,82.59 33.01,82.49 33.53,82.49C34.05,82.49 34.56,82.59 35.04,82.79C35.5,82.99 35.91,83.27 36.27,83.62C36.62,83.98 36.9,84.39 37.1,84.85C37.3,85.33 37.4,85.84 37.4,86.36ZM36.74,86.36C36.74,84.58 35.3,83.15 33.53,83.15C31.76,83.15 30.32,84.59 30.32,86.36C30.32,88.13 31.76,89.57 33.53,89.57C35.3,89.57 36.74,88.13 36.74,86.36Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M34.5,85.74C34.16,85.74 33.88,86.01 33.88,86.35C33.88,86.69 34.16,86.97 34.5,86.97C34.84,86.97 35.12,86.69 35.12,86.35C35.12,86.01 34.84,85.74 34.5,85.74Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,84.48C32.71,84.48 32.44,84.76 32.44,85.1C32.44,85.44 32.72,85.72 33.06,85.72C33.4,85.72 33.67,85.44 33.67,85.1C33.67,84.76 33.4,84.48 33.06,84.48Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,86.99C32.71,86.99 32.44,87.27 32.44,87.61C32.44,87.95 32.72,88.23 33.06,88.23C33.4,88.23 33.67,87.95 33.67,87.61C33.67,87.27 33.4,86.99 33.06,86.99Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.18,60.56C10.18,61.93 9.08,63.03 7.71,63.03C6.35,63.03 5.25,61.93 5.25,60.56C5.25,59.2 6.35,58.09 7.71,58.09C9.08,58.09 10.18,59.2 10.18,60.56ZM7.3,62.16V58.97C6.6,59.15 6.07,59.8 6.07,60.56C6.07,61.33 6.6,61.97 7.3,62.16M9.36,60.56C9.36,59.8 8.83,59.15 8.13,58.97V62.16C8.83,61.97 9.36,61.33 9.36,60.56"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.58,60.56C11.58,61.08 11.48,61.59 11.28,62.07C11.08,62.53 10.8,62.94 10.45,63.3C10.09,63.65 9.68,63.93 9.22,64.13C8.74,64.33 8.23,64.43 7.71,64.43C7.19,64.43 6.68,64.33 6.21,64.13C5.75,63.93 5.33,63.65 4.98,63.3C4.62,62.94 4.34,62.53 4.15,62.07C3.95,61.59 3.84,61.08 3.84,60.56C3.84,60.04 3.94,59.54 4.15,59.06C4.34,58.6 4.62,58.18 4.98,57.83C5.33,57.47 5.75,57.2 6.21,57C6.68,56.8 7.19,56.69 7.71,56.69C8.23,56.69 8.74,56.8 9.22,57C9.68,57.19 10.09,57.47 10.45,57.83C10.8,58.18 11.08,58.6 11.28,59.06C11.48,59.54 11.58,60.04 11.58,60.56ZM10.92,60.56C10.92,58.79 9.49,57.35 7.71,57.35C5.94,57.35 4.51,58.79 4.51,60.56C4.51,62.33 5.94,63.77 7.71,63.77C9.49,63.77 10.92,62.33 10.92,60.56Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.18,86.36C10.18,87.72 9.08,88.83 7.71,88.83C6.35,88.83 5.25,87.72 5.25,86.36C5.25,84.99 6.35,83.89 7.71,83.89C9.08,83.89 10.18,84.99 10.18,86.36ZM7.3,87.95V84.76C6.6,84.94 6.07,85.59 6.07,86.36C6.07,87.12 6.6,87.77 7.3,87.95M9.36,86.36C9.36,85.59 8.83,84.95 8.13,84.76V87.95C8.83,87.77 9.36,87.12 9.36,86.35"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.58,86.36C11.58,86.88 11.48,87.38 11.28,87.86C11.08,88.32 10.8,88.74 10.45,89.09C10.09,89.45 9.68,89.72 9.22,89.92C8.74,90.12 8.23,90.23 7.71,90.23C7.19,90.23 6.68,90.12 6.21,89.92C5.75,89.73 5.33,89.45 4.98,89.09C4.62,88.74 4.34,88.32 4.15,87.86C3.95,87.38 3.84,86.88 3.84,86.36C3.84,85.84 3.94,85.33 4.15,84.85C4.34,84.39 4.62,83.98 4.98,83.62C5.33,83.27 5.75,82.99 6.21,82.79C6.68,82.59 7.19,82.49 7.71,82.49C8.23,82.49 8.74,82.59 9.22,82.79C9.68,82.99 10.09,83.27 10.45,83.62C10.8,83.98 11.08,84.39 11.28,84.85C11.48,85.33 11.58,85.84 11.58,86.36ZM10.92,86.36C10.92,84.58 9.49,83.15 7.71,83.15C5.94,83.15 4.51,84.59 4.51,86.36C4.51,88.13 5.94,89.57 7.71,89.57C9.49,89.57 10.92,88.13 10.92,86.36Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M43.81,-8.78H-9.07V44.09H43.81V-8.78Z"/>
|
||||||
|
<group>
|
||||||
|
<clip-path
|
||||||
|
android:pathData="M43.81,-8.78H-9.07V44.09H43.81V-8.78Z"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24.5,47.67C24.5,48.19 24.4,48.69 24.2,49.17C24,49.63 23.72,50.04 23.37,50.4C23.01,50.75 22.6,51.03 22.14,51.23C21.66,51.43 21.16,51.53 20.63,51.53C20.11,51.53 19.61,51.43 19.13,51.23C18.67,51.03 18.25,50.75 17.9,50.4C17.55,50.04 17.27,49.63 17.07,49.17C16.87,48.69 16.76,48.19 16.76,47.67C16.76,47.14 16.87,46.64 17.07,46.16C17.26,45.7 17.55,45.29 17.9,44.93C18.25,44.58 18.67,44.3 19.13,44.1C19.61,43.9 20.11,43.8 20.63,43.8C21.16,43.8 21.66,43.9 22.14,44.1C22.6,44.3 23.01,44.58 23.37,44.93C23.72,45.29 24,45.7 24.2,46.16C24.4,46.64 24.5,47.14 24.5,47.67ZM23.85,47.67C23.85,45.89 22.41,44.46 20.64,44.46C18.87,44.46 17.43,45.89 17.43,47.67C17.43,49.44 18.87,50.87 20.64,50.87C22.41,50.87 23.85,49.44 23.85,47.67Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M37.4,7.69C37.4,8.21 37.3,8.71 37.1,9.19C36.9,9.65 36.62,10.07 36.27,10.42C35.91,10.78 35.5,11.05 35.04,11.25C34.56,11.45 34.05,11.56 33.53,11.56C33.01,11.56 32.5,11.45 32.03,11.25C31.57,11.06 31.15,10.78 30.8,10.42C30.44,10.07 30.17,9.65 29.97,9.19C29.77,8.71 29.66,8.21 29.66,7.69C29.66,7.17 29.76,6.66 29.97,6.18C30.16,5.72 30.44,5.31 30.8,4.95C31.15,4.6 31.57,4.32 32.03,4.12C32.5,3.92 33.01,3.82 33.53,3.82C34.05,3.82 34.56,3.92 35.04,4.12C35.5,4.32 35.91,4.6 36.27,4.95C36.62,5.31 36.9,5.72 37.1,6.18C37.3,6.66 37.4,7.17 37.4,7.69ZM36.74,7.69C36.74,5.91 35.3,4.48 33.53,4.48C31.76,4.48 30.32,5.92 30.32,7.69C30.32,9.46 31.76,10.9 33.53,10.9C35.3,10.9 36.74,9.46 36.74,7.69Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M34.5,7.07C34.16,7.07 33.88,7.35 33.88,7.69C33.88,8.03 34.16,8.3 34.5,8.3C34.84,8.3 35.12,8.03 35.12,7.69C35.12,7.35 34.84,7.07 34.5,7.07Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,5.81C32.71,5.81 32.44,6.09 32.44,6.43C32.44,6.77 32.72,7.05 33.06,7.05C33.4,7.05 33.67,6.77 33.67,6.43C33.67,6.09 33.4,5.81 33.06,5.81Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,8.33C32.71,8.33 32.44,8.6 32.44,8.94C32.44,9.28 32.72,9.56 33.06,9.56C33.4,9.56 33.67,9.28 33.67,8.94C33.67,8.6 33.4,8.33 33.06,8.33Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M20.63,18.11C19.27,18.11 18.17,19.22 18.17,20.58C18.17,21.95 19.27,23.05 20.63,23.05C22,23.05 23.1,21.95 23.1,20.58C23.1,19.22 22,18.11 20.63,18.11Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M24.5,20.58C24.5,21.1 24.4,21.61 24.2,22.09C24,22.55 23.72,22.96 23.37,23.32C23.01,23.67 22.6,23.95 22.14,24.15C21.66,24.35 21.16,24.45 20.63,24.45C20.11,24.45 19.61,24.35 19.13,24.15C18.67,23.95 18.25,23.67 17.9,23.32C17.55,22.96 17.27,22.55 17.07,22.09C16.87,21.61 16.76,21.1 16.76,20.58C16.76,20.06 16.87,19.55 17.07,19.08C17.26,18.62 17.55,18.2 17.9,17.85C18.25,17.49 18.67,17.22 19.13,17.02C19.61,16.82 20.11,16.71 20.63,16.71C21.16,16.71 21.66,16.81 22.14,17.02C22.6,17.21 23.01,17.49 23.37,17.85C23.72,18.2 24,18.62 24.2,19.08C24.4,19.55 24.5,20.06 24.5,20.58ZM23.85,20.58C23.85,18.81 22.41,17.37 20.64,17.37C18.87,17.37 17.43,18.81 17.43,20.58C17.43,22.35 18.87,23.79 20.64,23.79C22.41,23.79 23.85,22.35 23.85,20.58Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M37.4,33.48C37.4,34 37.3,34.51 37.1,34.99C36.9,35.45 36.62,35.86 36.27,36.21C35.91,36.57 35.5,36.85 35.04,37.04C34.56,37.24 34.05,37.35 33.53,37.35C33.01,37.35 32.5,37.25 32.03,37.04C31.57,36.85 31.15,36.57 30.8,36.21C30.44,35.86 30.17,35.45 29.97,34.99C29.77,34.51 29.66,34 29.66,33.48C29.66,32.96 29.76,32.45 29.97,31.97C30.16,31.51 30.44,31.1 30.8,30.75C31.15,30.39 31.57,30.11 32.03,29.92C32.5,29.72 33.01,29.61 33.53,29.61C34.05,29.61 34.56,29.71 35.04,29.92C35.5,30.11 35.91,30.39 36.27,30.75C36.62,31.1 36.9,31.51 37.1,31.97C37.3,32.45 37.4,32.96 37.4,33.48ZM36.74,33.48C36.74,31.71 35.3,30.27 33.53,30.27C31.76,30.27 30.32,31.71 30.32,33.48C30.32,35.25 31.76,36.69 33.53,36.69C35.3,36.69 36.74,35.25 36.74,33.48Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M34.5,32.86C34.16,32.86 33.88,33.14 33.88,33.48C33.88,33.82 34.16,34.1 34.5,34.1C34.84,34.1 35.12,33.82 35.12,33.48C35.12,33.14 34.84,32.86 34.5,32.86Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,31.61C32.71,31.61 32.44,31.88 32.44,32.22C32.44,32.56 32.72,32.84 33.06,32.84C33.4,32.84 33.67,32.56 33.67,32.22C33.67,31.88 33.4,31.61 33.06,31.61Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M33.06,34.12C32.71,34.12 32.44,34.4 32.44,34.74C32.44,35.08 32.72,35.35 33.06,35.35C33.4,35.35 33.67,35.08 33.67,34.74C33.67,34.4 33.4,34.12 33.06,34.12Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.18,7.69C10.18,9.05 9.08,10.16 7.71,10.16C6.35,10.16 5.25,9.05 5.25,7.69C5.25,6.32 6.35,5.22 7.71,5.22C9.08,5.22 10.18,6.32 10.18,7.69ZM7.3,9.28V6.09C6.6,6.28 6.07,6.92 6.07,7.69C6.07,8.45 6.6,9.1 7.3,9.28M9.36,7.69C9.36,6.92 8.83,6.28 8.13,6.09V9.28C8.83,9.1 9.36,8.45 9.36,7.68"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.58,7.69C11.58,8.21 11.48,8.71 11.28,9.19C11.08,9.65 10.8,10.07 10.45,10.42C10.09,10.78 9.68,11.05 9.22,11.25C8.74,11.45 8.23,11.56 7.71,11.56C7.19,11.56 6.68,11.45 6.21,11.25C5.75,11.06 5.33,10.78 4.98,10.42C4.62,10.07 4.34,9.65 4.15,9.19C3.95,8.71 3.84,8.21 3.84,7.69C3.84,7.17 3.94,6.66 4.15,6.18C4.34,5.72 4.62,5.31 4.98,4.95C5.33,4.6 5.75,4.32 6.21,4.12C6.68,3.92 7.19,3.82 7.71,3.82C8.23,3.82 8.74,3.92 9.22,4.12C9.68,4.32 10.09,4.6 10.45,4.95C10.8,5.31 11.08,5.72 11.28,6.18C11.48,6.66 11.58,7.17 11.58,7.69ZM10.92,7.69C10.92,5.91 9.49,4.48 7.71,4.48C5.94,4.48 4.51,5.92 4.51,7.69C4.51,9.46 5.94,10.9 7.71,10.9C9.49,10.9 10.92,9.46 10.92,7.69Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M10.18,33.48C10.18,34.84 9.08,35.95 7.71,35.95C6.35,35.95 5.25,34.84 5.25,33.48C5.25,32.12 6.35,31.01 7.71,31.01C9.08,31.01 10.18,32.12 10.18,33.48ZM7.3,35.08V31.89C6.6,32.07 6.07,32.72 6.07,33.48C6.07,34.25 6.6,34.89 7.3,35.08M9.36,33.48C9.36,32.71 8.83,32.07 8.13,31.88V35.07C8.83,34.89 9.36,34.24 9.36,33.48"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M11.58,33.48C11.58,34 11.48,34.51 11.28,34.99C11.08,35.45 10.8,35.86 10.45,36.21C10.09,36.57 9.68,36.85 9.22,37.04C8.74,37.24 8.23,37.35 7.71,37.35C7.19,37.35 6.68,37.25 6.21,37.04C5.75,36.85 5.33,36.57 4.98,36.21C4.62,35.86 4.34,35.45 4.15,34.99C3.95,34.51 3.84,34 3.84,33.48C3.84,32.96 3.94,32.45 4.15,31.97C4.34,31.51 4.62,31.1 4.98,30.75C5.33,30.39 5.75,30.11 6.21,29.92C6.68,29.72 7.19,29.61 7.71,29.61C8.23,29.61 8.74,29.71 9.22,29.92C9.68,30.11 10.09,30.39 10.45,30.75C10.8,31.1 11.08,31.51 11.28,31.97C11.48,32.45 11.58,32.96 11.58,33.48ZM10.92,33.48C10.92,31.71 9.49,30.27 7.71,30.27C5.94,30.27 4.51,31.71 4.51,33.48C4.51,35.25 5.94,36.69 7.71,36.69C9.49,36.69 10.92,35.25 10.92,33.48Z"
|
||||||
|
android:strokeAlpha="0.2"
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:fillAlpha="0.2"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</vector>
|
Before Width: | Height: | Size: 38 KiB |
8
osu.Android/Resources/drawable/monochrome.xml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<vector android:height="108dp" android:viewportHeight="434"
|
||||||
|
android:viewportWidth="434" android:width="108dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#000000" android:pathData="M299.36,178.05C303.08,178.05 305.62,180.81 305.62,184.62V219.92C305.62,223.74 303.08,226.5 299.36,226.5C295.55,226.5 293.11,223.74 293.11,219.92V184.62C293.11,180.81 295.55,178.05 299.36,178.05ZM299.36,248.97C294.81,248.97 291.2,245.37 291.2,240.81C291.2,236.35 294.81,232.75 299.36,232.75C303.92,232.75 307.53,236.35 307.53,240.81C307.53,245.37 303.92,248.97 299.36,248.97Z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M276.52,195.12C280.34,195.12 282.77,197.87 282.77,201.58V225.01C282.77,242.29 272.12,248.97 259.19,248.97C246.15,248.97 235.49,242.29 235.49,225.01V201.58C235.49,197.87 237.93,195.12 241.75,195.12C245.46,195.12 248,197.87 248,201.58V224.16C248,233.6 251.98,237.31 259.19,237.31C266.29,237.31 270.27,233.6 270.27,224.16V201.58C270.27,197.87 272.81,195.12 276.52,195.12Z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M200.02,209.43C200.02,212.93 203.63,214.2 210.52,215.79C220.06,218.12 229.18,220.56 229.18,232.33C229.18,243.78 220.7,248.97 208.51,248.97C198.43,248.97 191.12,245.47 187.73,241.44C185.08,238.26 185.4,235.51 187.94,233.07C191.12,229.99 193.88,231.27 195.68,232.86C198.54,235.51 202.04,238.26 208.82,238.26C213.91,238.26 217.09,236.57 217.09,233.18C217.09,229.78 213.7,228.62 204.8,226.18C196,223.74 188.15,221.41 188.15,210.91C188.15,199.15 197.69,194.27 208.29,194.27C214.34,194.27 221.23,195.86 225.47,200.42C227.27,202.22 228.65,204.76 225.47,208.26C222.29,211.55 219.96,210.6 217.73,208.9C215.71,207.41 212.22,204.87 206.92,204.87C203.31,204.87 200.02,206.04 200.02,209.43Z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M153.74,248.97C138.46,248.97 127.5,237.27 127.5,221.53C127.5,205.68 138.46,194.09 153.74,194.09C169.03,194.09 179.99,205.68 179.99,221.53C179.99,237.27 169.03,248.97 153.74,248.97ZM153.74,237.27C162.59,237.27 168.12,230.46 168.12,221.53C168.12,212.6 162.59,205.68 153.74,205.68C144.89,205.68 139.36,212.6 139.36,221.53C139.36,230.46 144.89,237.27 153.74,237.27Z"/>
|
||||||
|
<path android:fillColor="#000000" android:pathData="M349,217.5C349,290.13 290.13,349 217.5,349C144.88,349 86,290.13 86,217.5C86,144.88 144.88,86 217.5,86C290.13,86 349,144.88 349,217.5ZM99.15,217.5C99.15,282.86 152.14,335.85 217.5,335.85C282.86,335.85 335.85,282.86 335.85,217.5C335.85,152.14 282.86,99.15 217.5,99.15C152.14,99.15 99.15,152.14 99.15,217.5Z"/>
|
||||||
|
</vector>
|
6
osu.Android/Resources/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/monochrome"/>
|
||||||
|
</adaptive-icon>
|
BIN
osu.Android/Resources/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
osu.Android/Resources/mipmap-hdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
osu.Android/Resources/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
BIN
osu.Android/Resources/mipmap-mdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 14 KiB |
BIN
osu.Android/Resources/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 8.5 KiB |
BIN
osu.Android/Resources/mipmap-xhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
osu.Android/Resources/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
osu.Android/Resources/mipmap-xxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
osu.Android/Resources/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
osu.Android/Resources/mipmap-xxxhdpi/ic_launcher_foreground.png
Normal file
After Width: | Height: | Size: 49 KiB |
@ -1,13 +1,10 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.Android.props" />
|
<Import Project="..\osu.Android.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0-android</TargetFramework>
|
<TargetFramework>net8.0-android</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RootNamespace>osu.Android</RootNamespace>
|
<RootNamespace>osu.Android</RootNamespace>
|
||||||
<AssemblyName>osu.Android</AssemblyName>
|
<AssemblyName>osu.Android</AssemblyName>
|
||||||
<UseMauiEssentials>true</UseMauiEssentials>
|
|
||||||
<!-- This currently causes random lockups during gameplay. https://github.com/mono/mono/issues/18973 -->
|
|
||||||
<EnableLLVM>false</EnableLLVM>
|
|
||||||
<Version>0.0.0</Version>
|
<Version>0.0.0</Version>
|
||||||
<ApplicationVersion Condition=" '$(ApplicationVersion)' == '' ">1</ApplicationVersion>
|
<ApplicationVersion Condition=" '$(ApplicationVersion)' == '' ">1</ApplicationVersion>
|
||||||
<ApplicationDisplayVersion Condition=" '$(ApplicationDisplayVersion)' == '' ">$(Version)</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion Condition=" '$(ApplicationDisplayVersion)' == '' ">$(Version)</ApplicationDisplayVersion>
|
||||||
@ -19,4 +16,7 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Maui.Essentials" Version="8.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -16,15 +16,14 @@
|
|||||||
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
|
"osu.Game.Tournament.Tests\\osu.Game.Tournament.Tests.csproj",
|
||||||
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
|
"osu.Game.Tournament\\osu.Game.Tournament.csproj",
|
||||||
"osu.Game\\osu.Game.csproj",
|
"osu.Game\\osu.Game.csproj",
|
||||||
|
|
||||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
|
|
||||||
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform.Tests\\osu.Game.Rulesets.EmptyFreeform.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
"Templates\\Rulesets\\ruleset-empty\\osu.Game.Rulesets.EmptyFreeform\\osu.Game.Rulesets.EmptyFreeform.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
|
"Templates\\Rulesets\\ruleset-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
|
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling.Tests\\osu.Game.Rulesets.EmptyScrolling.Tests.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj",
|
"Templates\\Rulesets\\ruleset-scrolling-empty\\osu.Game.Rulesets.EmptyScrolling\\osu.Game.Rulesets.EmptyScrolling.csproj",
|
||||||
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj"
|
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon.Tests\\osu.Game.Rulesets.Pippidon.Tests.csproj",
|
||||||
|
"Templates\\Rulesets\\ruleset-scrolling-example\\osu.Game.Rulesets.Pippidon\\osu.Game.Rulesets.Pippidon.csproj"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,14 +5,21 @@ using System;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using DiscordRPC;
|
using DiscordRPC;
|
||||||
using DiscordRPC.Message;
|
using DiscordRPC.Message;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Online.Rooms;
|
||||||
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Users;
|
using osu.Game.Users;
|
||||||
using LogLevel = osu.Framework.Logging.LogLevel;
|
using LogLevel = osu.Framework.Logging.LogLevel;
|
||||||
@ -21,39 +28,78 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
internal partial class DiscordRichPresence : Component
|
internal partial class DiscordRichPresence : Component
|
||||||
{
|
{
|
||||||
private const string client_id = "367827983903490050";
|
private const string client_id = "1216669957799018608";
|
||||||
|
|
||||||
private DiscordRpcClient client = null!;
|
private DiscordRpcClient client = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||||
|
|
||||||
private IBindable<APIUser> user = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; } = null!;
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuGame game { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private LoginOverlay? login { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private MultiplayerClient multiplayerClient { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
private readonly IBindable<UserStatus?> status = new Bindable<UserStatus?>();
|
private readonly IBindable<UserStatus?> status = new Bindable<UserStatus?>();
|
||||||
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
private readonly IBindable<UserActivity> activity = new Bindable<UserActivity>();
|
||||||
|
|
||||||
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
|
private readonly Bindable<DiscordRichPresenceMode> privacyMode = new Bindable<DiscordRichPresenceMode>();
|
||||||
|
|
||||||
private readonly RichPresence presence = new RichPresence
|
private readonly RichPresence presence = new RichPresence
|
||||||
{
|
{
|
||||||
Assets = new Assets { LargeImageKey = "osu_logo_lazer", }
|
Assets = new Assets { LargeImageKey = "osu_logo_lazer" },
|
||||||
|
Secrets = new Secrets
|
||||||
|
{
|
||||||
|
JoinSecret = null,
|
||||||
|
SpectateSecret = null,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private IBindable<APIUser>? user;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load()
|
||||||
{
|
{
|
||||||
client = new DiscordRpcClient(client_id)
|
client = new DiscordRpcClient(client_id)
|
||||||
{
|
{
|
||||||
SkipIdenticalPresence = false // handles better on discord IPC loss, see updateStatus call in onReady.
|
// SkipIdenticalPresence allows us to fire SetPresence at any point and leave it to the underlying implementation
|
||||||
|
// to check whether a difference has actually occurred before sending a command to Discord (with a minor caveat that's handled in onReady).
|
||||||
|
SkipIdenticalPresence = true
|
||||||
};
|
};
|
||||||
|
|
||||||
client.OnReady += onReady;
|
client.OnReady += onReady;
|
||||||
|
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Message} ({e.Code})", LoggingTarget.Network, LogLevel.Error);
|
||||||
|
|
||||||
client.OnError += (_, e) => Logger.Log($"An error occurred with Discord RPC Client: {e.Code} {e.Message}", LoggingTarget.Network);
|
try
|
||||||
|
{
|
||||||
|
client.RegisterUriScheme();
|
||||||
|
client.Subscribe(EventType.Join);
|
||||||
|
client.OnJoin += onJoin;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
// This is known to fail in at least the following sandboxed environments:
|
||||||
|
// - macOS (when packaged as an app bundle)
|
||||||
|
// - flatpak (see: https://github.com/flathub/sh.ppy.osu/issues/170)
|
||||||
|
// There is currently no better way to do this offered by Discord, so the best we can do is simply ignore it for now.
|
||||||
|
Logger.Log($"Failed to register Discord URI scheme: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
client.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
|
config.BindWith(OsuSetting.DiscordRichPresence, privacyMode);
|
||||||
|
|
||||||
@ -67,21 +113,32 @@ namespace osu.Desktop
|
|||||||
activity.BindTo(u.NewValue.Activity);
|
activity.BindTo(u.NewValue.Activity);
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
ruleset.BindValueChanged(_ => updateStatus());
|
ruleset.BindValueChanged(_ => schedulePresenceUpdate());
|
||||||
status.BindValueChanged(_ => updateStatus());
|
status.BindValueChanged(_ => schedulePresenceUpdate());
|
||||||
activity.BindValueChanged(_ => updateStatus());
|
activity.BindValueChanged(_ => schedulePresenceUpdate());
|
||||||
privacyMode.BindValueChanged(_ => updateStatus());
|
privacyMode.BindValueChanged(_ => schedulePresenceUpdate());
|
||||||
|
multiplayerClient.RoomUpdated += onRoomUpdated;
|
||||||
client.Initialize();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onReady(object _, ReadyMessage __)
|
private void onReady(object _, ReadyMessage __)
|
||||||
{
|
{
|
||||||
Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
|
Logger.Log("Discord RPC Client ready.", LoggingTarget.Network, LogLevel.Debug);
|
||||||
updateStatus();
|
|
||||||
|
// when RPC is lost and reconnected, we have to clear presence state for updatePresence to work (see DiscordRpcClient.SkipIdenticalPresence).
|
||||||
|
if (client.CurrentPresence != null)
|
||||||
|
client.SetPresence(null);
|
||||||
|
|
||||||
|
schedulePresenceUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateStatus()
|
private void onRoomUpdated() => schedulePresenceUpdate();
|
||||||
|
|
||||||
|
private ScheduledDelegate? presenceUpdateDelegate;
|
||||||
|
|
||||||
|
private void schedulePresenceUpdate()
|
||||||
|
{
|
||||||
|
presenceUpdateDelegate?.Cancel();
|
||||||
|
presenceUpdateDelegate = Scheduler.AddDelayed(() =>
|
||||||
{
|
{
|
||||||
if (!client.IsInitialized)
|
if (!client.IsInitialized)
|
||||||
return;
|
return;
|
||||||
@ -92,11 +149,23 @@ namespace osu.Desktop
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.Value == UserStatus.Online && activity.Value != null)
|
bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited || status.Value == UserStatus.DoNotDisturb;
|
||||||
|
|
||||||
|
updatePresence(hideIdentifiableInformation);
|
||||||
|
client.SetPresence(presence);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updatePresence(bool hideIdentifiableInformation)
|
||||||
{
|
{
|
||||||
bool hideIdentifiableInformation = privacyMode.Value == DiscordRichPresenceMode.Limited;
|
if (user == null)
|
||||||
presence.State = truncate(activity.Value.GetStatus(hideIdentifiableInformation));
|
return;
|
||||||
presence.Details = truncate(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty);
|
|
||||||
|
// user activity
|
||||||
|
if (activity.Value != null)
|
||||||
|
{
|
||||||
|
presence.State = clampLength(activity.Value.GetStatus(hideIdentifiableInformation));
|
||||||
|
presence.Details = clampLength(activity.Value.GetDetails(hideIdentifiableInformation) ?? string.Empty);
|
||||||
|
|
||||||
if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0)
|
if (getBeatmapID(activity.Value) is int beatmapId && beatmapId > 0)
|
||||||
{
|
{
|
||||||
@ -120,7 +189,42 @@ namespace osu.Desktop
|
|||||||
presence.Details = string.Empty;
|
presence.Details = string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
// update user information
|
// user party
|
||||||
|
if (!hideIdentifiableInformation && multiplayerClient.Room != null)
|
||||||
|
{
|
||||||
|
MultiplayerRoom room = multiplayerClient.Room;
|
||||||
|
|
||||||
|
presence.Party = new Party
|
||||||
|
{
|
||||||
|
Privacy = string.IsNullOrEmpty(room.Settings.Password) ? Party.PrivacySetting.Public : Party.PrivacySetting.Private,
|
||||||
|
ID = room.RoomID.ToString(),
|
||||||
|
// technically lobbies can have infinite users, but Discord needs this to be set to something.
|
||||||
|
// to make party display sensible, assign a powers of two above participants count (8 at minimum).
|
||||||
|
Max = (int)Math.Max(8, Math.Pow(2, Math.Ceiling(Math.Log2(room.Users.Count)))),
|
||||||
|
Size = room.Users.Count,
|
||||||
|
};
|
||||||
|
|
||||||
|
RoomSecret roomSecret = new RoomSecret
|
||||||
|
{
|
||||||
|
RoomID = room.RoomID,
|
||||||
|
Password = room.Settings.Password,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (client.HasRegisteredUriScheme)
|
||||||
|
presence.Secrets.JoinSecret = JsonConvert.SerializeObject(roomSecret);
|
||||||
|
|
||||||
|
// discord cannot handle both secrets and buttons at the same time, so we need to choose something.
|
||||||
|
// the multiplayer room seems more important.
|
||||||
|
presence.Buttons = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
presence.Party = null;
|
||||||
|
presence.Secrets.JoinSecret = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// game images:
|
||||||
|
// large image tooltip
|
||||||
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
|
if (privacyMode.Value == DiscordRichPresenceMode.Limited)
|
||||||
presence.Assets.LargeImageText = string.Empty;
|
presence.Assets.LargeImageText = string.Empty;
|
||||||
else
|
else
|
||||||
@ -131,17 +235,55 @@ namespace osu.Desktop
|
|||||||
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
presence.Assets.LargeImageText = $"{user.Value.Username}" + (user.Value.Statistics?.GlobalRank > 0 ? $" (rank #{user.Value.Statistics.GlobalRank:N0})" : string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// update ruleset
|
// small image
|
||||||
presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom";
|
presence.Assets.SmallImageKey = ruleset.Value.IsLegacyRuleset() ? $"mode_{ruleset.Value.OnlineID}" : "mode_custom";
|
||||||
presence.Assets.SmallImageText = ruleset.Value.Name;
|
presence.Assets.SmallImageText = ruleset.Value.Name;
|
||||||
|
|
||||||
client.SetPresence(presence);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onJoin(object sender, JoinMessage args) => Scheduler.AddOnce(() =>
|
||||||
|
{
|
||||||
|
game.Window?.Raise();
|
||||||
|
|
||||||
|
if (!api.IsLoggedIn)
|
||||||
|
{
|
||||||
|
login?.Show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Log($"Received room secret from Discord RPC Client: \"{args.Secret}\"", LoggingTarget.Network, LogLevel.Debug);
|
||||||
|
|
||||||
|
// Stable and lazer share the same Discord client ID, meaning they can accept join requests from each other.
|
||||||
|
// Since they aren't compatible in multi, see if stable's format is being used and log to avoid confusion.
|
||||||
|
if (args.Secret[0] != '{' || !tryParseRoomSecret(args.Secret, out long roomId, out string? password))
|
||||||
|
{
|
||||||
|
Logger.Log("Could not join multiplayer room, invitation is invalid or incompatible.", LoggingTarget.Network, LogLevel.Important);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var request = new GetRoomRequest(roomId);
|
||||||
|
request.Success += room => Schedule(() =>
|
||||||
|
{
|
||||||
|
game.PresentMultiplayerMatch(room, password);
|
||||||
|
});
|
||||||
|
request.Failure += _ => Logger.Log($"Could not join multiplayer room, room could not be found (room ID: {roomId}).", LoggingTarget.Network, LogLevel.Important);
|
||||||
|
api.Queue(request);
|
||||||
|
});
|
||||||
|
|
||||||
private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
|
private static readonly int ellipsis_length = Encoding.UTF8.GetByteCount(new[] { '…' });
|
||||||
|
|
||||||
private string truncate(string str)
|
private static string clampLength(string str)
|
||||||
{
|
{
|
||||||
|
// Empty strings are fine to discord even though single-character strings are not. Make it make sense.
|
||||||
|
if (string.IsNullOrEmpty(str))
|
||||||
|
return str;
|
||||||
|
|
||||||
|
// As above, discord decides that *non-empty* strings shorter than 2 characters cannot possibly be valid input, because... reasons?
|
||||||
|
// And yes, that is two *characters*, or *codepoints*, not *bytes* as further down below (as determined by empirical testing).
|
||||||
|
// That seems very questionable, and isn't even documented anywhere. So to *make it* accept such valid input,
|
||||||
|
// just tack on enough of U+200B ZERO WIDTH SPACEs at the end.
|
||||||
|
if (str.Length < 2)
|
||||||
|
return str.PadRight(2, '\u200B');
|
||||||
|
|
||||||
if (Encoding.UTF8.GetByteCount(str) <= 128)
|
if (Encoding.UTF8.GetByteCount(str) <= 128)
|
||||||
return str;
|
return str;
|
||||||
|
|
||||||
@ -159,7 +301,31 @@ namespace osu.Desktop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private int? getBeatmapID(UserActivity activity)
|
private static bool tryParseRoomSecret(string secretJson, out long roomId, out string? password)
|
||||||
|
{
|
||||||
|
roomId = 0;
|
||||||
|
password = null;
|
||||||
|
|
||||||
|
RoomSecret? roomSecret;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
roomSecret = JsonConvert.DeserializeObject<RoomSecret>(secretJson);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roomSecret == null) return false;
|
||||||
|
|
||||||
|
roomId = roomSecret.RoomID;
|
||||||
|
password = roomSecret.Password;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int? getBeatmapID(UserActivity activity)
|
||||||
{
|
{
|
||||||
switch (activity)
|
switch (activity)
|
||||||
{
|
{
|
||||||
@ -175,8 +341,20 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
|
if (multiplayerClient.IsNotNull())
|
||||||
|
multiplayerClient.RoomUpdated -= onRoomUpdated;
|
||||||
|
|
||||||
client.Dispose();
|
client.Dispose();
|
||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class RoomSecret
|
||||||
|
{
|
||||||
|
[JsonProperty(@"roomId", Required = Required.Always)]
|
||||||
|
public long RoomID { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty(@"password", Required = Required.AllowNull)]
|
||||||
|
public string? Password { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
internal static class NVAPI
|
internal static class NVAPI
|
||||||
{
|
{
|
||||||
private const string osu_filename = "osu!.exe";
|
private const string osu_filename = "osu!.exe";
|
||||||
@ -138,7 +140,7 @@ namespace osu.Desktop
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Make sure that this is a laptop.
|
// Make sure that this is a laptop.
|
||||||
var gpus = new IntPtr[64];
|
IntPtr[] gpus = new IntPtr[64];
|
||||||
if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount)))
|
if (checkError(EnumPhysicalGPUs(gpus, out int gpuCount)))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -456,7 +458,7 @@ namespace osu.Desktop
|
|||||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NVAPI.UNICODE_STRING_MAX)]
|
||||||
public string ProfileName;
|
public string ProfileName;
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValArray)]
|
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
|
||||||
public uint[] GPUSupport;
|
public uint[] GPUSupport;
|
||||||
|
|
||||||
public uint IsPredefined;
|
public uint IsPredefined;
|
||||||
@ -487,6 +489,7 @@ namespace osu.Desktop
|
|||||||
public static uint Stride => (uint)Marshal.SizeOf(typeof(NvApplication)) | (2 << 16);
|
public static uint Stride => (uint)Marshal.SizeOf(typeof(NvApplication)) | (2 << 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
internal enum NvStatus
|
internal enum NvStatus
|
||||||
{
|
{
|
||||||
OK = 0, // Success. Request is completed.
|
OK = 0, // Success. Request is completed.
|
||||||
@ -609,6 +612,7 @@ namespace osu.Desktop
|
|||||||
FIRMWARE_REVISION_NOT_SUPPORTED = -200, // The device's firmware is not supported.
|
FIRMWARE_REVISION_NOT_SUPPORTED = -200, // The device's firmware is not supported.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
internal enum NvSystemType
|
internal enum NvSystemType
|
||||||
{
|
{
|
||||||
UNKNOWN = 0,
|
UNKNOWN = 0,
|
||||||
@ -616,6 +620,7 @@ namespace osu.Desktop
|
|||||||
DESKTOP = 2
|
DESKTOP = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
internal enum NvGpuType
|
internal enum NvGpuType
|
||||||
{
|
{
|
||||||
UNKNOWN = 0,
|
UNKNOWN = 0,
|
||||||
@ -623,6 +628,7 @@ namespace osu.Desktop
|
|||||||
DGPU = 2, // Discrete
|
DGPU = 2, // Discrete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
internal enum NvSettingID : uint
|
internal enum NvSettingID : uint
|
||||||
{
|
{
|
||||||
OGL_AA_LINE_GAMMA_ID = 0x2089BF6C,
|
OGL_AA_LINE_GAMMA_ID = 0x2089BF6C,
|
||||||
@ -715,6 +721,7 @@ namespace osu.Desktop
|
|||||||
INVALID_SETTING_ID = 0xFFFFFFFF
|
INVALID_SETTING_ID = 0xFFFFFFFF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
internal enum NvShimSetting : uint
|
internal enum NvShimSetting : uint
|
||||||
{
|
{
|
||||||
SHIM_RENDERING_MODE_INTEGRATED = 0x00000000,
|
SHIM_RENDERING_MODE_INTEGRATED = 0x00000000,
|
||||||
@ -729,6 +736,7 @@ namespace osu.Desktop
|
|||||||
SHIM_RENDERING_MODE_DEFAULT = SHIM_RENDERING_MODE_AUTO_SELECT
|
SHIM_RENDERING_MODE_DEFAULT = SHIM_RENDERING_MODE_AUTO_SELECT
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
internal enum NvThreadControlSetting : uint
|
internal enum NvThreadControlSetting : uint
|
||||||
{
|
{
|
||||||
OGL_THREAD_CONTROL_ENABLE = 0x00000001,
|
OGL_THREAD_CONTROL_ENABLE = 0x00000001,
|
||||||
|
@ -7,6 +7,7 @@ using System.IO;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
using osu.Desktop.Performance;
|
||||||
using osu.Desktop.Security;
|
using osu.Desktop.Security;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
using osu.Game;
|
||||||
@ -15,11 +16,12 @@ using osu.Framework;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Updater;
|
using osu.Game.Updater;
|
||||||
using osu.Desktop.Windows;
|
using osu.Desktop.Windows;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Performance;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using SDL2;
|
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
{
|
{
|
||||||
@ -28,6 +30,9 @@ namespace osu.Desktop
|
|||||||
private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
|
private OsuSchemeLinkIPCChannel? osuSchemeLinkIPCChannel;
|
||||||
private ArchiveImportIPCChannel? archiveImportIPCChannel;
|
private ArchiveImportIPCChannel? archiveImportIPCChannel;
|
||||||
|
|
||||||
|
[Cached(typeof(IHighPerformanceSessionManager))]
|
||||||
|
private readonly HighPerformanceSessionManager highPerformanceSessionManager = new HighPerformanceSessionManager();
|
||||||
|
|
||||||
public OsuGameDesktop(string[]? args = null)
|
public OsuGameDesktop(string[]? args = null)
|
||||||
: base(args)
|
: base(args)
|
||||||
{
|
{
|
||||||
@ -86,8 +91,8 @@ namespace osu.Desktop
|
|||||||
[SupportedOSPlatform("windows")]
|
[SupportedOSPlatform("windows")]
|
||||||
private string? getStableInstallPathFromRegistry()
|
private string? getStableInstallPathFromRegistry()
|
||||||
{
|
{
|
||||||
using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu"))
|
using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu!"))
|
||||||
return key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
return key?.OpenSubKey(WindowsAssociationManager.SHELL_OPEN_COMMAND)?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override UpdateManager CreateUpdateManager()
|
protected override UpdateManager CreateUpdateManager()
|
||||||
@ -155,7 +160,7 @@ namespace osu.Desktop
|
|||||||
host.Window.Title = Name;
|
host.Window.Title = Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => FrameworkEnvironment.UseSDL3 ? new SDL3BatteryInfo() : new SDL2BatteryInfo();
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
@ -163,23 +168,5 @@ namespace osu.Desktop
|
|||||||
osuSchemeLinkIPCChannel?.Dispose();
|
osuSchemeLinkIPCChannel?.Dispose();
|
||||||
archiveImportIPCChannel?.Dispose();
|
archiveImportIPCChannel?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SDL2BatteryInfo : BatteryInfo
|
|
||||||
{
|
|
||||||
public override double? ChargeLevel
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
SDL.SDL_GetPowerInfo(out _, out int percentage);
|
|
||||||
|
|
||||||
if (percentage == -1)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return percentage / 100.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool OnBattery => SDL.SDL_GetPowerInfo(out _, out _) == SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
60
osu.Desktop/Performance/HighPerformanceSessionManager.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 System;
|
||||||
|
using System.Runtime;
|
||||||
|
using System.Threading;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Game.Performance;
|
||||||
|
|
||||||
|
namespace osu.Desktop.Performance
|
||||||
|
{
|
||||||
|
public class HighPerformanceSessionManager : IHighPerformanceSessionManager
|
||||||
|
{
|
||||||
|
public bool IsSessionActive => activeSessions > 0;
|
||||||
|
|
||||||
|
private int activeSessions;
|
||||||
|
|
||||||
|
private GCLatencyMode originalGCMode;
|
||||||
|
|
||||||
|
public IDisposable BeginSession()
|
||||||
|
{
|
||||||
|
enterSession();
|
||||||
|
return new InvokeOnDisposal<HighPerformanceSessionManager>(this, static m => m.exitSession());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void enterSession()
|
||||||
|
{
|
||||||
|
if (Interlocked.Increment(ref activeSessions) > 1)
|
||||||
|
{
|
||||||
|
Logger.Log($"High performance session requested ({activeSessions} running in total)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 exitSession()
|
||||||
|
{
|
||||||
|
if (Interlocked.Decrement(ref activeSessions) > 0)
|
||||||
|
{
|
||||||
|
Logger.Log($"High performance session finished ({activeSessions} others remain)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.IO;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using osu.Desktop.LegacyIpc;
|
using osu.Desktop.LegacyIpc;
|
||||||
|
using osu.Desktop.Windows;
|
||||||
using osu.Framework;
|
using osu.Framework;
|
||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
@ -12,7 +13,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Game;
|
using osu.Game;
|
||||||
using osu.Game.IPC;
|
using osu.Game.IPC;
|
||||||
using osu.Game.Tournament;
|
using osu.Game.Tournament;
|
||||||
using SDL2;
|
using SDL;
|
||||||
using Squirrel;
|
using Squirrel;
|
||||||
|
|
||||||
namespace osu.Desktop
|
namespace osu.Desktop
|
||||||
@ -30,35 +31,51 @@ namespace osu.Desktop
|
|||||||
[STAThread]
|
[STAThread]
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// NVIDIA profiles are based on the executable name of a process.
|
/*
|
||||||
// Lazer and stable share the same executable name.
|
* WARNING: DO NOT PLACE **ANY** CODE ABOVE THE FOLLOWING BLOCK!
|
||||||
// Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup.
|
*
|
||||||
NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
* Logic handling Squirrel MUST run before EVERYTHING if you do not want to break it.
|
||||||
|
* To be more precise: Squirrel is internally using a rather... crude method to determine whether it is running under NUnit,
|
||||||
// run Squirrel first, as the app may exit after these run
|
* namely by checking loaded assemblies:
|
||||||
|
* https://github.com/clowd/Clowd.Squirrel/blob/24427217482deeeb9f2cacac555525edfc7bd9ac/src/Squirrel/SimpleSplat/PlatformModeDetector.cs#L17-L32
|
||||||
|
*
|
||||||
|
* If it finds ANY assembly from the ones listed above - REGARDLESS of the reason why it is loaded -
|
||||||
|
* the app will then do completely broken things like:
|
||||||
|
* - not creating system shortcuts (as the logic is if'd out if "running tests")
|
||||||
|
* - not exiting after the install / first-update / uninstall hooks are ran (as the `Environment.Exit()` calls are if'd out if "running tests")
|
||||||
|
*/
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
var windowsVersion = Environment.OSVersion.Version;
|
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.
|
// 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/#supported-platforms
|
// See https://www.mongodb.com/docs/realm/sdk/dotnet/compatibility/
|
||||||
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
|
if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2))
|
||||||
|
{
|
||||||
|
unsafe
|
||||||
{
|
{
|
||||||
// If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider
|
// If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider
|
||||||
// disabling it ourselves.
|
// disabling it ourselves.
|
||||||
// We could also better detect compatibility mode if required:
|
// We could also better detect compatibility mode if required:
|
||||||
// https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730
|
// https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730
|
||||||
SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
|
SDL3.SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR,
|
||||||
"Your operating system is too old to run osu!",
|
"Your operating system is too old to run osu!"u8,
|
||||||
"This version of osu! requires at least Windows 8.1 to run.\n"
|
"This version of osu! requires at least Windows 8.1 to run.\n"u8
|
||||||
+ "Please upgrade your operating system or consider using an older version of osu!.\n\n"
|
+ "Please upgrade your operating system or consider using an older version of osu!.\n\n"u8
|
||||||
+ "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!", IntPtr.Zero);
|
+ "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!"u8, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setupSquirrel();
|
setupSquirrel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NVIDIA profiles are based on the executable name of a process.
|
||||||
|
// Lazer and stable share the same executable name.
|
||||||
|
// Stable sets this setting to "Off", which may not be what we want, so let's force it back to the default "Auto" on startup.
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
NVAPI.ThreadedOptimisations = NvThreadControlSetting.OGL_THREAD_CONTROL_DEFAULT;
|
||||||
|
|
||||||
// Back up the cwd before DesktopGameHost changes it
|
// Back up the cwd before DesktopGameHost changes it
|
||||||
string cwd = Environment.CurrentDirectory;
|
string cwd = Environment.CurrentDirectory;
|
||||||
|
|
||||||
@ -90,7 +107,13 @@ namespace osu.Desktop
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, new HostOptions { BindIPC = !tournamentClient }))
|
var hostOptions = new HostOptions
|
||||||
|
{
|
||||||
|
IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null,
|
||||||
|
FriendlyGameName = OsuGameBase.GAME_NAME,
|
||||||
|
};
|
||||||
|
|
||||||
|
using (DesktopGameHost host = Host.GetSuitableDesktopHost(gameName, hostOptions))
|
||||||
{
|
{
|
||||||
if (!host.IsPrimaryInstance)
|
if (!host.IsPrimaryInstance)
|
||||||
{
|
{
|
||||||
@ -161,13 +184,16 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
tools.CreateShortcutForThisExe();
|
tools.CreateShortcutForThisExe();
|
||||||
tools.CreateUninstallerRegistryEntry();
|
tools.CreateUninstallerRegistryEntry();
|
||||||
|
WindowsAssociationManager.InstallAssociations();
|
||||||
}, onAppUpdate: (_, tools) =>
|
}, onAppUpdate: (_, tools) =>
|
||||||
{
|
{
|
||||||
tools.CreateUninstallerRegistryEntry();
|
tools.CreateUninstallerRegistryEntry();
|
||||||
|
WindowsAssociationManager.UpdateAssociations();
|
||||||
}, onAppUninstall: (_, tools) =>
|
}, onAppUninstall: (_, tools) =>
|
||||||
{
|
{
|
||||||
tools.RemoveShortcutForThisExe();
|
tools.RemoveShortcutForThisExe();
|
||||||
tools.RemoveUninstallerRegistryEntry();
|
tools.RemoveUninstallerRegistryEntry();
|
||||||
|
WindowsAssociationManager.UninstallAssociations();
|
||||||
}, onEveryRun: (_, _, _) =>
|
}, onEveryRun: (_, _, _) =>
|
||||||
{
|
{
|
||||||
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
|
// While setting the `ProcessAppUserModelId` fixes duplicate icons/shortcuts on the taskbar, it currently
|
||||||
|
25
osu.Desktop/SDL2BatteryInfo.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
|
namespace osu.Desktop
|
||||||
|
{
|
||||||
|
internal class SDL2BatteryInfo : BatteryInfo
|
||||||
|
{
|
||||||
|
public override double? ChargeLevel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
SDL2.SDL.SDL_GetPowerInfo(out _, out int percentage);
|
||||||
|
|
||||||
|
if (percentage == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return percentage / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnBattery => SDL2.SDL.SDL_GetPowerInfo(out _, out _) == SDL2.SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
|
||||||
|
}
|
||||||
|
}
|
27
osu.Desktop/SDL3BatteryInfo.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using SDL;
|
||||||
|
|
||||||
|
namespace osu.Desktop
|
||||||
|
{
|
||||||
|
internal unsafe class SDL3BatteryInfo : BatteryInfo
|
||||||
|
{
|
||||||
|
public override double? ChargeLevel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
int percentage;
|
||||||
|
SDL3.SDL_GetPowerInfo(null, &percentage);
|
||||||
|
|
||||||
|
if (percentage == -1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return percentage / 100.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnBattery => SDL3.SDL_GetPowerInfo(null, null) == SDL_PowerState.SDL_POWERSTATE_ON_BATTERY;
|
||||||
|
}
|
||||||
|
}
|
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");
|
||||||
|
}
|
||||||
|
}
|
295
osu.Desktop/Windows/WindowsAssociationManager.cs
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
// 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.Diagnostics.CodeAnalysis;
|
||||||
|
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);
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
osu.Desktop/lazer.ico
Executable file → Normal file
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 75 KiB |
@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup Label="Project">
|
<PropertyGroup Label="Project">
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
<Description>A free-to-win rhythm game. Rhythm is just a *click* away!</Description>
|
||||||
@ -23,12 +23,15 @@
|
|||||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Clowd.Squirrel" Version="2.10.2" />
|
<PackageReference Include="Clowd.Squirrel" Version="2.11.1" />
|
||||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||||
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
|
<PackageReference Include="System.IO.Packaging" Version="8.0.0" />
|
||||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Resources">
|
<ItemGroup Label="Resources">
|
||||||
<EmbeddedResource Include="lazer.ico" />
|
<EmbeddedResource Include="lazer.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup Label="Windows Icons">
|
||||||
|
<Content Include="*.ico" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -7,11 +7,12 @@
|
|||||||
<authors>ppy Pty Ltd</authors>
|
<authors>ppy Pty Ltd</authors>
|
||||||
<owners>Dean Herbert</owners>
|
<owners>Dean Herbert</owners>
|
||||||
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
<projectUrl>https://osu.ppy.sh/</projectUrl>
|
||||||
<iconUrl>https://puu.sh/tYyXZ/9a01a5d1b0.ico</iconUrl>
|
<iconUrl>https://github.com/ppy/osu/blob/master/assets/lazer-nuget.png?raw=true</iconUrl>
|
||||||
|
<icon>icon.png</icon>
|
||||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||||
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
<description>A free-to-win rhythm game. Rhythm is just a *click* away!</description>
|
||||||
<releaseNotes>testing</releaseNotes>
|
<releaseNotes>testing</releaseNotes>
|
||||||
<copyright>Copyright (c) 2022 ppy Pty Ltd</copyright>
|
<copyright>Copyright (c) 2024 ppy Pty Ltd</copyright>
|
||||||
<language>en-AU</language>
|
<language>en-AU</language>
|
||||||
</metadata>
|
</metadata>
|
||||||
<files>
|
<files>
|
||||||
@ -19,5 +20,7 @@
|
|||||||
<file src="**.dll" target="lib\net45\"/>
|
<file src="**.dll" target="lib\net45\"/>
|
||||||
<file src="**.config" target="lib\net45\"/>
|
<file src="**.config" target="lib\net45\"/>
|
||||||
<file src="**.json" target="lib\net45\"/>
|
<file src="**.json" target="lib\net45\"/>
|
||||||
|
<file src="**.ico" target="lib\net45\"/>
|
||||||
|
<file src="icon.png" target=""/>
|
||||||
</files>
|
</files>
|
||||||
</package>
|
</package>
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
osu.Game.Benchmarks/BenchmarkUnstableRate.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkUnstableRate : BenchmarkTest
|
||||||
|
{
|
||||||
|
private List<HitEvent> events = null!;
|
||||||
|
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
base.SetUp();
|
||||||
|
events = new List<HitEvent>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 1000; i++)
|
||||||
|
events.Add(new HitEvent(RNG.NextDouble(-200.0, 200.0), RNG.NextDouble(1.0, 2.0), HitResult.Great, new HitObject(), null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CalculateUnstableRate()
|
||||||
|
{
|
||||||
|
_ = events.CalculateUnstableRate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,14 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.9" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||||
<PackageReference Include="nunit" Version="3.13.3" />
|
<PackageReference Include="nunit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- using a different name because package name cannot contain 'catch' -->
|
<!-- using a different name because package name cannot contain 'catch' -->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch_Tests.Android" android:installLocation="auto">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="osu.Game.Rulesets.Catch_Tests.Android" android:installLocation="auto">
|
||||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="34" />
|
||||||
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!catch Test" />
|
<application android:allowBackup="true" android:supportsRtl="true" android:label="osu!catch Test" />
|
||||||
</manifest>
|
</manifest>
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<Import Project="..\osu.Android.props" />
|
<Import Project="..\osu.Android.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0-android</TargetFramework>
|
<TargetFramework>net8.0-android</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<RootNamespace>osu.Game.Rulesets.Catch.Tests</RootNamespace>
|
<RootNamespace>osu.Game.Rulesets.Catch.Tests</RootNamespace>
|
||||||
<AssemblyName>osu.Game.Rulesets.Catch.Tests.Android</AssemblyName>
|
<AssemblyName>osu.Game.Rulesets.Catch.Tests.Android</AssemblyName>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net6.0-ios</TargetFramework>
|
<TargetFramework>net8.0-ios</TargetFramework>
|
||||||
<SupportedOSPlatformVersion>13.4</SupportedOSPlatformVersion>
|
<SupportedOSPlatformVersion>13.4</SupportedOSPlatformVersion>
|
||||||
<RootNamespace>osu.Game.Rulesets.Catch.Tests</RootNamespace>
|
<RootNamespace>osu.Game.Rulesets.Catch.Tests</RootNamespace>
|
||||||
<AssemblyName>osu.Game.Rulesets.Catch.Tests.iOS</AssemblyName>
|
<AssemblyName>osu.Game.Rulesets.Catch.Tests.iOS</AssemblyName>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"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}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Debug)",
|
"preLaunchTask": "Build (Debug)",
|
||||||
@ -20,7 +20,7 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "dotnet",
|
"program": "dotnet",
|
||||||
"args": [
|
"args": [
|
||||||
"${workspaceRoot}/bin/Release/net6.0/osu.Game.Rulesets.Catch.Tests.dll"
|
"${workspaceRoot}/bin/Release/net8.0/osu.Game.Rulesets.Catch.Tests.dll"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceRoot}",
|
"cwd": "${workspaceRoot}",
|
||||||
"preLaunchTask": "Build (Release)",
|
"preLaunchTask": "Build (Release)",
|
||||||
|
@ -53,6 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
[TestCase("3689906", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
||||||
[TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
[TestCase("3949367", new[] { typeof(CatchModDoubleTime), typeof(CatchModEasy) })]
|
||||||
[TestCase("112643")]
|
[TestCase("112643")]
|
||||||
|
[TestCase("1041052", new[] { typeof(CatchModHardRock) })]
|
||||||
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
public new void Test(string name, params Type[] mods) => base.Test(name, mods);
|
||||||
|
|
||||||
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
protected override IEnumerable<ConvertValue> CreateConvertValue(HitObject hitObject)
|
||||||
|
59
osu.Game.Rulesets.Catch.Tests/CatchHealthProcessorTest.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// 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.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CatchHealthProcessorTest
|
||||||
|
{
|
||||||
|
private static readonly object[][] test_cases =
|
||||||
|
[
|
||||||
|
// hitobject, starting HP, fail expected after miss
|
||||||
|
[new Fruit(), 0.01, true],
|
||||||
|
[new Droplet(), 0.01, true],
|
||||||
|
[new TinyDroplet(), 0, false],
|
||||||
|
[new Banana(), 0, false],
|
||||||
|
[new BananaShower(), 0, false]
|
||||||
|
];
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
public void TestFailAfterMinResult(CatchHitObject hitObject, double startingHealth, bool failExpected)
|
||||||
|
{
|
||||||
|
var healthProcessor = new CatchHealthProcessor(0);
|
||||||
|
healthProcessor.ApplyBeatmap(new CatchBeatmap
|
||||||
|
{
|
||||||
|
HitObjects = { hitObject }
|
||||||
|
});
|
||||||
|
healthProcessor.Health.Value = startingHealth;
|
||||||
|
|
||||||
|
var result = new CatchJudgementResult(hitObject, hitObject.CreateJudgement());
|
||||||
|
result.Type = result.Judgement.MinResult;
|
||||||
|
healthProcessor.ApplyResult(result);
|
||||||
|
|
||||||
|
Assert.That(healthProcessor.HasFailed, Is.EqualTo(failExpected));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(test_cases))]
|
||||||
|
public void TestNoFailAfterMaxResult(CatchHitObject hitObject, double startingHealth, bool _)
|
||||||
|
{
|
||||||
|
var healthProcessor = new CatchHealthProcessor(0);
|
||||||
|
healthProcessor.ApplyBeatmap(new CatchBeatmap
|
||||||
|
{
|
||||||
|
HitObjects = { hitObject }
|
||||||
|
});
|
||||||
|
healthProcessor.Health.Value = startingHealth;
|
||||||
|
|
||||||
|
var result = new CatchJudgementResult(hitObject, hitObject.CreateJudgement());
|
||||||
|
result.Type = result.Judgement.MaxResult;
|
||||||
|
healthProcessor.ApplyResult(result);
|
||||||
|
|
||||||
|
Assert.That(healthProcessor.HasFailed, Is.False);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
// 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.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor.Checks
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CheckCatchAbnormalDifficultySettingsTest
|
||||||
|
{
|
||||||
|
private CheckCatchAbnormalDifficultySettings check = null!;
|
||||||
|
|
||||||
|
private readonly IBeatmap beatmap = new Beatmap<HitObject>();
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
check = new CheckCatchAbnormalDifficultySettings();
|
||||||
|
|
||||||
|
beatmap.BeatmapInfo.Ruleset = new CatchRuleset().RulesetInfo;
|
||||||
|
beatmap.Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
ApproachRate = 5,
|
||||||
|
CircleSize = 5,
|
||||||
|
DrainRate = 5,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNormalSettings()
|
||||||
|
{
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApproachRateTwoDecimals()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.ApproachRate = 5.55f;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleSizeTwoDecimals()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.CircleSize = 5.55f;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDrainRateTwoDecimals()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.DrainRate = 5.55f;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateMoreThanOneDecimal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApproachRateUnder()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.ApproachRate = -10;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleSizeUnder()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.CircleSize = -10;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDrainRateUnder()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.DrainRate = -10;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestApproachRateOver()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.ApproachRate = 20;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCircleSizeOver()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.CircleSize = 20;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDrainRateOver()
|
||||||
|
{
|
||||||
|
beatmap.Difficulty.DrainRate = 20;
|
||||||
|
|
||||||
|
var context = getContext();
|
||||||
|
var issues = check.Run(context).ToList();
|
||||||
|
|
||||||
|
Assert.That(issues, Has.Count.EqualTo(1));
|
||||||
|
Assert.That(issues.Single().Template is CheckAbnormalDifficultySettings.IssueTemplateOutOfRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeatmapVerifierContext getContext()
|
||||||
|
{
|
||||||
|
return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,253 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Editor
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public partial class TestSceneCatchReverseSelection : TestSceneEditor
|
||||||
|
{
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset, false);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReverseSelectionTwoFruits()
|
||||||
|
{
|
||||||
|
CatchHitObject[] objects = null!;
|
||||||
|
bool[] newCombos = null!;
|
||||||
|
|
||||||
|
addObjects([
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 200,
|
||||||
|
X = 0,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 400,
|
||||||
|
X = 20,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
AddStep("store objects & new combo data", () =>
|
||||||
|
{
|
||||||
|
objects = getObjects().ToArray();
|
||||||
|
newCombos = getObjectNewCombos().ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
selectEverything();
|
||||||
|
reverseSelection();
|
||||||
|
|
||||||
|
AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse()));
|
||||||
|
AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReverseSelectionThreeFruits()
|
||||||
|
{
|
||||||
|
CatchHitObject[] objects = null!;
|
||||||
|
bool[] newCombos = null!;
|
||||||
|
|
||||||
|
addObjects([
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 200,
|
||||||
|
X = 0,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 400,
|
||||||
|
X = 20,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 600,
|
||||||
|
X = 40,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
AddStep("store objects & new combo data", () =>
|
||||||
|
{
|
||||||
|
objects = getObjects().ToArray();
|
||||||
|
newCombos = getObjectNewCombos().ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
selectEverything();
|
||||||
|
reverseSelection();
|
||||||
|
|
||||||
|
AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse()));
|
||||||
|
AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReverseSelectionFruitAndJuiceStream()
|
||||||
|
{
|
||||||
|
CatchHitObject[] objects = null!;
|
||||||
|
bool[] newCombos = null!;
|
||||||
|
|
||||||
|
addObjects([
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 200,
|
||||||
|
X = 0,
|
||||||
|
},
|
||||||
|
new JuiceStream
|
||||||
|
{
|
||||||
|
StartTime = 400,
|
||||||
|
X = 20,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(),
|
||||||
|
new PathControlPoint(new Vector2(50))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
AddStep("store objects & new combo data", () =>
|
||||||
|
{
|
||||||
|
objects = getObjects().ToArray();
|
||||||
|
newCombos = getObjectNewCombos().ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
selectEverything();
|
||||||
|
reverseSelection();
|
||||||
|
|
||||||
|
AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse()));
|
||||||
|
AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReverseSelectionTwoFruitsAndJuiceStream()
|
||||||
|
{
|
||||||
|
CatchHitObject[] objects = null!;
|
||||||
|
bool[] newCombos = null!;
|
||||||
|
|
||||||
|
addObjects([
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 200,
|
||||||
|
X = 0,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 400,
|
||||||
|
X = 20,
|
||||||
|
},
|
||||||
|
new JuiceStream
|
||||||
|
{
|
||||||
|
StartTime = 600,
|
||||||
|
X = 40,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(),
|
||||||
|
new PathControlPoint(new Vector2(50))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
AddStep("store objects & new combo data", () =>
|
||||||
|
{
|
||||||
|
objects = getObjects().ToArray();
|
||||||
|
newCombos = getObjectNewCombos().ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
selectEverything();
|
||||||
|
reverseSelection();
|
||||||
|
|
||||||
|
AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse()));
|
||||||
|
AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestReverseSelectionTwoCombos()
|
||||||
|
{
|
||||||
|
CatchHitObject[] objects = null!;
|
||||||
|
bool[] newCombos = null!;
|
||||||
|
|
||||||
|
addObjects([
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 200,
|
||||||
|
X = 0,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 400,
|
||||||
|
X = 20,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 600,
|
||||||
|
X = 40,
|
||||||
|
},
|
||||||
|
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 800,
|
||||||
|
NewCombo = true,
|
||||||
|
X = 60,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
X = 80,
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
StartTime = 1200,
|
||||||
|
X = 100,
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
AddStep("store objects & new combo data", () =>
|
||||||
|
{
|
||||||
|
objects = getObjects().ToArray();
|
||||||
|
newCombos = getObjectNewCombos().ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
selectEverything();
|
||||||
|
reverseSelection();
|
||||||
|
|
||||||
|
AddAssert("objects reversed", getObjects, () => Is.EqualTo(objects.Reverse()));
|
||||||
|
AddAssert("new combo positions preserved", getObjectNewCombos, () => Is.EqualTo(newCombos));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addObjects(CatchHitObject[] hitObjects) => AddStep("Add objects", () => EditorBeatmap.AddRange(hitObjects));
|
||||||
|
|
||||||
|
private IEnumerable<CatchHitObject> getObjects() => EditorBeatmap.HitObjects.OfType<CatchHitObject>();
|
||||||
|
|
||||||
|
private IEnumerable<bool> getObjectNewCombos() => getObjects().Select(ho => ho.NewCombo);
|
||||||
|
|
||||||
|
private void selectEverything()
|
||||||
|
{
|
||||||
|
AddStep("Select everything", () =>
|
||||||
|
{
|
||||||
|
EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reverseSelection()
|
||||||
|
{
|
||||||
|
AddStep("Reverse selection", () =>
|
||||||
|
{
|
||||||
|
InputManager.PressKey(Key.LControl);
|
||||||
|
InputManager.Key(Key.G);
|
||||||
|
InputManager.ReleaseKey(Key.LControl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|