mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 11:07:52 +08:00
Merge branch 'master' into gameplay/key-counter-abstraction
This commit is contained in:
commit
b0c09df259
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -13,17 +13,17 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
||||||
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
||||||
- name: Install .NET 3.1.x LTS
|
- name: Install .NET 3.1.x LTS
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "3.1.x"
|
dotnet-version: "3.1.x"
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -77,10 +77,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ jobs:
|
|||||||
# Attempt to upload results even if test fails.
|
# Attempt to upload results even if test fails.
|
||||||
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||||
- name: Upload Test Results
|
- name: Upload Test Results
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
with:
|
with:
|
||||||
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||||
@ -106,10 +106,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
@ -125,10 +125,10 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Install .NET 6.0.x
|
- name: Install .NET 6.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "6.0.x"
|
dotnet-version: "6.0.x"
|
||||||
|
|
||||||
|
18
.github/workflows/diffcalc.yml
vendored
18
.github/workflows/diffcalc.yml
vendored
@ -48,8 +48,8 @@ jobs:
|
|||||||
CONTINUE="no"
|
CONTINUE="no"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "::set-output name=continue::${CONTINUE}"
|
echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=matrix::${MATRIX_JSON}"
|
echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
|
||||||
diffcalc:
|
diffcalc:
|
||||||
name: Run
|
name: Run
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
@ -80,34 +80,34 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"
|
echo "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||||
echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')"
|
echo "repo=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
# Checkout osu
|
# Checkout osu
|
||||||
- name: Checkout osu (master)
|
- name: Checkout osu (master)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: 'master/osu'
|
path: 'master/osu'
|
||||||
- name: Checkout osu (pr)
|
- name: Checkout osu (pr)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
path: 'pr/osu'
|
path: 'pr/osu'
|
||||||
repository: ${{ steps.upstreambranch.outputs.repo }}
|
repository: ${{ steps.upstreambranch.outputs.repo }}
|
||||||
ref: ${{ steps.upstreambranch.outputs.branchname }}
|
ref: ${{ steps.upstreambranch.outputs.branchname }}
|
||||||
|
|
||||||
- name: Checkout osu-difficulty-calculator (master)
|
- name: Checkout osu-difficulty-calculator (master)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-difficulty-calculator
|
repository: ppy/osu-difficulty-calculator
|
||||||
path: 'master/osu-difficulty-calculator'
|
path: 'master/osu-difficulty-calculator'
|
||||||
- name: Checkout osu-difficulty-calculator (pr)
|
- name: Checkout osu-difficulty-calculator (pr)
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
repository: ppy/osu-difficulty-calculator
|
repository: ppy/osu-difficulty-calculator
|
||||||
path: 'pr/osu-difficulty-calculator'
|
path: 'pr/osu-difficulty-calculator'
|
||||||
|
|
||||||
- name: Install .NET 5.0.x
|
- name: Install .NET 5.0.x
|
||||||
uses: actions/setup-dotnet@v1
|
uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: "5.0.x"
|
dotnet-version: "5.0.x"
|
||||||
|
|
||||||
|
2
.github/workflows/sentry-release.yml
vendored
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@v2
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
@ -1,7 +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>9.0</LangVersion>
|
<LangVersion>10.0</LangVersion>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -105,7 +105,7 @@ When it comes to contributing to the project, the two main things you can do to
|
|||||||
|
|
||||||
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
||||||
|
|
||||||
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
|
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
|
||||||
|
|
||||||
## Licence
|
## Licence
|
||||||
|
|
||||||
|
@ -3,15 +3,53 @@
|
|||||||
#
|
#
|
||||||
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
|
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
|
||||||
|
|
||||||
$CSPROJ="osu.Game/osu.Game.csproj"
|
$GAME_CSPROJ="osu.Game/osu.Game.csproj"
|
||||||
|
$ANDROID_PROPS="osu.Android.props"
|
||||||
|
$IOS_PROPS="osu.iOS.props"
|
||||||
$SLN="osu.sln"
|
$SLN="osu.sln"
|
||||||
|
|
||||||
dotnet remove $CSPROJ package ppy.osu.Framework;
|
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework;
|
||||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj;
|
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android;
|
||||||
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
dotnet remove $IOS_PROPS reference ppy.osu.Framework.iOS;
|
||||||
|
|
||||||
|
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj `
|
||||||
|
../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj `
|
||||||
|
../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj `
|
||||||
|
../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj;
|
||||||
|
|
||||||
|
dotnet add $GAME_CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj;
|
||||||
|
dotnet add $ANDROID_PROPS reference ../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj;
|
||||||
|
dotnet add $IOS_PROPS reference ../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj;
|
||||||
|
|
||||||
|
# workaround for dotnet add not inserting $(MSBuildThisFileDirectory) on props files
|
||||||
|
(Get-Content "osu.Android.props") -replace "`"..\\osu-framework", "`"`$(MSBuildThisFileDirectory)..\osu-framework" | Set-Content "osu.Android.props"
|
||||||
|
(Get-Content "osu.iOS.props") -replace "`"..\\osu-framework", "`"`$(MSBuildThisFileDirectory)..\osu-framework" | Set-Content "osu.iOS.props"
|
||||||
|
|
||||||
|
# needed because iOS framework nupkg includes a set of properties to work around certain issues during building,
|
||||||
|
# and those get ignored when referencing framework via project, threfore we have to manually include it via props reference.
|
||||||
|
(Get-Content "osu.iOS.props") |
|
||||||
|
Foreach-Object {
|
||||||
|
if ($_ -match "</Project>")
|
||||||
|
{
|
||||||
|
" <Import Project=`"`$(MSBuildThisFileDirectory)../osu-framework/osu.Framework.iOS.props`"/>"
|
||||||
|
}
|
||||||
|
|
||||||
|
$_
|
||||||
|
} | Set-Content "osu.iOS.props"
|
||||||
|
|
||||||
|
$TMP=New-TemporaryFile
|
||||||
|
|
||||||
$SLNF=Get-Content "osu.Desktop.slnf" | ConvertFrom-Json
|
$SLNF=Get-Content "osu.Desktop.slnf" | ConvertFrom-Json
|
||||||
$TMP=New-TemporaryFile
|
|
||||||
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj")
|
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj")
|
||||||
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
|
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
|
||||||
Move-Item -Path $TMP -Destination "osu.Desktop.slnf" -Force
|
Move-Item -Path $TMP -Destination "osu.Desktop.slnf" -Force
|
||||||
|
|
||||||
|
$SLNF=Get-Content "osu.Android.slnf" | ConvertFrom-Json
|
||||||
|
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj")
|
||||||
|
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
|
||||||
|
Move-Item -Path $TMP -Destination "osu.Android.slnf" -Force
|
||||||
|
|
||||||
|
$SLNF=Get-Content "osu.iOS.slnf" | ConvertFrom-Json
|
||||||
|
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj")
|
||||||
|
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
|
||||||
|
Move-Item -Path $TMP -Destination "osu.iOS.slnf" -Force
|
||||||
|
@ -5,14 +5,41 @@
|
|||||||
#
|
#
|
||||||
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
|
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
|
||||||
|
|
||||||
CSPROJ="osu.Game/osu.Game.csproj"
|
GAME_CSPROJ="osu.Game/osu.Game.csproj"
|
||||||
|
ANDROID_PROPS="osu.Android.props"
|
||||||
|
IOS_PROPS="osu.iOS.props"
|
||||||
SLN="osu.sln"
|
SLN="osu.sln"
|
||||||
|
|
||||||
dotnet remove $CSPROJ package ppy.osu.Framework
|
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework
|
||||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj
|
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android
|
||||||
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
dotnet remove $IOS_PROPS reference ppy.osu.Framework.iOS
|
||||||
|
|
||||||
|
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj \
|
||||||
|
../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj \
|
||||||
|
../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj \
|
||||||
|
../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj
|
||||||
|
|
||||||
|
dotnet add $GAME_CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
||||||
|
dotnet add $ANDROID_PROPS reference ../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj
|
||||||
|
dotnet add $IOS_PROPS reference ../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj
|
||||||
|
|
||||||
|
# workaround for dotnet add not inserting $(MSBuildThisFileDirectory) on props files
|
||||||
|
sed -i.bak 's:"..\\osu-framework:"$(MSBuildThisFileDirectory)..\\osu-framework:g' ./osu.Android.props && rm osu.Android.props.bak
|
||||||
|
sed -i.bak 's:"..\\osu-framework:"$(MSBuildThisFileDirectory)..\\osu-framework:g' ./osu.iOS.props && rm osu.iOS.props.bak
|
||||||
|
|
||||||
|
# needed because iOS framework nupkg includes a set of properties to work around certain issues during building,
|
||||||
|
# and those get ignored when referencing framework via project, threfore we have to manually include it via props reference.
|
||||||
|
sed -i.bak '/<\/Project>/i\
|
||||||
|
<Import Project=\"$(MSBuildThisFileDirectory)../osu-framework/osu.Framework.iOS.props\"/>\
|
||||||
|
' ./osu.iOS.props && rm osu.iOS.props.bak
|
||||||
|
|
||||||
SLNF="osu.Desktop.slnf"
|
|
||||||
tmp=$(mktemp)
|
tmp=$(mktemp)
|
||||||
|
|
||||||
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj"]' osu.Desktop.slnf > $tmp
|
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj"]' osu.Desktop.slnf > $tmp
|
||||||
mv -f $tmp $SLNF
|
mv -f $tmp osu.Desktop.slnf
|
||||||
|
|
||||||
|
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj"]' osu.Android.slnf > $tmp
|
||||||
|
mv -f $tmp osu.Android.slnf
|
||||||
|
|
||||||
|
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj"]' osu.iOS.slnf > $tmp
|
||||||
|
mv -f $tmp osu.iOS.slnf
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.228.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.403.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -139,7 +138,17 @@ namespace osu.Desktop
|
|||||||
|
|
||||||
desktopWindow.CursorState |= CursorState.Hidden;
|
desktopWindow.CursorState |= CursorState.Hidden;
|
||||||
desktopWindow.Title = Name;
|
desktopWindow.Title = Name;
|
||||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
desktopWindow.DragDrop += f =>
|
||||||
|
{
|
||||||
|
// on macOS, URL associations are handled via SDL_DROPFILE events.
|
||||||
|
if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
HandleLink(f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDrop(new[] { f });
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||||
@ -151,10 +160,6 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
lock (importableFiles)
|
lock (importableFiles)
|
||||||
{
|
{
|
||||||
string firstExtension = Path.GetExtension(filePaths.First());
|
|
||||||
|
|
||||||
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
|
||||||
|
|
||||||
importableFiles.AddRange(filePaths);
|
importableFiles.AddRange(filePaths);
|
||||||
|
|
||||||
Logger.Log($"Adding {filePaths.Length} files for import");
|
Logger.Log($"Adding {filePaths.Length} files for import");
|
||||||
|
123
osu.Game.Benchmarks/BenchmarkCarouselFilter.cs
Normal file
123
osu.Game.Benchmarks/BenchmarkCarouselFilter.cs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Screens.Select;
|
||||||
|
using osu.Game.Screens.Select.Carousel;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkCarouselFilter : BenchmarkTest
|
||||||
|
{
|
||||||
|
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo
|
||||||
|
{
|
||||||
|
ShortName = "osu",
|
||||||
|
OnlineID = 0
|
||||||
|
},
|
||||||
|
StarRating = 4.0d,
|
||||||
|
Difficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
ApproachRate = 5.0f,
|
||||||
|
DrainRate = 3.0f,
|
||||||
|
CircleSize = 2.0f,
|
||||||
|
},
|
||||||
|
Metadata = new BeatmapMetadata
|
||||||
|
{
|
||||||
|
Artist = "The Artist",
|
||||||
|
ArtistUnicode = "check unicode too",
|
||||||
|
Title = "Title goes here",
|
||||||
|
TitleUnicode = "Title goes here",
|
||||||
|
Author = { Username = "The Author" },
|
||||||
|
Source = "unit tests",
|
||||||
|
Tags = "look for tags too",
|
||||||
|
},
|
||||||
|
DifficultyName = "version as well",
|
||||||
|
Length = 2500,
|
||||||
|
BPM = 160,
|
||||||
|
BeatDivisor = 12,
|
||||||
|
Status = BeatmapOnlineStatus.Loved
|
||||||
|
};
|
||||||
|
|
||||||
|
private CarouselBeatmap carouselBeatmap = null!;
|
||||||
|
private FilterCriteria criteria1 = null!;
|
||||||
|
private FilterCriteria criteria2 = null!;
|
||||||
|
private FilterCriteria criteria3 = null!;
|
||||||
|
private FilterCriteria criteria4 = null!;
|
||||||
|
private FilterCriteria criteria5 = null!;
|
||||||
|
private FilterCriteria criteria6 = null!;
|
||||||
|
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
var beatmap = getExampleBeatmap();
|
||||||
|
beatmap.OnlineID = 20201010;
|
||||||
|
beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 };
|
||||||
|
carouselBeatmap = new CarouselBeatmap(beatmap);
|
||||||
|
criteria1 = new FilterCriteria();
|
||||||
|
criteria2 = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { ShortName = "catch" }
|
||||||
|
};
|
||||||
|
criteria3 = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { OnlineID = 6 },
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
BPM = new FilterCriteria.OptionalRange<double>
|
||||||
|
{
|
||||||
|
IsUpperInclusive = false,
|
||||||
|
Max = 160d
|
||||||
|
}
|
||||||
|
};
|
||||||
|
criteria4 = new FilterCriteria
|
||||||
|
{
|
||||||
|
Ruleset = new RulesetInfo { OnlineID = 6 },
|
||||||
|
AllowConvertedBeatmaps = true,
|
||||||
|
SearchText = "an artist"
|
||||||
|
};
|
||||||
|
criteria5 = new FilterCriteria
|
||||||
|
{
|
||||||
|
Creator = new FilterCriteria.OptionalTextFilter { SearchTerm = "the author AND then something else" }
|
||||||
|
};
|
||||||
|
criteria6 = new FilterCriteria { SearchText = "20201010" };
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CarouselBeatmapFilter()
|
||||||
|
{
|
||||||
|
carouselBeatmap.Filter(criteria1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CriteriaMatchingSpecificRuleset()
|
||||||
|
{
|
||||||
|
carouselBeatmap.Filter(criteria2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CriteriaMatchingRangeMax()
|
||||||
|
{
|
||||||
|
carouselBeatmap.Filter(criteria3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CriteriaMatchingTerms()
|
||||||
|
{
|
||||||
|
carouselBeatmap.Filter(criteria4);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CriteriaMatchingCreator()
|
||||||
|
{
|
||||||
|
carouselBeatmap.Filter(criteria5);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void CriteriaMatchingBeatmapIDs()
|
||||||
|
{
|
||||||
|
carouselBeatmap.Filter(criteria6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
ExtendedMaxValue = 11,
|
ExtendedMaxValue = 11,
|
||||||
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||||
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
|||||||
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||||
{
|
{
|
||||||
Precision = 0.1f,
|
Precision = 0.1f,
|
||||||
MinValue = 1,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
ExtendedMaxValue = 11,
|
ExtendedMaxValue = 11,
|
||||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||||
|
@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
Size = new Vector2(BASE_SIZE);
|
Size = new Vector2(BASE_SIZE);
|
||||||
|
|
||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
@ -333,8 +334,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
||||||
|
|
||||||
body.Scale = scaleFromDirection;
|
body.Scale = scaleFromDirection;
|
||||||
caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
// Inverse of catcher scale is applied here, as catcher gets scaled by circle size and so do the incoming fruit.
|
||||||
|
caughtObjectContainer.Scale = (1 / Scale.X) * (flipCatcherPlate ? scaleFromDirection : Vector2.One);
|
||||||
|
hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
||||||
|
|
||||||
// Correct overshooting.
|
// Correct overshooting.
|
||||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
@ -10,5 +13,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
public partial class TestSceneManiaPlayer : PlayerTestScene
|
public partial class TestSceneManiaPlayer : PlayerTestScene
|
||||||
{
|
{
|
||||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
public override void SetUpSteps()
|
||||||
|
{
|
||||||
|
base.SetUpSteps();
|
||||||
|
|
||||||
|
AddStep("change direction to down", () => changeDirectionTo(ManiaScrollingDirection.Down));
|
||||||
|
AddStep("change direction to up", () => changeDirectionTo(ManiaScrollingDirection.Up));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void changeDirectionTo(ManiaScrollingDirection direction)
|
||||||
|
{
|
||||||
|
var rulesetConfig = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(new ManiaRuleset()).AsNonNull();
|
||||||
|
rulesetConfig.SetValue(ManiaRulesetSetting.ScrollDirection, direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Position and resize the body to lie half-way under the head and the tail notes.
|
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||||
|
// The rationale for this is account for heads/tails with corner radius.
|
||||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
||||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||||
|
|
||||||
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||||
|
|
||||||
public DrawableHoldNoteTail()
|
public DrawableHoldNoteTail()
|
||||||
: this(null)
|
: this(null)
|
||||||
|
@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
largeFaint = new Container
|
largeFaint = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Blending = BlendingParameters.Additive,
|
Blending = BlendingParameters.Additive,
|
||||||
@ -80,11 +79,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
if (direction.NewValue == ScrollingDirection.Up)
|
if (direction.NewValue == ScrollingDirection.Up)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
|
largeFaint.Anchor = Anchor.TopCentre;
|
||||||
|
largeFaint.Origin = Anchor.TopCentre;
|
||||||
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
|
largeFaint.Anchor = Anchor.BottomCentre;
|
||||||
|
largeFaint.Origin = Anchor.BottomCentre;
|
||||||
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private void load(IScrollingInfo scrollingInfo)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO;
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
|
@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||||
{
|
{
|
||||||
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
|
||||||
|
|
||||||
private Drawable background = null!;
|
private Drawable background = null!;
|
||||||
private Box foreground = null!;
|
private ArgonHoldNoteHittingLayer hittingLayer = null!;
|
||||||
|
|
||||||
public ArgonHoldBodyPiece()
|
public ArgonHoldBodyPiece()
|
||||||
{
|
{
|
||||||
@ -32,7 +31,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
// Without this, the width of the body will be slightly larger than the head/tail.
|
// Without this, the width of the body will be slightly larger than the head/tail.
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||||
Blending = BlendingParameters.Additive;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -41,12 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||||
foreground = new Box
|
hittingLayer = new ArgonHoldNoteHittingLayer()
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
Alpha = 0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (drawableObject != null)
|
if (drawableObject != null)
|
||||||
@ -54,44 +47,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
var holdNote = (DrawableHoldNote)drawableObject;
|
var holdNote = (DrawableHoldNote)drawableObject;
|
||||||
|
|
||||||
AccentColour.BindTo(holdNote.AccentColour);
|
AccentColour.BindTo(holdNote.AccentColour);
|
||||||
IsHitting.BindTo(holdNote.IsHitting);
|
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||||
}
|
}
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
background.Colour = colour.NewValue.Darken(1.2f);
|
background.Colour = colour.NewValue.Darken(0.6f);
|
||||||
foreground.Colour = colour.NewValue.Opacity(0.2f);
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
IsHitting.BindValueChanged(hitting =>
|
|
||||||
{
|
|
||||||
const float animation_length = 50;
|
|
||||||
|
|
||||||
foreground.ClearTransforms();
|
|
||||||
|
|
||||||
if (hitting.NewValue)
|
|
||||||
{
|
|
||||||
// wait for the next sync point
|
|
||||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
|
||||||
|
|
||||||
using (foreground.BeginDelayedSequence(synchronisedOffset))
|
|
||||||
{
|
|
||||||
foreground.FadeTo(1, animation_length).Then()
|
|
||||||
.FadeTo(0.5f, animation_length)
|
|
||||||
.Loop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
foreground.FadeOut(animation_length);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Recycle()
|
public void Recycle()
|
||||||
{
|
{
|
||||||
foreground.ClearTransforms();
|
hittingLayer.Recycle();
|
||||||
foreground.Alpha = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
internal partial class ArgonHoldNoteHeadPiece : ArgonNotePiece
|
||||||
|
{
|
||||||
|
protected override Drawable CreateIcon() => new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Y = 2,
|
||||||
|
Size = new Vector2(20, 5),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using Box = osu.Framework.Graphics.Shapes.Box;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||||
|
{
|
||||||
|
public partial class ArgonHoldNoteHittingLayer : Box
|
||||||
|
{
|
||||||
|
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||||
|
public readonly Bindable<bool> IsHitting = new Bindable<bool>();
|
||||||
|
|
||||||
|
public ArgonHoldNoteHittingLayer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Blending = BlendingParameters.Additive;
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
AccentColour.BindValueChanged(colour =>
|
||||||
|
{
|
||||||
|
Colour = colour.NewValue.Lighten(0.2f).Opacity(0.3f);
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
IsHitting.BindValueChanged(hitting =>
|
||||||
|
{
|
||||||
|
const float animation_length = 80;
|
||||||
|
|
||||||
|
ClearTransforms();
|
||||||
|
|
||||||
|
if (hitting.NewValue)
|
||||||
|
{
|
||||||
|
// wait for the next sync point
|
||||||
|
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(synchronisedOffset))
|
||||||
|
{
|
||||||
|
this.FadeTo(1, animation_length, Easing.OutSine).Then()
|
||||||
|
.FadeTo(0.5f, animation_length, Easing.InSine)
|
||||||
|
.Loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.FadeOut(animation_length);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Recycle()
|
||||||
|
{
|
||||||
|
ClearTransforms();
|
||||||
|
Alpha = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,8 +5,10 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -16,47 +18,68 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private DrawableHitObject? drawableObject { get; set; }
|
||||||
|
|
||||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box shadeBackground;
|
private readonly Box foreground;
|
||||||
private readonly Box shadeForeground;
|
private readonly ArgonHoldNoteHittingLayer hittingLayer;
|
||||||
|
private readonly Box foregroundAdditive;
|
||||||
|
|
||||||
public ArgonHoldNoteTailPiece()
|
public ArgonHoldNoteTailPiece()
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||||
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
|
||||||
Masking = true;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
},
|
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
Height = ArgonNotePiece.NOTE_HEIGHT,
|
||||||
Anchor = Anchor.BottomCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
shadeForeground = new Box
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black),
|
||||||
|
// Avoid ugly single pixel overlap.
|
||||||
|
Height = 0.9f,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||||
|
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||||
|
Masking = true,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
foreground = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
hittingLayer = new ArgonHoldNoteHittingLayer(),
|
||||||
|
foregroundAdditive = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Height = 0.5f,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
{
|
{
|
||||||
direction.BindTo(scrollingInfo.Direction);
|
direction.BindTo(scrollingInfo.Direction);
|
||||||
direction.BindValueChanged(onDirectionChanged, true);
|
direction.BindValueChanged(onDirectionChanged, true);
|
||||||
@ -65,9 +88,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
accentColour.BindTo(drawableObject.AccentColour);
|
accentColour.BindTo(drawableObject.AccentColour);
|
||||||
accentColour.BindValueChanged(onAccentChanged, true);
|
accentColour.BindValueChanged(onAccentChanged, true);
|
||||||
|
|
||||||
|
drawableObject.HitObjectApplied += hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void hitObjectApplied(DrawableHitObject drawableHitObject)
|
||||||
|
{
|
||||||
|
var holdNoteTail = (DrawableHoldNoteTail)drawableHitObject;
|
||||||
|
|
||||||
|
hittingLayer.Recycle();
|
||||||
|
|
||||||
|
hittingLayer.AccentColour.UnbindBindings();
|
||||||
|
hittingLayer.AccentColour.BindTo(holdNoteTail.HoldNote.AccentColour);
|
||||||
|
|
||||||
|
hittingLayer.IsHitting.UnbindBindings();
|
||||||
|
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNoteTail.HoldNote.IsHitting);
|
||||||
|
}
|
||||||
|
|
||||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
{
|
{
|
||||||
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
||||||
@ -75,8 +113,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||||
{
|
{
|
||||||
shadeBackground.Colour = accent.NewValue.Darken(1.7f);
|
foreground.Colour = accent.NewValue.Darken(0.6f); // matches body
|
||||||
shadeForeground.Colour = accent.NewValue.Darken(1.1f);
|
|
||||||
|
foregroundAdditive.Colour = ColourInfo.GradientVertical(
|
||||||
|
accent.NewValue.Opacity(0.4f),
|
||||||
|
accent.NewValue.Opacity(0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (drawableObject != null)
|
||||||
|
drawableObject.HitObjectApplied -= hitObjectApplied;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||||
|
|
||||||
private readonly Box colouredBox;
|
private readonly Box colouredBox;
|
||||||
private readonly Box shadow;
|
|
||||||
|
|
||||||
public ArgonNotePiece()
|
public ArgonNotePiece()
|
||||||
{
|
{
|
||||||
@ -36,11 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
CornerRadius = CORNER_RADIUS;
|
CornerRadius = CORNER_RADIUS;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
shadow = new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black)
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -65,17 +65,21 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = CORNER_RADIUS * 2,
|
Height = CORNER_RADIUS * 2,
|
||||||
},
|
},
|
||||||
new SpriteIcon
|
CreateIcon(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual Drawable CreateIcon() => new SpriteIcon
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Y = 4,
|
Y = 4,
|
||||||
|
// TODO: replace with a non-squashed version.
|
||||||
|
// The 0.7f height scale should be removed.
|
||||||
Icon = FontAwesome.Solid.AngleDown,
|
Icon = FontAwesome.Solid.AngleDown,
|
||||||
Size = new Vector2(20),
|
Size = new Vector2(20),
|
||||||
Scale = new Vector2(1, 0.7f)
|
Scale = new Vector2(1, 0.7f)
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||||
@ -105,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
accent.NewValue.Lighten(0.1f),
|
accent.NewValue.Lighten(0.1f),
|
||||||
accent.NewValue
|
accent.NewValue
|
||||||
);
|
);
|
||||||
|
|
||||||
shadow.Colour = accent.NewValue.Darken(0.5f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return new ArgonHoldNoteTailPiece();
|
return new ArgonHoldNoteTailPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.HoldNoteHead:
|
case ManiaSkinComponents.HoldNoteHead:
|
||||||
|
return new ArgonHoldNoteHeadPiece();
|
||||||
|
|
||||||
case ManiaSkinComponents.Note:
|
case ManiaSkinComponents.Note:
|
||||||
return new ArgonNotePiece();
|
return new ArgonNotePiece();
|
||||||
|
|
||||||
@ -69,12 +71,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
return base.GetDrawableComponent(lookup);
|
return base.GetDrawableComponent(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly Color4 colour_special_column = new Color4(169, 106, 255, 255);
|
||||||
|
|
||||||
|
private const int total_colours = 6;
|
||||||
|
|
||||||
|
private static readonly Color4 colour_yellow = new Color4(255, 197, 40, 255);
|
||||||
|
private static readonly Color4 colour_orange = new Color4(252, 109, 1, 255);
|
||||||
|
private static readonly Color4 colour_pink = new Color4(213, 35, 90, 255);
|
||||||
|
private static readonly Color4 colour_purple = new Color4(203, 60, 236, 255);
|
||||||
|
private static readonly Color4 colour_cyan = new Color4(72, 198, 255, 255);
|
||||||
|
private static readonly Color4 colour_green = new Color4(100, 192, 92, 255);
|
||||||
|
|
||||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||||
{
|
{
|
||||||
int column = maniaLookup.ColumnIndex ?? 0;
|
int columnIndex = maniaLookup.ColumnIndex ?? 0;
|
||||||
var stage = beatmap.GetStageForColumnIndex(column);
|
var stage = beatmap.GetStageForColumnIndex(columnIndex);
|
||||||
|
|
||||||
switch (maniaLookup.Lookup)
|
switch (maniaLookup.Lookup)
|
||||||
{
|
{
|
||||||
@ -87,53 +100,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||||
return SkinUtils.As<TValue>(new Bindable<float>(
|
return SkinUtils.As<TValue>(new Bindable<float>(
|
||||||
stage.IsSpecialColumn(column) ? 120 : 60
|
stage.IsSpecialColumn(columnIndex) ? 120 : 60
|
||||||
));
|
));
|
||||||
|
|
||||||
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||||
|
|
||||||
Color4 colour;
|
var colour = getColourForLayout(columnIndex, stage);
|
||||||
|
|
||||||
const int total_colours = 7;
|
|
||||||
|
|
||||||
if (stage.IsSpecialColumn(column))
|
|
||||||
colour = new Color4(159, 101, 255, 255);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
switch (column % total_colours)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
colour = new Color4(240, 216, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
colour = new Color4(240, 101, 0, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
colour = new Color4(240, 0, 130, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
colour = new Color4(192, 0, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 4:
|
|
||||||
colour = new Color4(0, 96, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 5:
|
|
||||||
colour = new Color4(0, 226, 240, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 6:
|
|
||||||
colour = new Color4(0, 240, 96, 255);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||||
}
|
}
|
||||||
@ -141,5 +113,203 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
|
|
||||||
return base.GetConfig<TLookup, TValue>(lookup);
|
return base.GetConfig<TLookup, TValue>(lookup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Color4 getColourForLayout(int columnIndex, StageDefinition stage)
|
||||||
|
{
|
||||||
|
// Account for cases like dual-stage (assume that all stages have the same column count for now).
|
||||||
|
columnIndex %= stage.Columns;
|
||||||
|
|
||||||
|
// For now, these are defined per column count as per https://user-images.githubusercontent.com/50823728/218038463-b450f46c-ef21-4551-b133-f866be59970c.png
|
||||||
|
// See https://github.com/ppy/osu/discussions/21996 for discussion.
|
||||||
|
switch (stage.Columns)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return colour_yellow;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_green;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 5:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_green;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 6:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_yellow;
|
||||||
|
|
||||||
|
case 3: return colour_cyan;
|
||||||
|
|
||||||
|
case 4: return colour_purple;
|
||||||
|
|
||||||
|
case 5: return colour_pink;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 7:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_pink;
|
||||||
|
|
||||||
|
case 1: return colour_cyan;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_special_column;
|
||||||
|
|
||||||
|
case 4: return colour_green;
|
||||||
|
|
||||||
|
case 5: return colour_cyan;
|
||||||
|
|
||||||
|
case 6: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 8:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_yellow;
|
||||||
|
|
||||||
|
case 5: return colour_orange;
|
||||||
|
|
||||||
|
case 6: return colour_pink;
|
||||||
|
|
||||||
|
case 7: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 9:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_special_column;
|
||||||
|
|
||||||
|
case 5: return colour_yellow;
|
||||||
|
|
||||||
|
case 6: return colour_orange;
|
||||||
|
|
||||||
|
case 7: return colour_pink;
|
||||||
|
|
||||||
|
case 8: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
|
||||||
|
case 10:
|
||||||
|
switch (columnIndex)
|
||||||
|
{
|
||||||
|
case 0: return colour_purple;
|
||||||
|
|
||||||
|
case 1: return colour_pink;
|
||||||
|
|
||||||
|
case 2: return colour_orange;
|
||||||
|
|
||||||
|
case 3: return colour_yellow;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
case 6: return colour_yellow;
|
||||||
|
|
||||||
|
case 7: return colour_orange;
|
||||||
|
|
||||||
|
case 8: return colour_pink;
|
||||||
|
|
||||||
|
case 9: return colour_purple;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback for unhandled scenarios
|
||||||
|
|
||||||
|
if (stage.IsSpecialColumn(columnIndex))
|
||||||
|
return colour_special_column;
|
||||||
|
|
||||||
|
switch (columnIndex % total_colours)
|
||||||
|
{
|
||||||
|
case 0: return colour_yellow;
|
||||||
|
|
||||||
|
case 1: return colour_orange;
|
||||||
|
|
||||||
|
case 2: return colour_pink;
|
||||||
|
|
||||||
|
case 3: return colour_purple;
|
||||||
|
|
||||||
|
case 4: return colour_cyan;
|
||||||
|
|
||||||
|
case 5: return colour_green;
|
||||||
|
|
||||||
|
default: throw new ArgumentOutOfRangeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,42 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
assertKeyCounter(1, 1);
|
assertKeyCounter(1, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPositionalTrackingAfterLongDistanceTravelled()
|
||||||
|
{
|
||||||
|
// When a single touch has already travelled enough distance on screen, it should remain as the positional
|
||||||
|
// tracking touch until released (unless a direct touch occurs).
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 0);
|
||||||
|
checkPressed(OsuAction.LeftButton);
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// cover some distance
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch1);
|
||||||
|
|
||||||
|
beginTouch(TouchSource.Touch2);
|
||||||
|
|
||||||
|
assertKeyCounter(1, 1);
|
||||||
|
checkNotPressed(OsuAction.LeftButton);
|
||||||
|
checkPressed(OsuAction.RightButton);
|
||||||
|
// in this case, touch 2 should not become the positional tracking touch.
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
|
||||||
|
// even if the second touch moves on the screen, the original tracking touch is retained.
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||||
|
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||||
|
|
||||||
|
checkPosition(TouchSource.Touch1);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
||||||
{
|
{
|
||||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
|||||||
protected override bool AlwaysShowWhenSelected => true;
|
protected override bool AlwaysShowWhenSelected => true;
|
||||||
|
|
||||||
protected override bool ShouldBeAlive => base.ShouldBeAlive
|
protected override bool ShouldBeAlive => base.ShouldBeAlive
|
||||||
|| (ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
|| (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
||||||
|
|
||||||
protected OsuSelectionBlueprint(T hitObject)
|
protected OsuSelectionBlueprint(T hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
|
@ -252,13 +252,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
|||||||
renderer.SetBlend(BlendingParameters.Additive);
|
renderer.SetBlend(BlendingParameters.Additive);
|
||||||
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||||
|
|
||||||
TextureShader.Bind();
|
BindTextureShader(renderer);
|
||||||
|
|
||||||
texture.Bind();
|
texture.Bind();
|
||||||
|
|
||||||
for (int i = 0; i < points.Count; i++)
|
for (int i = 0; i < points.Count; i++)
|
||||||
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
||||||
|
|
||||||
TextureShader.Unbind();
|
UnbindTextureShader(renderer);
|
||||||
renderer.PopLocalMatrix();
|
renderer.PopLocalMatrix();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
|
|||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
using osu.Framework.Graphics.Rendering.Vertices;
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
@ -255,15 +256,23 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
Source.parts.CopyTo(parts, 0);
|
Source.parts.CopyTo(parts, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<CursorTrailParameters> cursorTrailParameters;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
|
|
||||||
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
|
|
||||||
|
cursorTrailParameters ??= renderer.CreateUniformBuffer<CursorTrailParameters>();
|
||||||
|
cursorTrailParameters.Data = cursorTrailParameters.Data with
|
||||||
|
{
|
||||||
|
FadeClock = time,
|
||||||
|
FadeExponent = fadeExponent
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
shader.BindUniformBlock("m_CursorTrailParameters", cursorTrailParameters);
|
||||||
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
|
||||||
|
|
||||||
texture.Bind();
|
texture.Bind();
|
||||||
|
|
||||||
@ -323,6 +332,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
cursorTrailParameters?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct CursorTrailParameters
|
||||||
|
{
|
||||||
|
public UniformFloat FadeClock;
|
||||||
|
public UniformFloat FadeExponent;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The distance (in local pixels) that a touch must move before being considered a permanent tracking touch.
|
||||||
|
/// After this distance is covered, any extra touches on the screen will be considered as button inputs, unless
|
||||||
|
/// a new touch directly interacts with a hit circle.
|
||||||
|
/// </summary>
|
||||||
|
private const float distance_before_position_tracking_lock_in = 100;
|
||||||
|
|
||||||
private TrackedTouch? positionTrackingTouch;
|
private TrackedTouch? positionTrackingTouch;
|
||||||
|
|
||||||
private readonly OsuInputManager osuInputManager;
|
private readonly OsuInputManager osuInputManager;
|
||||||
@ -97,26 +104,32 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
|
// ..or if the current position tracking touch was not a direct touch (and didn't travel across the screen too far).
|
||||||
if (!positionTrackingTouch.DirectTouch)
|
if (!positionTrackingTouch.DirectTouch && positionTrackingTouch.DistanceTravelled < distance_before_position_tracking_lock_in)
|
||||||
{
|
{
|
||||||
positionTrackingTouch = newTouch;
|
positionTrackingTouch = newTouch;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
||||||
// If it was a direct touch and still has its action pressed, that action should be released.
|
// If it still has its action pressed, that action should be released.
|
||||||
//
|
//
|
||||||
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
||||||
if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction)
|
if (positionTrackingTouch.Action is OsuAction touchAction)
|
||||||
{
|
{
|
||||||
osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction);
|
osuInputManager.KeyBindingContainer.TriggerReleased(touchAction);
|
||||||
positionTrackingTouch.Action = null;
|
positionTrackingTouch.Action = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTouchMovement(TouchEvent touchEvent)
|
private void handleTouchMovement(TouchEvent touchEvent)
|
||||||
{
|
{
|
||||||
|
if (touchEvent is TouchMoveEvent moveEvent)
|
||||||
|
{
|
||||||
|
var trackedTouch = trackedTouches.Single(t => t.Source == touchEvent.Touch.Source);
|
||||||
|
trackedTouch.DistanceTravelled += moveEvent.Delta.Length;
|
||||||
|
}
|
||||||
|
|
||||||
// Movement should only be tracked for the most recent touch.
|
// Movement should only be tracked for the most recent touch.
|
||||||
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
||||||
return;
|
return;
|
||||||
@ -148,8 +161,16 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public OsuAction? Action;
|
public OsuAction? Action;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the touch was on a hit circle receptor.
|
||||||
|
/// </summary>
|
||||||
public readonly bool DirectTouch;
|
public readonly bool DirectTouch;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The total distance on screen travelled by this touch (in local pixels).
|
||||||
|
/// </summary>
|
||||||
|
public float DistanceTravelled;
|
||||||
|
|
||||||
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
||||||
{
|
{
|
||||||
Source = source;
|
Source = source;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<OutputType>Library</OutputType>
|
<OutputType>Library</OutputType>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
<Description>click the circles. to the beat.</Description>
|
<Description>click the circles. to the beat.</Description>
|
||||||
|
<LangVersion>10</LangVersion>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Label="Nuget">
|
<PropertyGroup Label="Nuget">
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
using osu.Game.Tests;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
using osu.Game.Tests;
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
212
osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs
Normal file
212
osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Replays;
|
||||||
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||||
|
{
|
||||||
|
public partial class TestSceneTaikoModSingleTap : TaikoModTestScene
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestInputAlternate() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TaikoModSingleTap(),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 100,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 300,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 700,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(120),
|
||||||
|
new TaikoReplayFrame(300, TaikoAction.LeftRim),
|
||||||
|
new TaikoReplayFrame(320),
|
||||||
|
new TaikoReplayFrame(500, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(520),
|
||||||
|
new TaikoReplayFrame(700, TaikoAction.LeftRim),
|
||||||
|
new TaikoReplayFrame(720),
|
||||||
|
},
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputSameKey() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TaikoModSingleTap(),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 100,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 300,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 700,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(120),
|
||||||
|
new TaikoReplayFrame(300, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(320),
|
||||||
|
new TaikoReplayFrame(500, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(520),
|
||||||
|
new TaikoReplayFrame(700, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(720),
|
||||||
|
},
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 4
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputIntro() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TaikoModSingleTap(),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 100,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(0, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(20),
|
||||||
|
new TaikoReplayFrame(100, TaikoAction.LeftRim),
|
||||||
|
new TaikoReplayFrame(120),
|
||||||
|
},
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputStrong() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TaikoModSingleTap(),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 100,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 300,
|
||||||
|
Type = HitType.Rim,
|
||||||
|
IsStrong = true
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 500,
|
||||||
|
Type = HitType.Rim,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(120),
|
||||||
|
new TaikoReplayFrame(300, TaikoAction.LeftRim),
|
||||||
|
new TaikoReplayFrame(320),
|
||||||
|
new TaikoReplayFrame(500, TaikoAction.LeftRim),
|
||||||
|
new TaikoReplayFrame(520),
|
||||||
|
},
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputBreaks() => CreateModTest(new ModTestData
|
||||||
|
{
|
||||||
|
Mod = new TaikoModSingleTap(),
|
||||||
|
Autoplay = false,
|
||||||
|
Beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
Breaks = new List<BreakPeriod>
|
||||||
|
{
|
||||||
|
new BreakPeriod(100, 1600),
|
||||||
|
},
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 100,
|
||||||
|
Type = HitType.Rim
|
||||||
|
},
|
||||||
|
new Hit
|
||||||
|
{
|
||||||
|
StartTime = 2000,
|
||||||
|
Type = HitType.Rim,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ReplayFrames = new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(120),
|
||||||
|
// Press different key after break but before hit object.
|
||||||
|
new TaikoReplayFrame(1900, TaikoAction.LeftRim),
|
||||||
|
new TaikoReplayFrame(1820),
|
||||||
|
// Press original key at second hitobject and ensure it has been hit.
|
||||||
|
new TaikoReplayFrame(2000, TaikoAction.RightRim),
|
||||||
|
new TaikoReplayFrame(2020),
|
||||||
|
},
|
||||||
|
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -73,11 +73,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint
|
beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint
|
||||||
{
|
{
|
||||||
BeatLength = beat_length,
|
BeatLength = beat_length,
|
||||||
TimeSignature = new TimeSignature(time_signature_numerator)
|
TimeSignature = new TimeSignature(time_signature_numerator),
|
||||||
|
OmitFirstBarLine = true
|
||||||
});
|
});
|
||||||
|
|
||||||
beatmap.ControlPointInfo.Add(start_time, new EffectControlPoint { OmitFirstBarLine = true });
|
|
||||||
|
|
||||||
var barlines = new BarLineGenerator<BarLine>(beatmap).BarLines;
|
var barlines = new BarLineGenerator<BarLine>(beatmap).BarLines;
|
||||||
|
|
||||||
AddAssert("first barline ommited", () => barlines.All(b => b.StartTime != start_time));
|
AddAssert("first barline ommited", () => barlines.All(b => b.StartTime != start_time));
|
||||||
|
@ -72,7 +72,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
converted.ControlPointInfo.Add(hitObject.StartTime, new EffectControlPoint
|
converted.ControlPointInfo.Add(hitObject.StartTime, new EffectControlPoint
|
||||||
{
|
{
|
||||||
KiaiMode = currentEffectPoint.KiaiMode,
|
KiaiMode = currentEffectPoint.KiaiMode,
|
||||||
OmitFirstBarLine = currentEffectPoint.OmitFirstBarLine,
|
|
||||||
ScrollSpeed = lastScrollSpeed = nextScrollSpeed,
|
ScrollSpeed = lastScrollSpeed = nextScrollSpeed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Replays;
|
using osu.Game.Rulesets.Taiko.Replays;
|
||||||
@ -12,5 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
@ -13,5 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
{
|
{
|
||||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||||
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
@ -9,5 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
|||||||
public class TaikoModRelax : ModRelax
|
public class TaikoModRelax : ModRelax
|
||||||
{
|
{
|
||||||
public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katus.";
|
public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katus.";
|
||||||
|
|
||||||
|
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
127
osu.Game.Rulesets.Taiko/Mods/TaikoModSingleTap.cs
Normal file
127
osu.Game.Rulesets.Taiko/Mods/TaikoModSingleTap.cs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// 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.Localisation;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Input.Bindings;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Beatmaps.Timing;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.Mods
|
||||||
|
{
|
||||||
|
public partial class TaikoModSingleTap : Mod, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield
|
||||||
|
{
|
||||||
|
public override string Name => @"Single Tap";
|
||||||
|
public override string Acronym => @"SG";
|
||||||
|
public override LocalisableString Description => @"One key for dons, one key for kats.";
|
||||||
|
|
||||||
|
public override double ScoreMultiplier => 1.0;
|
||||||
|
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
private DrawableTaikoRuleset ruleset = null!;
|
||||||
|
|
||||||
|
private TaikoPlayfield playfield { get; set; } = null!;
|
||||||
|
|
||||||
|
private TaikoAction? lastAcceptedCentreAction { get; set; }
|
||||||
|
private TaikoAction? lastAcceptedRimAction { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A tracker for periods where single tap should not be enforced (i.e. non-gameplay periods).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||||
|
/// </remarks>
|
||||||
|
private PeriodTracker nonGameplayPeriods = null!;
|
||||||
|
|
||||||
|
private IFrameStableClock gameplayClock = null!;
|
||||||
|
|
||||||
|
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
ruleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||||
|
ruleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||||
|
playfield = (TaikoPlayfield)ruleset.Playfield;
|
||||||
|
|
||||||
|
var periods = new List<Period>();
|
||||||
|
|
||||||
|
if (drawableRuleset.Objects.Any())
|
||||||
|
{
|
||||||
|
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||||
|
|
||||||
|
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||||
|
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||||
|
|
||||||
|
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||||
|
}
|
||||||
|
|
||||||
|
nonGameplayPeriods = new PeriodTracker(periods);
|
||||||
|
|
||||||
|
gameplayClock = drawableRuleset.FrameStableClock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
if (!nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) return;
|
||||||
|
|
||||||
|
lastAcceptedCentreAction = null;
|
||||||
|
lastAcceptedRimAction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkCorrectAction(TaikoAction action)
|
||||||
|
{
|
||||||
|
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// If next hit object is strong, allow usage of all actions. Strong drumrolls are ignored in this check.
|
||||||
|
if (playfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.HasResult != true)?.HitObject is TaikoStrongableHitObject hitObject
|
||||||
|
&& hitObject.IsStrong
|
||||||
|
&& hitObject is not DrumRoll)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if ((action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre)
|
||||||
|
&& (lastAcceptedCentreAction == null || lastAcceptedCentreAction == action))
|
||||||
|
{
|
||||||
|
lastAcceptedCentreAction = action;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((action == TaikoAction.LeftRim || action == TaikoAction.RightRim)
|
||||||
|
&& (lastAcceptedRimAction == null || lastAcceptedRimAction == action))
|
||||||
|
{
|
||||||
|
lastAcceptedRimAction = action;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class InputInterceptor : Component, IKeyBindingHandler<TaikoAction>
|
||||||
|
{
|
||||||
|
private readonly TaikoModSingleTap mod;
|
||||||
|
|
||||||
|
public InputInterceptor(TaikoModSingleTap mod)
|
||||||
|
{
|
||||||
|
this.mod = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||||
|
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||||
|
=> !mod.checkCorrectAction(e.Action);
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -158,6 +158,7 @@ namespace osu.Game.Rulesets.Taiko
|
|||||||
new TaikoModDifficultyAdjust(),
|
new TaikoModDifficultyAdjust(),
|
||||||
new TaikoModClassic(),
|
new TaikoModClassic(),
|
||||||
new TaikoModSwap(),
|
new TaikoModSwap(),
|
||||||
|
new TaikoModSingleTap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true);
|
public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true);
|
||||||
|
|
||||||
|
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
||||||
|
|
||||||
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
||||||
|
|
||||||
protected override bool UserScrollSpeedAdjustment => false;
|
protected override bool UserScrollSpeedAdjustment => false;
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using Foundation;
|
|
||||||
using osu.Framework.iOS;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.iOS
|
|
||||||
{
|
|
||||||
[Register("AppDelegate")]
|
|
||||||
public class AppDelegate : GameAppDelegate
|
|
||||||
{
|
|
||||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using osu.Framework.iOS;
|
||||||
|
|
||||||
using UIKit;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.iOS
|
namespace osu.Game.Tests.iOS
|
||||||
{
|
{
|
||||||
@ -11,7 +9,7 @@ namespace osu.Game.Tests.iOS
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
GameApplication.Main(new OsuTestBrowser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,16 +181,19 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual(956, timingPoint.Time);
|
Assert.AreEqual(956, timingPoint.Time);
|
||||||
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
|
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
|
||||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||||
|
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||||
|
|
||||||
timingPoint = controlPoints.TimingPointAt(48428);
|
timingPoint = controlPoints.TimingPointAt(48428);
|
||||||
Assert.AreEqual(956, timingPoint.Time);
|
Assert.AreEqual(956, timingPoint.Time);
|
||||||
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
||||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||||
|
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||||
|
|
||||||
timingPoint = controlPoints.TimingPointAt(119637);
|
timingPoint = controlPoints.TimingPointAt(119637);
|
||||||
Assert.AreEqual(119637, timingPoint.Time);
|
Assert.AreEqual(119637, timingPoint.Time);
|
||||||
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
|
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
|
||||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||||
|
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||||
|
|
||||||
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
||||||
Assert.AreEqual(0, difficultyPoint.Time);
|
Assert.AreEqual(0, difficultyPoint.Time);
|
||||||
@ -222,17 +225,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
var effectPoint = controlPoints.EffectPointAt(0);
|
var effectPoint = controlPoints.EffectPointAt(0);
|
||||||
Assert.AreEqual(0, effectPoint.Time);
|
Assert.AreEqual(0, effectPoint.Time);
|
||||||
Assert.IsFalse(effectPoint.KiaiMode);
|
Assert.IsFalse(effectPoint.KiaiMode);
|
||||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
|
||||||
|
|
||||||
effectPoint = controlPoints.EffectPointAt(53703);
|
effectPoint = controlPoints.EffectPointAt(53703);
|
||||||
Assert.AreEqual(53703, effectPoint.Time);
|
Assert.AreEqual(53703, effectPoint.Time);
|
||||||
Assert.IsTrue(effectPoint.KiaiMode);
|
Assert.IsTrue(effectPoint.KiaiMode);
|
||||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
|
||||||
|
|
||||||
effectPoint = controlPoints.EffectPointAt(116637);
|
effectPoint = controlPoints.EffectPointAt(116637);
|
||||||
Assert.AreEqual(95901, effectPoint.Time);
|
Assert.AreEqual(95901, effectPoint.Time);
|
||||||
Assert.IsFalse(effectPoint.KiaiMode);
|
Assert.IsFalse(effectPoint.KiaiMode);
|
||||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,6 +273,28 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDecodeOmitBarLineEffect()
|
||||||
|
{
|
||||||
|
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||||
|
|
||||||
|
using (var resStream = TestResources.OpenResource("omit-barline-control-points.osu"))
|
||||||
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
|
{
|
||||||
|
var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
|
||||||
|
|
||||||
|
Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(6));
|
||||||
|
Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(0));
|
||||||
|
|
||||||
|
Assert.That(controlPoints.TimingPointAt(500).OmitFirstBarLine, Is.False);
|
||||||
|
Assert.That(controlPoints.TimingPointAt(1500).OmitFirstBarLine, Is.True);
|
||||||
|
Assert.That(controlPoints.TimingPointAt(2500).OmitFirstBarLine, Is.False);
|
||||||
|
Assert.That(controlPoints.TimingPointAt(3500).OmitFirstBarLine, Is.False);
|
||||||
|
Assert.That(controlPoints.TimingPointAt(4500).OmitFirstBarLine, Is.False);
|
||||||
|
Assert.That(controlPoints.TimingPointAt(5500).OmitFirstBarLine, Is.True);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestTimingPointResetsSpeedMultiplier()
|
public void TestTimingPointResetsSpeedMultiplier()
|
||||||
{
|
{
|
||||||
|
125
osu.Game.Tests/Database/LegacyExporterTest.cs
Normal file
125
osu.Game.Tests/Database/LegacyExporterTest.cs
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Database;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class LegacyExporterTest
|
||||||
|
{
|
||||||
|
private TestLegacyExporter legacyExporter = null!;
|
||||||
|
private TemporaryNativeStorage storage = null!;
|
||||||
|
|
||||||
|
private const string short_filename = "normal file name";
|
||||||
|
|
||||||
|
private const string long_filename =
|
||||||
|
"some file with super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name";
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
storage = new TemporaryNativeStorage("export-storage");
|
||||||
|
legacyExporter = new TestLegacyExporter(storage);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExportFileWithNormalNameTest()
|
||||||
|
{
|
||||||
|
var item = new TestPathInfo(short_filename);
|
||||||
|
|
||||||
|
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||||
|
|
||||||
|
exportItemAndAssert(item, short_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExportFileWithNormalNameMultipleTimesTest()
|
||||||
|
{
|
||||||
|
var item = new TestPathInfo(short_filename);
|
||||||
|
|
||||||
|
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||||
|
|
||||||
|
//Export multiple times
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
string expectedFileName = i == 0 ? short_filename : $"{short_filename} ({i})";
|
||||||
|
exportItemAndAssert(item, expectedFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExportFileWithSuperLongNameTest()
|
||||||
|
{
|
||||||
|
int expectedLength = TestLegacyExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||||
|
string expectedName = long_filename.Remove(expectedLength);
|
||||||
|
|
||||||
|
var item = new TestPathInfo(long_filename);
|
||||||
|
|
||||||
|
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||||
|
exportItemAndAssert(item, expectedName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ExportFileWithSuperLongNameMultipleTimesTest()
|
||||||
|
{
|
||||||
|
int expectedLength = TestLegacyExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||||
|
string expectedName = long_filename.Remove(expectedLength);
|
||||||
|
|
||||||
|
var item = new TestPathInfo(long_filename);
|
||||||
|
|
||||||
|
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||||
|
|
||||||
|
//Export multiple times
|
||||||
|
for (int i = 0; i < 100; i++)
|
||||||
|
{
|
||||||
|
string expectedFilename = i == 0 ? expectedName : $"{expectedName} ({i})";
|
||||||
|
exportItemAndAssert(item, expectedFilename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportItemAndAssert(IHasNamedFiles item, string expectedName)
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() => legacyExporter.Export(item));
|
||||||
|
Assert.That(storage.Exists($"exports/{expectedName}{legacyExporter.GetExtension()}"), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TearDown]
|
||||||
|
public void TearDown()
|
||||||
|
{
|
||||||
|
if (storage.IsNotNull())
|
||||||
|
storage.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestPathInfo : IHasNamedFiles
|
||||||
|
{
|
||||||
|
public string Filename { get; }
|
||||||
|
|
||||||
|
public IEnumerable<INamedFileUsage> Files { get; } = new List<INamedFileUsage>();
|
||||||
|
|
||||||
|
public TestPathInfo(string filename)
|
||||||
|
{
|
||||||
|
Filename = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestLegacyExporter : LegacyExporter<IHasNamedFiles>
|
||||||
|
{
|
||||||
|
public TestLegacyExporter(Storage storage)
|
||||||
|
: base(storage)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetExtension() => FileExtension;
|
||||||
|
|
||||||
|
protected override string FileExtension => ".test";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -43,6 +43,18 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
||||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||||
|
|
||||||
|
cpi.Add(1200, new TimingControlPoint { OmitFirstBarLine = true }); // is not redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(3));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(3));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(3));
|
||||||
|
|
||||||
|
cpi.Add(1500, new TimingControlPoint { OmitFirstBarLine = true }); // is not redundant
|
||||||
|
|
||||||
|
Assert.That(cpi.Groups.Count, Is.EqualTo(4));
|
||||||
|
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(4));
|
||||||
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -95,12 +107,12 @@ namespace osu.Game.Tests.NonVisual
|
|||||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
|
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
|
||||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||||
|
|
||||||
cpi.Add(1000, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // is not redundant
|
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
|
||||||
cpi.Add(1400, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // same settings, but is not redundant
|
cpi.Add(1400, new EffectControlPoint { KiaiMode = true }); // is redundant
|
||||||
|
|
||||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(2));
|
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1));
|
||||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20230305.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20230305.osk
Normal file
Binary file not shown.
@ -176,6 +176,7 @@ namespace osu.Game.Tests.Resources
|
|||||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||||
},
|
},
|
||||||
BeatmapInfo = beatmap,
|
BeatmapInfo = beatmap,
|
||||||
|
BeatmapHash = beatmap.Hash,
|
||||||
Ruleset = beatmap.Ruleset,
|
Ruleset = beatmap.Ruleset,
|
||||||
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
||||||
TotalScore = 2845370,
|
TotalScore = 2845370,
|
||||||
|
27
osu.Game.Tests/Resources/omit-barline-control-points.osu
Normal file
27
osu.Game.Tests/Resources/omit-barline-control-points.osu
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
osu file format v14
|
||||||
|
|
||||||
|
[TimingPoints]
|
||||||
|
|
||||||
|
// Uninherited: none, inherited: none
|
||||||
|
0,500,4,2,0,100,1,0
|
||||||
|
0,-50,4,3,0,100,0,0
|
||||||
|
|
||||||
|
// Uninherited: omit, inherited: none
|
||||||
|
1000,500,4,2,0,100,1,8
|
||||||
|
1000,-50,4,3,0,100,0,0
|
||||||
|
|
||||||
|
// Uninherited: none, inherited: omit (should be ignored, inheriting cannot omit)
|
||||||
|
2000,500,4,2,0,100,1,0
|
||||||
|
2000,-50,4,3,0,100,0,8
|
||||||
|
|
||||||
|
// Inherited: none, uninherited: none
|
||||||
|
3000,-50,4,3,0,100,0,0
|
||||||
|
3000,500,4,2,0,100,1,0
|
||||||
|
|
||||||
|
// Inherited: omit, uninherited: none (should be ignored, inheriting cannot omit)
|
||||||
|
4000,-50,4,3,0,100,0,8
|
||||||
|
4000,500,4,2,0,100,1,0
|
||||||
|
|
||||||
|
// Inherited: none, uninherited: omit
|
||||||
|
5000,-50,4,3,0,100,0,0
|
||||||
|
5000,500,4,2,0,100,1,8
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
|
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
|
||||||
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
||||||
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer));
|
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer, parent.Get<ShaderManager>()));
|
||||||
|
|
||||||
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
||||||
}
|
}
|
||||||
@ -156,12 +156,15 @@ namespace osu.Game.Tests.Rulesets
|
|||||||
|
|
||||||
private class TestShaderManager : ShaderManager
|
private class TestShaderManager : ShaderManager
|
||||||
{
|
{
|
||||||
public TestShaderManager(IRenderer renderer)
|
private readonly ShaderManager parentManager;
|
||||||
|
|
||||||
|
public TestShaderManager(IRenderer renderer, ShaderManager parentManager)
|
||||||
: base(renderer, new ResourceStore<byte[]>())
|
: base(renderer, new ResourceStore<byte[]>())
|
||||||
{
|
{
|
||||||
|
this.parentManager = parentManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override byte[] LoadRaw(string name) => null;
|
public override byte[] LoadRaw(string name) => parentManager.LoadRaw(name);
|
||||||
|
|
||||||
public bool IsDisposed { get; private set; }
|
public bool IsDisposed { get; private set; }
|
||||||
|
|
||||||
|
@ -133,6 +133,25 @@ namespace osu.Game.Tests.Skins.IO
|
|||||||
assertImportedOnce(import1, import2);
|
assertImportedOnce(import1, import2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public Task TestImportExportedNonAsciiSkinFilename() => runSkinTest(async osu =>
|
||||||
|
{
|
||||||
|
MemoryStream exportStream = new MemoryStream();
|
||||||
|
|
||||||
|
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 『1』", "author 1"), "custom.osk"));
|
||||||
|
assertCorrectMetadata(import1, "name 『1』 [custom]", "author 1", osu);
|
||||||
|
|
||||||
|
import1.PerformRead(s =>
|
||||||
|
{
|
||||||
|
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||||
|
});
|
||||||
|
|
||||||
|
string exportFilename = import1.GetDisplayString().GetValidFilename();
|
||||||
|
|
||||||
|
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
||||||
|
assertCorrectMetadata(import2, "name 『1』 [custom]", "author 1", osu);
|
||||||
|
});
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
||||||
{
|
{
|
||||||
|
@ -48,7 +48,9 @@ namespace osu.Game.Tests.Skins
|
|||||||
// Covers BPM counter.
|
// Covers BPM counter.
|
||||||
"Archives/modified-default-20221205.osk",
|
"Archives/modified-default-20221205.osk",
|
||||||
// Covers judgement counter.
|
// Covers judgement counter.
|
||||||
"Archives/modified-default-20230117.osk"
|
"Archives/modified-default-20230117.osk",
|
||||||
|
// Covers player avatar and flag.
|
||||||
|
"Archives/modified-argon-20230305.osk",
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
@ -51,9 +49,11 @@ namespace osu.Game.Tests.Testing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestRetrieveShader()
|
public void TestRetrieveShader()
|
||||||
{
|
{
|
||||||
AddAssert("ruleset shaders retrieved", () =>
|
AddStep("ruleset shaders retrieved without error", () =>
|
||||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs") != null &&
|
{
|
||||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs") != null);
|
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs");
|
||||||
|
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -76,12 +76,12 @@ namespace osu.Game.Tests.Testing
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
||||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager();
|
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TestRulesetConfigManager();
|
||||||
|
|
||||||
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
||||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => null;
|
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => null!;
|
||||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
|
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
|
||||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestRulesetConfigManager : IRulesetConfigManager
|
private class TestRulesetConfigManager : IRulesetConfigManager
|
||||||
|
@ -9,6 +9,7 @@ using osuTK;
|
|||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Game.Graphics.Backgrounds;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Background
|
namespace osu.Game.Tests.Visual.Background
|
||||||
{
|
{
|
||||||
@ -97,15 +98,29 @@ namespace osu.Game.Tests.Visual.Background
|
|||||||
texelSize = Source.texelSize;
|
texelSize = Source.texelSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||||
{
|
|
||||||
TextureShader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
|
||||||
TextureShader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
base.Draw(renderer);
|
protected override void BindUniformResources(IShader shader, IRenderer renderer)
|
||||||
|
{
|
||||||
|
base.BindUniformResources(shader, renderer);
|
||||||
|
|
||||||
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
|
{
|
||||||
|
Thickness = thickness,
|
||||||
|
TexelSize = texelSize
|
||||||
|
};
|
||||||
|
|
||||||
|
shader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanDrawOpaqueInterior => false;
|
protected override bool CanDrawOpaqueInterior => false;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,8 @@ using osu.Game.Rulesets.Osu;
|
|||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Skinning.Components;
|
||||||
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
@ -52,6 +54,134 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDragSelection()
|
||||||
|
{
|
||||||
|
BigBlackBox box1 = null!;
|
||||||
|
BigBlackBox box2 = null!;
|
||||||
|
BigBlackBox box3 = null!;
|
||||||
|
|
||||||
|
AddStep("Add big black boxes", () =>
|
||||||
|
{
|
||||||
|
var target = Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||||
|
target.Add(box1 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Position = new Vector2(-90),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
target.Add(box2 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
target.Add(box3 = new BigBlackBox
|
||||||
|
{
|
||||||
|
Position = new Vector2(90),
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// This step is specifically added to reproduce an edge case which was found during cyclic selection development.
|
||||||
|
// If everything is working as expected it should not affect the subsequent drag selections.
|
||||||
|
AddRepeatStep("Select top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft + new Vector2(box1.ScreenSpaceDrawQuad.Width / 8));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
}, 2);
|
||||||
|
|
||||||
|
AddStep("Begin drag top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Drag to bottom right", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Release button", () =>
|
||||||
|
{
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("First two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box1, box2 }));
|
||||||
|
|
||||||
|
AddStep("Begin drag bottom right", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.BottomRight + new Vector2(box3.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Drag to top left", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Release button", () =>
|
||||||
|
{
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Last two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||||
|
|
||||||
|
// Test cyclic selection doesn't trigger in this state.
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Last two boxes still selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCyclicSelection()
|
||||||
|
{
|
||||||
|
SkinBlueprint[] blueprints = null!;
|
||||||
|
|
||||||
|
AddStep("Add big black boxes", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Three black boxes added", () => targetContainer.Components.OfType<BigBlackBox>().Count(), () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("Store black box blueprints", () =>
|
||||||
|
{
|
||||||
|
blueprints = skinEditor.ChildrenOfType<SkinBlueprint>().Where(b => b.Item is BigBlackBox).ToArray();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||||
|
|
||||||
|
AddStep("move cursor to black box", () =>
|
||||||
|
{
|
||||||
|
// Slightly offset from centre to avoid random failures (see https://github.com/ppy/osu-framework/issues/5669).
|
||||||
|
InputManager.MoveMouseTo(((Drawable)blueprints[0].Item).ScreenSpaceDrawQuad.Centre + new Vector2(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 2", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item));
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 3", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item));
|
||||||
|
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||||
|
|
||||||
|
AddStep("select all boxes", () =>
|
||||||
|
{
|
||||||
|
skinEditor.SelectedComponents.Clear();
|
||||||
|
skinEditor.SelectedComponents.AddRange(targetContainer.Components.OfType<BigBlackBox>().Skip(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("all boxes selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("all boxes still selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestBringToFront(bool alterSelectionOrder)
|
public void TestBringToFront(bool alterSelectionOrder)
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Screens;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
@ -106,6 +107,26 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSaveFailedReplayWithStoryboardEndedDoesNotProgress()
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("fail on first judgement", () => currentFailConditions = (_, _) => true);
|
||||||
|
AddStep("set storyboard duration to 0s", () => currentStoryboardDuration = 0);
|
||||||
|
});
|
||||||
|
AddUntilStep("storyboard ends", () => Player.GameplayClockContainer.CurrentTime >= currentStoryboardDuration);
|
||||||
|
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||||
|
|
||||||
|
AddUntilStep("wait for fail overlay", () => Player.FailOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddUntilStep("wait for button clickable", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().Enabled.Value);
|
||||||
|
AddStep("click save button", () => Player.ChildrenOfType<SaveFailedScoreButton>().First().ChildrenOfType<OsuClickableContainer>().First().TriggerClick());
|
||||||
|
|
||||||
|
// Test a regression where importing the fail replay would cause progression to results screen in a failed state.
|
||||||
|
AddWaitStep("wait some", 10);
|
||||||
|
AddAssert("player is still current screen", () => Player.IsCurrentScreen());
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestShowResultsFalse()
|
public void TestShowResultsFalse()
|
||||||
{
|
{
|
||||||
|
@ -126,6 +126,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Id = 13926,
|
Id = 13926,
|
||||||
TournamentId = 35,
|
TournamentId = 35,
|
||||||
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
|
ImageLowRes = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US.jpg",
|
||||||
|
Image = "https://assets.ppy.sh/tournament-banners/official/owc2022/profile/winner_US@2x.jpg",
|
||||||
},
|
},
|
||||||
Badges = new[]
|
Badges = new[]
|
||||||
{
|
{
|
||||||
|
@ -84,16 +84,80 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(10);
|
checkDisplayedCount(10);
|
||||||
|
|
||||||
loadMoreScores(() => beatmapInfo);
|
importMoreScores(() => beatmapInfo);
|
||||||
checkCount(20);
|
checkDisplayedCount(20);
|
||||||
|
|
||||||
clearScores();
|
clearScores();
|
||||||
checkCount(0);
|
checkDisplayedCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLocalScoresDisplayOnBeatmapEdit()
|
||||||
|
{
|
||||||
|
BeatmapInfo beatmapInfo = null!;
|
||||||
|
string originalHash = string.Empty;
|
||||||
|
|
||||||
|
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||||
|
|
||||||
|
AddStep(@"Import beatmap", () =>
|
||||||
|
{
|
||||||
|
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||||
|
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||||
|
|
||||||
|
leaderboard.BeatmapInfo = beatmapInfo;
|
||||||
|
});
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
|
||||||
|
AddStep(@"Perform initial save to guarantee stable hash", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
|
||||||
|
originalHash = beatmapInfo.Hash;
|
||||||
|
});
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
AddStep(@"Save with changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 12;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash changed", () => beatmapInfo.Hash, () => Is.Not.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(10);
|
||||||
|
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
importMoreScores(() => beatmapInfo);
|
||||||
|
checkDisplayedCount(20);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
AddStep(@"Revert changes", () =>
|
||||||
|
{
|
||||||
|
IBeatmap beatmap = beatmapManager.GetWorkingBeatmap(beatmapInfo).Beatmap;
|
||||||
|
beatmap.Difficulty.ApproachRate = 8;
|
||||||
|
beatmapManager.Save(beatmapInfo, beatmap);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Hash restored", () => beatmapInfo.Hash, () => Is.EqualTo(originalHash));
|
||||||
|
checkDisplayedCount(10);
|
||||||
|
checkStoredCount(30);
|
||||||
|
|
||||||
|
clearScores();
|
||||||
|
checkDisplayedCount(0);
|
||||||
|
checkStoredCount(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -162,9 +226,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadMoreScores(Func<BeatmapInfo> beatmapInfo)
|
private void importMoreScores(Func<BeatmapInfo> beatmapInfo)
|
||||||
{
|
{
|
||||||
AddStep(@"Load new scores via manager", () =>
|
AddStep(@"Import new scores", () =>
|
||||||
{
|
{
|
||||||
foreach (var score in generateSampleScores(beatmapInfo()))
|
foreach (var score in generateSampleScores(beatmapInfo()))
|
||||||
scoreManager.Import(score);
|
scoreManager.Import(score);
|
||||||
@ -176,8 +240,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("Clear all scores", () => scoreManager.Delete());
|
AddStep("Clear all scores", () => scoreManager.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkCount(int expected) =>
|
private void checkDisplayedCount(int expected) =>
|
||||||
AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count() == expected);
|
AddUntilStep($"{expected} scores displayed", () => leaderboard.ChildrenOfType<LeaderboardScore>().Count(), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
|
private void checkStoredCount(int expected) =>
|
||||||
|
AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All<ScoreInfo>().Count(s => !s.DeletePending)), () => Is.EqualTo(expected));
|
||||||
|
|
||||||
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
@ -210,6 +277,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
},
|
},
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
Id = 6602580,
|
Id = 6602580,
|
||||||
@ -226,6 +294,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-30),
|
Date = DateTime.Now.AddSeconds(-30),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
{
|
{
|
||||||
@ -243,6 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddSeconds(-70),
|
Date = DateTime.Now.AddSeconds(-70),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -261,6 +331,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMinutes(-40),
|
Date = DateTime.Now.AddMinutes(-40),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -279,6 +350,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-2),
|
Date = DateTime.Now.AddHours(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -297,6 +369,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-25),
|
Date = DateTime.Now.AddHours(-25),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -315,6 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-50),
|
Date = DateTime.Now.AddHours(-50),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -333,6 +407,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddHours(-72),
|
Date = DateTime.Now.AddHours(-72),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -351,6 +426,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddMonths(-3),
|
Date = DateTime.Now.AddMonths(-3),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
@ -369,6 +445,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
Date = DateTime.Now.AddYears(-2),
|
Date = DateTime.Now.AddYears(-2),
|
||||||
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), },
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Ruleset = new OsuRuleset().RulesetInfo,
|
Ruleset = new OsuRuleset().RulesetInfo,
|
||||||
|
|
||||||
User = new APIUser
|
User = new APIUser
|
||||||
|
@ -14,10 +14,11 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.BeatmapListing;
|
using osu.Game.Overlays.BeatmapListing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
public partial class TestSceneBeatmapListingSortTabControl : OsuTestScene
|
public partial class TestSceneBeatmapListingSortTabControl : OsuManualInputManagerTestScene
|
||||||
{
|
{
|
||||||
private readonly BeatmapListingSortTabControl control;
|
private readonly BeatmapListingSortTabControl control;
|
||||||
|
|
||||||
@ -111,6 +112,29 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine);
|
resetUsesCriteriaOnCategory(SortCriteria.Updated, SearchCategory.Mine);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSortDirectionOnCriteriaChange()
|
||||||
|
{
|
||||||
|
AddStep("set category to leaderboard", () => control.Reset(SearchCategory.Leaderboard, false));
|
||||||
|
AddAssert("sort direction is descending", () => control.SortDirection.Value == SortDirection.Descending);
|
||||||
|
|
||||||
|
AddStep("click ranked sort button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(control.TabControl.ChildrenOfType<BeatmapListingSortTabControl.BeatmapTabButton>().Single(s => s.Active.Value));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sort direction is ascending", () => control.SortDirection.Value == SortDirection.Ascending);
|
||||||
|
|
||||||
|
AddStep("click first inactive sort button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(control.TabControl.ChildrenOfType<BeatmapListingSortTabControl.BeatmapTabButton>().First(s => !s.Active.Value));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("sort direction is descending", () => control.SortDirection.Value == SortDirection.Descending);
|
||||||
|
}
|
||||||
|
|
||||||
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
|
private void criteriaShowsOnCategory(bool expected, SortCriteria criteria, SearchCategory category)
|
||||||
{
|
{
|
||||||
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
|
AddAssert($"{criteria.ToString().ToLowerInvariant()} {(expected ? "shown" : "not shown")} on {category.ToString().ToLowerInvariant()}", () =>
|
||||||
|
@ -94,6 +94,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
OnlineID = i,
|
OnlineID = i,
|
||||||
BeatmapInfo = beatmapInfo,
|
BeatmapInfo = beatmapInfo,
|
||||||
|
BeatmapHash = beatmapInfo.Hash,
|
||||||
Accuracy = RNG.NextDouble(),
|
Accuracy = RNG.NextDouble(),
|
||||||
TotalScore = RNG.Next(1, 1000000),
|
TotalScore = RNG.Next(1, 1000000),
|
||||||
MaxCombo = RNG.Next(1, 1000),
|
MaxCombo = RNG.Next(1, 1000),
|
||||||
|
@ -332,13 +332,6 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
private void saveChanges()
|
private void saveChanges()
|
||||||
{
|
{
|
||||||
foreach (var r in ladder.Rounds)
|
|
||||||
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
|
|
||||||
|
|
||||||
ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat(
|
|
||||||
ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true)))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
// Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state.
|
||||||
string serialisedLadder = GetSerialisedLadder();
|
string serialisedLadder = GetSerialisedLadder();
|
||||||
|
|
||||||
@ -349,6 +342,13 @@ namespace osu.Game.Tournament
|
|||||||
|
|
||||||
public string GetSerialisedLadder()
|
public string GetSerialisedLadder()
|
||||||
{
|
{
|
||||||
|
foreach (var r in ladder.Rounds)
|
||||||
|
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();
|
||||||
|
|
||||||
|
ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat(
|
||||||
|
ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true)))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
return JsonConvert.SerializeObject(ladder,
|
return JsonConvert.SerializeObject(ladder,
|
||||||
new JsonSerializerSettings
|
new JsonSerializerSettings
|
||||||
{
|
{
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -29,10 +29,21 @@ namespace osu.Game.Beatmaps
|
|||||||
return new RomanisableString($"{metadata.GetPreferred(true)}".Trim(), $"{metadata.GetPreferred(false)}".Trim());
|
return new RomanisableString($"{metadata.GetPreferred(true)}".Trim(), $"{metadata.GetPreferred(false)}".Trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string[] GetSearchableTerms(this IBeatmapInfo beatmapInfo) => new[]
|
public static List<string> GetSearchableTerms(this IBeatmapInfo beatmapInfo)
|
||||||
{
|
{
|
||||||
beatmapInfo.DifficultyName
|
var termsList = new List<string>(BeatmapMetadataInfoExtensions.MAX_SEARCHABLE_TERM_COUNT + 1);
|
||||||
}.Concat(beatmapInfo.Metadata.GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
|
||||||
|
addIfNotNull(beatmapInfo.DifficultyName);
|
||||||
|
|
||||||
|
BeatmapMetadataInfoExtensions.CollectSearchableTerms(beatmapInfo.Metadata, termsList);
|
||||||
|
return termsList;
|
||||||
|
|
||||||
|
void addIfNotNull(string? s)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(s))
|
||||||
|
termsList.Add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
|
private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]";
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using System.Linq;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -13,16 +13,31 @@ namespace osu.Game.Beatmaps
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// An array of all searchable terms provided in contained metadata.
|
/// An array of all searchable terms provided in contained metadata.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string[] GetSearchableTerms(this IBeatmapMetadataInfo metadataInfo) => new[]
|
public static string[] GetSearchableTerms(this IBeatmapMetadataInfo metadataInfo)
|
||||||
{
|
{
|
||||||
metadataInfo.Author.Username,
|
var termsList = new List<string>(MAX_SEARCHABLE_TERM_COUNT);
|
||||||
metadataInfo.Artist,
|
CollectSearchableTerms(metadataInfo, termsList);
|
||||||
metadataInfo.ArtistUnicode,
|
return termsList.ToArray();
|
||||||
metadataInfo.Title,
|
}
|
||||||
metadataInfo.TitleUnicode,
|
|
||||||
metadataInfo.Source,
|
internal const int MAX_SEARCHABLE_TERM_COUNT = 7;
|
||||||
metadataInfo.Tags
|
|
||||||
}.Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
internal static void CollectSearchableTerms(IBeatmapMetadataInfo metadataInfo, IList<string> termsList)
|
||||||
|
{
|
||||||
|
addIfNotNull(metadataInfo.Author.Username);
|
||||||
|
addIfNotNull(metadataInfo.Artist);
|
||||||
|
addIfNotNull(metadataInfo.ArtistUnicode);
|
||||||
|
addIfNotNull(metadataInfo.Title);
|
||||||
|
addIfNotNull(metadataInfo.TitleUnicode);
|
||||||
|
addIfNotNull(metadataInfo.Source);
|
||||||
|
addIfNotNull(metadataInfo.Tags);
|
||||||
|
|
||||||
|
void addIfNotNull(string s)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(s))
|
||||||
|
termsList.Add(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A user-presentable display title representing this metadata.
|
/// A user-presentable display title representing this metadata.
|
||||||
|
@ -13,15 +13,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
|
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
|
||||||
{
|
{
|
||||||
KiaiModeBindable = { Disabled = true },
|
KiaiModeBindable = { Disabled = true },
|
||||||
OmitFirstBarLineBindable = { Disabled = true },
|
|
||||||
ScrollSpeedBindable = { Disabled = true }
|
ScrollSpeedBindable = { Disabled = true }
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the first bar line of this control point is ignored.
|
|
||||||
/// </summary>
|
|
||||||
public readonly BindableBool OmitFirstBarLineBindable = new BindableBool();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The relative scroll speed at this control point.
|
/// The relative scroll speed at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -43,15 +37,6 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
|
|
||||||
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
|
public override Color4 GetRepresentingColour(OsuColour colours) => colours.Purple;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the first bar line of this control point is ignored.
|
|
||||||
/// </summary>
|
|
||||||
public bool OmitFirstBarLine
|
|
||||||
{
|
|
||||||
get => OmitFirstBarLineBindable.Value;
|
|
||||||
set => OmitFirstBarLineBindable.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this control point enables Kiai mode.
|
/// Whether this control point enables Kiai mode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -67,16 +52,13 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override bool IsRedundant(ControlPoint? existing)
|
public override bool IsRedundant(ControlPoint? existing)
|
||||||
=> !OmitFirstBarLine
|
=> existing is EffectControlPoint existingEffect
|
||||||
&& existing is EffectControlPoint existingEffect
|
|
||||||
&& KiaiMode == existingEffect.KiaiMode
|
&& KiaiMode == existingEffect.KiaiMode
|
||||||
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine
|
|
||||||
&& ScrollSpeed == existingEffect.ScrollSpeed;
|
&& ScrollSpeed == existingEffect.ScrollSpeed;
|
||||||
|
|
||||||
public override void CopyFrom(ControlPoint other)
|
public override void CopyFrom(ControlPoint other)
|
||||||
{
|
{
|
||||||
KiaiMode = ((EffectControlPoint)other).KiaiMode;
|
KiaiMode = ((EffectControlPoint)other).KiaiMode;
|
||||||
OmitFirstBarLine = ((EffectControlPoint)other).OmitFirstBarLine;
|
|
||||||
ScrollSpeed = ((EffectControlPoint)other).ScrollSpeed;
|
ScrollSpeed = ((EffectControlPoint)other).ScrollSpeed;
|
||||||
|
|
||||||
base.CopyFrom(other);
|
base.CopyFrom(other);
|
||||||
@ -88,10 +70,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
|
|
||||||
public bool Equals(EffectControlPoint? other)
|
public bool Equals(EffectControlPoint? other)
|
||||||
=> base.Equals(other)
|
=> base.Equals(other)
|
||||||
&& OmitFirstBarLine == other.OmitFirstBarLine
|
|
||||||
&& ScrollSpeed == other.ScrollSpeed
|
&& ScrollSpeed == other.ScrollSpeed
|
||||||
&& KiaiMode == other.KiaiMode;
|
&& KiaiMode == other.KiaiMode;
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), OmitFirstBarLine, ScrollSpeed, KiaiMode);
|
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), ScrollSpeed, KiaiMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,11 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Bindable<TimeSignature> TimeSignatureBindable = new Bindable<TimeSignature>(TimeSignature.SimpleQuadruple);
|
public readonly Bindable<TimeSignature> TimeSignatureBindable = new Bindable<TimeSignature>(TimeSignature.SimpleQuadruple);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the first bar line of this control point is ignored.
|
||||||
|
/// </summary>
|
||||||
|
public readonly BindableBool OmitFirstBarLineBindable = new BindableBool();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
|
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -30,6 +35,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
Value = default_beat_length,
|
Value = default_beat_length,
|
||||||
Disabled = true
|
Disabled = true
|
||||||
},
|
},
|
||||||
|
OmitFirstBarLineBindable = { Disabled = true },
|
||||||
TimeSignatureBindable = { Disabled = true }
|
TimeSignatureBindable = { Disabled = true }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -42,6 +48,15 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
set => TimeSignatureBindable.Value = value;
|
set => TimeSignatureBindable.Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the first bar line of this control point is ignored.
|
||||||
|
/// </summary>
|
||||||
|
public bool OmitFirstBarLine
|
||||||
|
{
|
||||||
|
get => OmitFirstBarLineBindable.Value;
|
||||||
|
set => OmitFirstBarLineBindable.Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
public const double DEFAULT_BEAT_LENGTH = 1000;
|
public const double DEFAULT_BEAT_LENGTH = 1000;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -73,6 +88,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public override void CopyFrom(ControlPoint other)
|
public override void CopyFrom(ControlPoint other)
|
||||||
{
|
{
|
||||||
TimeSignature = ((TimingControlPoint)other).TimeSignature;
|
TimeSignature = ((TimingControlPoint)other).TimeSignature;
|
||||||
|
OmitFirstBarLine = ((TimingControlPoint)other).OmitFirstBarLine;
|
||||||
BeatLength = ((TimingControlPoint)other).BeatLength;
|
BeatLength = ((TimingControlPoint)other).BeatLength;
|
||||||
|
|
||||||
base.CopyFrom(other);
|
base.CopyFrom(other);
|
||||||
@ -85,8 +101,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
public bool Equals(TimingControlPoint? other)
|
public bool Equals(TimingControlPoint? other)
|
||||||
=> base.Equals(other)
|
=> base.Equals(other)
|
||||||
&& TimeSignature.Equals(other.TimeSignature)
|
&& TimeSignature.Equals(other.TimeSignature)
|
||||||
|
&& OmitFirstBarLine == other.OmitFirstBarLine
|
||||||
&& BeatLength.Equals(other.BeatLength);
|
&& BeatLength.Equals(other.BeatLength);
|
||||||
|
|
||||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength);
|
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), TimeSignature, BeatLength, OmitFirstBarLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -431,6 +431,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
|
|
||||||
controlPoint.BeatLength = beatLength;
|
controlPoint.BeatLength = beatLength;
|
||||||
controlPoint.TimeSignature = timeSignature;
|
controlPoint.TimeSignature = timeSignature;
|
||||||
|
controlPoint.OmitFirstBarLine = omitFirstBarSignature;
|
||||||
|
|
||||||
addControlPoint(time, controlPoint, true);
|
addControlPoint(time, controlPoint, true);
|
||||||
}
|
}
|
||||||
@ -447,7 +448,6 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
var effectPoint = new EffectControlPoint
|
var effectPoint = new EffectControlPoint
|
||||||
{
|
{
|
||||||
KiaiMode = kiaiMode,
|
KiaiMode = kiaiMode,
|
||||||
OmitFirstBarLine = omitFirstBarSignature,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// osu!taiko and osu!mania use effect points rather than difficulty points for scroll speed adjustments.
|
// osu!taiko and osu!mania use effect points rather than difficulty points for scroll speed adjustments.
|
||||||
|
@ -222,6 +222,7 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
{
|
{
|
||||||
var samplePoint = legacyControlPoints.SamplePointAt(time);
|
var samplePoint = legacyControlPoints.SamplePointAt(time);
|
||||||
var effectPoint = legacyControlPoints.EffectPointAt(time);
|
var effectPoint = legacyControlPoints.EffectPointAt(time);
|
||||||
|
var timingPoint = legacyControlPoints.TimingPointAt(time);
|
||||||
|
|
||||||
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
||||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
|
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo(string.Empty));
|
||||||
@ -230,10 +231,10 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
||||||
if (effectPoint.KiaiMode)
|
if (effectPoint.KiaiMode)
|
||||||
effectFlags |= LegacyEffectFlags.Kiai;
|
effectFlags |= LegacyEffectFlags.Kiai;
|
||||||
if (effectPoint.OmitFirstBarLine)
|
if (timingPoint.OmitFirstBarLine)
|
||||||
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
||||||
|
|
||||||
writer.Write(FormattableString.Invariant($"{legacyControlPoints.TimingPointAt(time).TimeSignature.Numerator},"));
|
writer.Write(FormattableString.Invariant($"{timingPoint.TimeSignature.Numerator},"));
|
||||||
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
||||||
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
||||||
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
||||||
|
@ -8,12 +8,12 @@ using osu.Game.Overlays.Dialog;
|
|||||||
|
|
||||||
namespace osu.Game.Collections
|
namespace osu.Game.Collections
|
||||||
{
|
{
|
||||||
public partial class DeleteCollectionDialog : DeleteConfirmationDialog
|
public partial class DeleteCollectionDialog : DangerousActionDialog
|
||||||
{
|
{
|
||||||
public DeleteCollectionDialog(Live<BeatmapCollection> collection, Action deleteAction)
|
public DeleteCollectionDialog(Live<BeatmapCollection> collection, Action deleteAction)
|
||||||
{
|
{
|
||||||
BodyText = collection.PerformRead(c => $"{c.Name} ({"beatmap".ToQuantity(c.BeatmapMD5Hashes.Count)})");
|
BodyText = collection.PerformRead(c => $"{c.Name} ({"beatmap".ToQuantity(c.BeatmapMD5Hashes.Count)})");
|
||||||
DeleteAction = deleteAction;
|
DangerousAction = deleteAction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -19,6 +20,19 @@ namespace osu.Game.Database
|
|||||||
public abstract class LegacyExporter<TModel>
|
public abstract class LegacyExporter<TModel>
|
||||||
where TModel : class, IHasNamedFiles
|
where TModel : class, IHasNamedFiles
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Max length of filename (including extension).
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// The filename limit for most OSs is 255. This actual usable length is smaller because <see cref="Storage.CreateFileSafely(string)"/> adds an additional "_<see cref="Guid"/>" to the end of the path.
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// For more information see <see href="https://www.ibm.com/docs/en/spectrum-protect/8.1.9?topic=parameters-file-specification-syntax">file specification syntax</see>, <seealso href="https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits">file systems limitations</seealso>
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public const int MAX_FILENAME_LENGTH = 255 - (32 + 4 + 2 + 5); //max path - (Guid + Guid "D" format chars + Storage.CreateFileSafely chars + account for ' (99)' suffix)
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The file extension for exports (including the leading '.').
|
/// The file extension for exports (including the leading '.').
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -44,12 +58,16 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
string itemFilename = GetFilename(item).GetValidFilename();
|
string itemFilename = GetFilename(item).GetValidFilename();
|
||||||
|
|
||||||
|
if (itemFilename.Length > MAX_FILENAME_LENGTH - FileExtension.Length)
|
||||||
|
itemFilename = itemFilename.Remove(MAX_FILENAME_LENGTH - FileExtension.Length);
|
||||||
|
|
||||||
IEnumerable<string> existingExports =
|
IEnumerable<string> existingExports =
|
||||||
exportStorage
|
exportStorage
|
||||||
.GetFiles(string.Empty, $"{itemFilename}*{FileExtension}")
|
.GetFiles(string.Empty, $"{itemFilename}*{FileExtension}")
|
||||||
.Concat(exportStorage.GetDirectories(string.Empty));
|
.Concat(exportStorage.GetDirectories(string.Empty));
|
||||||
|
|
||||||
string filename = NamingUtils.GetNextBestFilename(existingExports, $"{itemFilename}{FileExtension}");
|
string filename = NamingUtils.GetNextBestFilename(existingExports, $"{itemFilename}{FileExtension}");
|
||||||
|
|
||||||
using (var stream = exportStorage.CreateFileSafely(filename))
|
using (var stream = exportStorage.CreateFileSafely(filename))
|
||||||
ExportModelTo(item, stream);
|
ExportModelTo(item, stream);
|
||||||
|
|
||||||
|
@ -70,8 +70,9 @@ namespace osu.Game.Database
|
|||||||
/// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo.
|
/// 23 2022-08-01 Added LastLocalUpdate to BeatmapInfo.
|
||||||
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
/// 24 2022-08-22 Added MaximumStatistics to ScoreInfo.
|
||||||
/// 25 2022-09-18 Remove skins to add with new naming.
|
/// 25 2022-09-18 Remove skins to add with new naming.
|
||||||
|
/// 26 2023-02-05 Added BeatmapHash to ScoreInfo.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const int schema_version = 25;
|
private const int schema_version = 26;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
/// Lock object which is held during <see cref="BlockAllOperations"/> sections, blocking realm retrieval during blocking periods.
|
||||||
@ -866,6 +867,15 @@ namespace osu.Game.Database
|
|||||||
// Remove the default skins so they can be added back by SkinManager with updated naming.
|
// Remove the default skins so they can be added back by SkinManager with updated naming.
|
||||||
migration.NewRealm.RemoveRange(migration.NewRealm.All<SkinInfo>().Where(s => s.Protected));
|
migration.NewRealm.RemoveRange(migration.NewRealm.All<SkinInfo>().Where(s => s.Protected));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case 26:
|
||||||
|
// Add ScoreInfo.BeatmapHash property to ensure scores correspond to the correct version of beatmap.
|
||||||
|
var scores = migration.NewRealm.All<ScoreInfo>();
|
||||||
|
|
||||||
|
foreach (var score in scores)
|
||||||
|
score.BeatmapHash = score.BeatmapInfo.Hash;
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
osu.Game/Graphics/Backgrounds/TriangleBorderData.cs
Normal file
16
osu.Game/Graphics/Backgrounds/TriangleBorderData.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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.Runtime.InteropServices;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.Backgrounds
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
public record struct TriangleBorderData
|
||||||
|
{
|
||||||
|
public UniformFloat Thickness;
|
||||||
|
public UniformFloat TexelSize;
|
||||||
|
private readonly UniformPadding8 pad1;
|
||||||
|
}
|
||||||
|
}
|
@ -252,7 +252,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
|
|
||||||
private class TrianglesDrawNode : DrawNode
|
private class TrianglesDrawNode : DrawNode
|
||||||
{
|
{
|
||||||
private float fill = 1f;
|
private const float fill = 1f;
|
||||||
|
|
||||||
protected new Triangles Source => (Triangles)base.Source;
|
protected new Triangles Source => (Triangles)base.Source;
|
||||||
|
|
||||||
@ -284,6 +284,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
parts.AddRange(Source.parts);
|
parts.AddRange(Source.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<TriangleBorderData> borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -294,14 +296,17 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Due to triangles having various sizes we would need to set a different "texelSize" value for each of them, which is insanely expensive, thus we should use one single value.
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
// texelSize computed for an average triangle (size 100) will result in big triangles becoming blurry, so we may just use 0 for all of them.
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
// But we still need to specify at least something, because otherwise other shader usages will override this value.
|
{
|
||||||
float texelSize = 0f;
|
Thickness = fill,
|
||||||
|
// Due to triangles having various sizes we would need to set a different "TexelSize" value for each of them, which is insanely expensive, thus we should use one single value.
|
||||||
|
// TexelSize computed for an average triangle (size 100) will result in big triangles becoming blurry, so we may just use 0 for all of them.
|
||||||
|
TexelSize = 0
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("thickness").UpdateValue(ref fill);
|
shader.BindUniformBlock(@"m_BorderData", borderDataBuffer);
|
||||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
foreach (TriangleParticle particle in parts)
|
foreach (TriangleParticle particle in parts)
|
||||||
{
|
{
|
||||||
@ -352,6 +357,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,6 +226,8 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
parts.AddRange(Source.parts);
|
parts.AddRange(Source.parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||||
|
|
||||||
public override void Draw(IRenderer renderer)
|
public override void Draw(IRenderer renderer)
|
||||||
{
|
{
|
||||||
base.Draw(renderer);
|
base.Draw(renderer);
|
||||||
@ -239,9 +241,15 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
vertexBatch = renderer.CreateQuadBatch<TexturedVertex2D>(Source.AimCount, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||||
|
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||||
|
{
|
||||||
|
Thickness = thickness,
|
||||||
|
TexelSize = texelSize
|
||||||
|
};
|
||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
shader.BindUniformBlock(@"m_BorderData", borderDataBuffer);
|
||||||
shader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
|
||||||
|
|
||||||
Vector2 relativeSize = Vector2.Divide(triangleSize, size);
|
Vector2 relativeSize = Vector2.Divide(triangleSize, size);
|
||||||
|
|
||||||
@ -289,6 +297,7 @@ namespace osu.Game.Graphics.Backgrounds
|
|||||||
base.Dispose(isDisposing);
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
vertexBatch?.Dispose();
|
vertexBatch?.Dispose();
|
||||||
|
borderDataBuffer?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
while (beatLength < MinimumBeatLength)
|
while (beatLength < MinimumBeatLength)
|
||||||
beatLength *= 2;
|
beatLength *= 2;
|
||||||
|
|
||||||
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / beatLength) - (effectPoint.OmitFirstBarLine ? 1 : 0);
|
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / beatLength) - (timingPoint.OmitFirstBarLine ? 1 : 0);
|
||||||
|
|
||||||
// The beats before the start of the first control point are off by 1, this should do the trick
|
// The beats before the start of the first control point are off by 1, this should do the trick
|
||||||
if (currentTrackTime < timingPoint.Time)
|
if (currentTrackTime < timingPoint.Time)
|
||||||
|
@ -3,11 +3,18 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Rendering;
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Framework.Graphics.Rendering.Vertices;
|
||||||
using osu.Framework.Graphics.Shaders;
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Graphics.Shaders.Types;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
using osuTK.Graphics.ES30;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.Sprites
|
namespace osu.Game.Graphics.Sprites
|
||||||
{
|
{
|
||||||
@ -16,7 +23,7 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ShaderManager shaders)
|
private void load(ShaderManager shaders)
|
||||||
{
|
{
|
||||||
TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LogoAnimation");
|
TextureShader = shaders.Load(@"LogoAnimation", @"LogoAnimation");
|
||||||
}
|
}
|
||||||
|
|
||||||
private float animationProgress;
|
private float animationProgress;
|
||||||
@ -41,11 +48,22 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
{
|
{
|
||||||
private LogoAnimation source => (LogoAnimation)Source;
|
private LogoAnimation source => (LogoAnimation)Source;
|
||||||
|
|
||||||
|
private readonly Action<TexturedVertex2D> addVertexAction;
|
||||||
|
|
||||||
private float progress;
|
private float progress;
|
||||||
|
|
||||||
public LogoAnimationDrawNode(LogoAnimation source)
|
public LogoAnimationDrawNode(LogoAnimation source)
|
||||||
: base(source)
|
: base(source)
|
||||||
{
|
{
|
||||||
|
addVertexAction = v =>
|
||||||
|
{
|
||||||
|
animationVertexBatch!.Add(new LogoAnimationVertex
|
||||||
|
{
|
||||||
|
Position = v.Position,
|
||||||
|
Colour = v.Colour,
|
||||||
|
TexturePosition = v.TexturePosition,
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ApplyState()
|
public override void ApplyState()
|
||||||
@ -55,14 +73,69 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
progress = source.animationProgress;
|
progress = source.animationProgress;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private IUniformBuffer<AnimationData> animationDataBuffer;
|
||||||
|
private IVertexBatch<LogoAnimationVertex> animationVertexBatch;
|
||||||
|
|
||||||
|
protected override void BindUniformResources(IShader shader, IRenderer renderer)
|
||||||
|
{
|
||||||
|
base.BindUniformResources(shader, renderer);
|
||||||
|
|
||||||
|
animationDataBuffer ??= renderer.CreateUniformBuffer<AnimationData>();
|
||||||
|
animationVertexBatch ??= renderer.CreateQuadBatch<LogoAnimationVertex>(1, 2);
|
||||||
|
|
||||||
|
animationDataBuffer.Data = animationDataBuffer.Data with { Progress = progress };
|
||||||
|
|
||||||
|
shader.BindUniformBlock(@"m_AnimationData", animationDataBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Blit(IRenderer renderer)
|
protected override void Blit(IRenderer renderer)
|
||||||
{
|
{
|
||||||
TextureShader.GetUniform<float>("progress").UpdateValue(ref progress);
|
if (DrawRectangle.Width == 0 || DrawRectangle.Height == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
base.Blit(renderer);
|
base.Blit(renderer);
|
||||||
|
|
||||||
|
renderer.DrawQuad(
|
||||||
|
Texture,
|
||||||
|
ScreenSpaceDrawQuad,
|
||||||
|
DrawColourInfo.Colour,
|
||||||
|
inflationPercentage: new Vector2(InflationAmount.X / DrawRectangle.Width, InflationAmount.Y / DrawRectangle.Height),
|
||||||
|
textureCoords: TextureCoords,
|
||||||
|
vertexAction: addVertexAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanDrawOpaqueInterior => false;
|
protected override bool CanDrawOpaqueInterior => false;
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
animationDataBuffer?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
|
private record struct AnimationData
|
||||||
|
{
|
||||||
|
public UniformFloat Progress;
|
||||||
|
private readonly UniformPadding12 pad1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
private struct LogoAnimationVertex : IEquatable<LogoAnimationVertex>, IVertex
|
||||||
|
{
|
||||||
|
[VertexMember(2, VertexAttribPointerType.Float)]
|
||||||
|
public Vector2 Position;
|
||||||
|
|
||||||
|
[VertexMember(4, VertexAttribPointerType.Float)]
|
||||||
|
public Color4 Colour;
|
||||||
|
|
||||||
|
[VertexMember(2, VertexAttribPointerType.Float)]
|
||||||
|
public Vector2 TexturePosition;
|
||||||
|
|
||||||
|
public readonly bool Equals(LogoAnimationVertex other) =>
|
||||||
|
Position.Equals(other.Position)
|
||||||
|
&& TexturePosition.Equals(other.TexturePosition)
|
||||||
|
&& Colour.Equals(other.Colour);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
|
public static LocalisableString RendererHeader => new TranslatableString(getKey(@"renderer_header"), @"Renderer");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Renderer"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString Renderer => new TranslatableString(getKey(@"renderer"), @"Renderer");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Frame limiter"
|
/// "Frame limiter"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -144,6 +149,12 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString Png => new TranslatableString(getKey(@"png_lossless"), @"PNG (lossless)");
|
public static LocalisableString Png => new TranslatableString(getKey(@"png_lossless"), @"PNG (lossless)");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "In order to change the renderer, the game will close. Please open it again."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString ChangeRendererConfirmation =>
|
||||||
|
new TranslatableString(getKey(@"change_renderer_configuration"), @"In order to change the renderer, the game will close. Please open it again.");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,16 +50,18 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString NoAutoplayMod => new TranslatableString(getKey(@"no_autoplay_mod"), @"The current ruleset doesn't have an autoplay mod available!");
|
public static LocalisableString NoAutoplayMod => new TranslatableString(getKey(@"no_autoplay_mod"), @"The current ruleset doesn't have an autoplay mod available!");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting."
|
/// "osu! doesn't seem to be able to play audio correctly.
|
||||||
|
///
|
||||||
|
/// Please try changing your audio device to a working setting."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"),
|
public static LocalisableString AudioPlaybackIssue => new TranslatableString(getKey(@"audio_playback_issue"), @"osu! doesn't seem to be able to play audio correctly.
|
||||||
@"osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting.");
|
|
||||||
|
Please try changing your audio device to a working setting.");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "The score overlay is currently disabled. You can toggle this by pressing {0}."
|
/// "The score overlay is currently disabled. You can toggle this by pressing {0}."
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"),
|
public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
|
||||||
@"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
|
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,11 @@ namespace osu.Game.Localisation
|
|||||||
if (manager == null)
|
if (manager == null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
// When using the English culture, prefer the fallbacks rather than osu-resources baked strings.
|
||||||
|
// They are guaranteed to be up-to-date, and is also what a developer expects to see when making changes to `xxxStrings.cs` files.
|
||||||
|
if (EffectiveCulture.Name == @"en")
|
||||||
|
return null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return manager.GetString(key, EffectiveCulture);
|
return manager.GetString(key, EffectiveCulture);
|
||||||
|
@ -39,6 +39,16 @@ namespace osu.Game.Localisation.SkinComponents
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), "The text to be displayed.");
|
public static LocalisableString TextElementTextDescription => new TranslatableString(getKey(@"text_element_text_description"), "The text to be displayed.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Corner radius"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CornerRadius => new TranslatableString(getKey(@"corner_radius"), "Corner radius");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "How rounded the corners should be."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CornerRadiusDescription => new TranslatableString(getKey(@"corner_radius_description"), "How rounded the corners should be.");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,12 @@ namespace osu.Game.Localisation
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Currently editing"
|
/// "Currently editing"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString CurrentlyEditing => new TranslatableString(getKey(@"currently_editing"), "Currently editing");
|
public static LocalisableString CurrentlyEditing => new TranslatableString(getKey(@"currently_editing"), @"Currently editing");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "All layout elements for layers in the current screen will be reset to defaults."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString RevertToDefaultDescription => new TranslatableString(getKey(@"revert_to_default_description"), @"All layout elements for layers in the current screen will be reset to defaults.");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
|
@ -209,7 +209,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
if (!string.IsNullOrEmpty(errors.Message))
|
if (!string.IsNullOrEmpty(errors.Message))
|
||||||
passwordDescription.AddErrors(new[] { errors.Message });
|
passwordDescription.AddErrors(new[] { errors.Message });
|
||||||
|
|
||||||
game.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}");
|
game.OpenUrlExternally($"{errors.Redirect}?username={usernameTextBox.Text}&email={emailTextBox.Text}", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
@ -10,8 +11,12 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
{
|
{
|
||||||
public partial class BeatmapListingHeader : OverlayHeader
|
public partial class BeatmapListingHeader : OverlayHeader
|
||||||
{
|
{
|
||||||
|
public BeatmapListingFilterControl FilterControl { get; private set; }
|
||||||
|
|
||||||
protected override OverlayTitle CreateTitle() => new BeatmapListingTitle();
|
protected override OverlayTitle CreateTitle() => new BeatmapListingTitle();
|
||||||
|
|
||||||
|
protected override Drawable CreateContent() => FilterControl = new BeatmapListingFilterControl();
|
||||||
|
|
||||||
private partial class BeatmapListingTitle : OverlayTitle
|
private partial class BeatmapListingTitle : OverlayTitle
|
||||||
{
|
{
|
||||||
public BeatmapListingTitle()
|
public BeatmapListingTitle()
|
||||||
|
@ -25,6 +25,8 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
|
|
||||||
if (currentParameters == null)
|
if (currentParameters == null)
|
||||||
Reset(SearchCategory.Leaderboard, false);
|
Reset(SearchCategory.Leaderboard, false);
|
||||||
|
|
||||||
|
Current.BindValueChanged(_ => SortDirection.Value = Overlays.SortDirection.Descending);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset(SearchCategory category, bool hasQuery)
|
public void Reset(SearchCategory category, bool hasQuery)
|
||||||
@ -102,7 +104,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class BeatmapTabButton : TabButton
|
public partial class BeatmapTabButton : TabButton
|
||||||
{
|
{
|
||||||
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
|
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
|
||||||
|
|
||||||
@ -136,7 +138,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
|
|
||||||
SortDirection.BindValueChanged(direction =>
|
SortDirection.BindValueChanged(direction =>
|
||||||
{
|
{
|
||||||
icon.Icon = direction.NewValue == Overlays.SortDirection.Ascending ? FontAwesome.Solid.CaretUp : FontAwesome.Solid.CaretDown;
|
icon.Icon = direction.NewValue == Overlays.SortDirection.Ascending && Active.Value ? FontAwesome.Solid.CaretUp : FontAwesome.Solid.CaretDown;
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,8 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private Container panelTarget;
|
private Container panelTarget;
|
||||||
private FillFlowContainer<BeatmapCard> foundContent;
|
private FillFlowContainer<BeatmapCard> foundContent;
|
||||||
private BeatmapListingFilterControl filterControl;
|
|
||||||
|
private BeatmapListingFilterControl filterControl => Header.FilterControl;
|
||||||
|
|
||||||
public BeatmapListingOverlay()
|
public BeatmapListingOverlay()
|
||||||
: base(OverlayColourScheme.Blue)
|
: base(OverlayColourScheme.Blue)
|
||||||
@ -60,12 +61,6 @@ namespace osu.Game.Overlays
|
|||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
filterControl = new BeatmapListingFilterControl
|
|
||||||
{
|
|
||||||
TypingStarted = onTypingStarted,
|
|
||||||
SearchStarted = onSearchStarted,
|
|
||||||
SearchFinished = onSearchFinished,
|
|
||||||
},
|
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
@ -88,6 +83,10 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
filterControl.TypingStarted = onTypingStarted;
|
||||||
|
filterControl.SearchStarted = onSearchStarted;
|
||||||
|
filterControl.SearchFinished = onSearchFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -132,11 +132,10 @@ namespace osu.Game.Overlays.Comments
|
|||||||
},
|
},
|
||||||
sideNumber = new OsuSpriteText
|
sideNumber = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.BottomCentre,
|
||||||
Text = "+1",
|
Text = "+1",
|
||||||
Font = OsuFont.GetFont(size: 14),
|
Font = OsuFont.GetFont(size: 14),
|
||||||
Margin = new MarginPadding { Right = 3 },
|
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
},
|
},
|
||||||
votesCounter = new OsuSpriteText
|
votesCounter = new OsuSpriteText
|
||||||
@ -189,7 +188,7 @@ namespace osu.Game.Overlays.Comments
|
|||||||
else
|
else
|
||||||
sideNumber.FadeTo(IsHovered ? 1 : 0);
|
sideNumber.FadeTo(IsHovered ? 1 : 0);
|
||||||
|
|
||||||
borderContainer.BorderThickness = IsHovered ? 3 : 0;
|
borderContainer.BorderThickness = IsHovered ? 2 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onHoverAction()
|
private void onHoverAction()
|
||||||
|
@ -8,18 +8,22 @@ using osu.Game.Localisation;
|
|||||||
namespace osu.Game.Overlays.Dialog
|
namespace osu.Game.Overlays.Dialog
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Base class for various confirmation dialogs that concern deletion actions.
|
/// A dialog which provides confirmation for actions which result in permanent consequences.
|
||||||
/// Differs from <see cref="ConfirmDialog"/> in that the confirmation button is a "dangerous" one
|
/// Differs from <see cref="ConfirmDialog"/> in that the confirmation button is a "dangerous" one
|
||||||
/// (requires the confirm button to be held).
|
/// (requires the confirm button to be held).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class DeleteConfirmationDialog : PopupDialog
|
/// <remarks>
|
||||||
|
/// The default implementation comes with text for a generic deletion operation.
|
||||||
|
/// This can be further customised by specifying custom <see cref="PopupDialog.HeaderText"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public abstract partial class DangerousActionDialog : PopupDialog
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The action which performs the deletion.
|
/// The action which performs the deletion.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected Action? DeleteAction { get; set; }
|
protected Action? DangerousAction { get; set; }
|
||||||
|
|
||||||
protected DeleteConfirmationDialog()
|
protected DangerousActionDialog()
|
||||||
{
|
{
|
||||||
HeaderText = DeleteConfirmationDialogStrings.HeaderText;
|
HeaderText = DeleteConfirmationDialogStrings.HeaderText;
|
||||||
|
|
||||||
@ -30,7 +34,7 @@ namespace osu.Game.Overlays.Dialog
|
|||||||
new PopupDialogDangerousButton
|
new PopupDialogDangerousButton
|
||||||
{
|
{
|
||||||
Text = DeleteConfirmationDialogStrings.Confirm,
|
Text = DeleteConfirmationDialogStrings.Confirm,
|
||||||
Action = () => DeleteAction?.Invoke()
|
Action = () => DangerousAction?.Invoke()
|
||||||
},
|
},
|
||||||
new PopupDialogCancelButton
|
new PopupDialogCancelButton
|
||||||
{
|
{
|
@ -7,12 +7,12 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public partial class DeleteModPresetDialog : DeleteConfirmationDialog
|
public partial class DeleteModPresetDialog : DangerousActionDialog
|
||||||
{
|
{
|
||||||
public DeleteModPresetDialog(Live<ModPreset> modPreset)
|
public DeleteModPresetDialog(Live<ModPreset> modPreset)
|
||||||
{
|
{
|
||||||
BodyText = modPreset.PerformRead(preset => preset.Name);
|
BodyText = modPreset.PerformRead(preset => preset.Name);
|
||||||
DeleteAction = () => modPreset.PerformWrite(preset => preset.DeletePending = true);
|
DangerousAction = () => modPreset.PerformWrite(preset => preset.DeletePending = true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -22,6 +23,7 @@ namespace osu.Game.Overlays
|
|||||||
protected readonly OverlayScrollContainer ScrollFlow;
|
protected readonly OverlayScrollContainer ScrollFlow;
|
||||||
|
|
||||||
protected readonly LoadingLayer Loading;
|
protected readonly LoadingLayer Loading;
|
||||||
|
private readonly Container loadingContainer;
|
||||||
private readonly Container content;
|
private readonly Container content;
|
||||||
|
|
||||||
protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
|
protected OnlineOverlay(OverlayColourScheme colourScheme, bool requiresSignIn = true)
|
||||||
@ -65,10 +67,22 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Loading = new LoadingLayer(true)
|
loadingContainer = new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = Loading = new LoadingLayer(true),
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
base.Content.Add(mainContent);
|
base.Content.Add(mainContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void UpdateAfterChildren()
|
||||||
|
{
|
||||||
|
base.UpdateAfterChildren();
|
||||||
|
|
||||||
|
// don't block header by applying padding equal to the visible header height
|
||||||
|
loadingContainer.Padding = new MarginPadding { Top = Math.Max(0, Header.Height - ScrollFlow.Current) };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
namespace osu.Game.Overlays
|
||||||
@ -39,7 +40,7 @@ namespace osu.Game.Overlays
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
Padding = new MarginPadding { Right = -3 }, // Compensate for scrollbar margin
|
||||||
Child = new OsuScrollContainer
|
Child = new SidebarScrollContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Child = new Container
|
Child = new Container
|
||||||
@ -74,5 +75,30 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
protected virtual Drawable CreateContent() => Empty();
|
protected virtual Drawable CreateContent() => Empty();
|
||||||
|
|
||||||
|
private partial class SidebarScrollContainer : OsuScrollContainer
|
||||||
|
{
|
||||||
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
|
{
|
||||||
|
if (e.ScrollDelta.Y > 0 && IsScrolledToStart())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (e.ScrollDelta.Y < 0 && IsScrolledToEnd())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.OnScroll(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
if (e.Delta.Y > 0 && IsScrolledToStart())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (e.Delta.Y < 0 && IsScrolledToEnd())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return base.OnDragStart(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Overlays
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected partial class TabButton : HeaderButton
|
public partial class TabButton : HeaderButton
|
||||||
{
|
{
|
||||||
public readonly BindableBool Active = new BindableBool();
|
public readonly BindableBool Active = new BindableBool();
|
||||||
|
|
||||||
|
@ -2,15 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osuTK;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
@ -52,36 +49,24 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
{
|
{
|
||||||
private readonly OsuSpriteText valueText;
|
private readonly OsuSpriteText valueText;
|
||||||
protected readonly LinkFlowContainer DescriptionText;
|
protected readonly LinkFlowContainer DescriptionText;
|
||||||
private readonly Box lineBackground;
|
|
||||||
|
|
||||||
public new int Count
|
public new int Count
|
||||||
{
|
{
|
||||||
set => valueText.Text = value.ToLocalisableString("N0");
|
set => valueText.Text = value.ToLocalisableString("N0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public CountSection(LocalisableString header)
|
protected CountSection(LocalisableString header)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Padding = new MarginPadding { Top = 10, Bottom = 20 };
|
Padding = new MarginPadding { Bottom = 20 };
|
||||||
Child = new FillFlowContainer
|
Child = new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 5),
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new CircularContainer
|
|
||||||
{
|
|
||||||
Masking = true,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 2,
|
|
||||||
Child = lineBackground = new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = header,
|
Text = header,
|
||||||
@ -91,7 +76,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
{
|
{
|
||||||
Text = "0",
|
Text = "0",
|
||||||
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
|
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
|
||||||
UseFullGlyphHeight = false,
|
|
||||||
},
|
},
|
||||||
DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14))
|
DescriptionText = new LinkFlowContainer(t => t.Font = t.Font.With(size: 14))
|
||||||
{
|
{
|
||||||
@ -101,12 +85,6 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
lineBackground.Colour = colourProvider.Highlight1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
|
private FillFlowContainer<SettingsSlider<float>> scalingSettings = null!;
|
||||||
|
|
||||||
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
private readonly Bindable<Display> currentDisplay = new Bindable<Display>();
|
||||||
private readonly IBindableList<WindowMode> windowModes = new BindableList<WindowMode>();
|
|
||||||
|
|
||||||
private Bindable<ScalingMode> scalingMode = null!;
|
private Bindable<ScalingMode> scalingMode = null!;
|
||||||
private Bindable<Size> sizeFullscreen = null!;
|
private Bindable<Size> sizeFullscreen = null!;
|
||||||
@ -75,7 +74,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
if (window != null)
|
if (window != null)
|
||||||
{
|
{
|
||||||
currentDisplay.BindTo(window.CurrentDisplayBindable);
|
currentDisplay.BindTo(window.CurrentDisplayBindable);
|
||||||
windowModes.BindTo(window.SupportedWindowModes);
|
|
||||||
window.DisplaysChanged += onDisplaysChanged;
|
window.DisplaysChanged += onDisplaysChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +85,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
windowModeDropdown = new SettingsDropdown<WindowMode>
|
windowModeDropdown = new SettingsDropdown<WindowMode>
|
||||||
{
|
{
|
||||||
LabelText = GraphicsSettingsStrings.ScreenMode,
|
LabelText = GraphicsSettingsStrings.ScreenMode,
|
||||||
ItemSource = windowModes,
|
Items = window?.SupportedWindowModes,
|
||||||
|
CanBeShown = { Value = window?.SupportedWindowModes.Count() > 1 },
|
||||||
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
Current = config.GetBindable<WindowMode>(FrameworkSetting.WindowMode),
|
||||||
},
|
},
|
||||||
displayDropdown = new DisplaySettingsDropdown
|
displayDropdown = new DisplaySettingsDropdown
|
||||||
@ -181,8 +180,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
updateScreenModeWarning();
|
updateScreenModeWarning();
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
windowModes.BindCollectionChanged((_, _) => updateDisplaySettingsVisibility());
|
|
||||||
|
|
||||||
currentDisplay.BindValueChanged(display => Schedule(() =>
|
currentDisplay.BindValueChanged(display => Schedule(() =>
|
||||||
{
|
{
|
||||||
resolutions.RemoveRange(1, resolutions.Count - 1);
|
resolutions.RemoveRange(1, resolutions.Count - 1);
|
||||||
@ -236,7 +233,6 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
private void updateDisplaySettingsVisibility()
|
private void updateDisplaySettingsVisibility()
|
||||||
{
|
{
|
||||||
windowModeDropdown.CanBeShown.Value = windowModes.Count > 1;
|
|
||||||
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
resolutionDropdown.CanBeShown.Value = resolutions.Count > 1 && windowModeDropdown.Current.Value == WindowMode.Fullscreen;
|
||||||
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
displayDropdown.CanBeShown.Value = displayDropdown.Items.Count() > 1;
|
||||||
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
safeAreaConsiderationsCheckbox.CanBeShown.Value = host.Window?.SafeAreaPadding.Value.Total != Vector2.Zero;
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System.Linq;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Overlays.Dialog;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
namespace osu.Game.Overlays.Settings.Sections.Graphics
|
||||||
{
|
{
|
||||||
@ -17,12 +20,25 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
{
|
{
|
||||||
protected override LocalisableString Header => GraphicsSettingsStrings.RendererHeader;
|
protected override LocalisableString Header => GraphicsSettingsStrings.RendererHeader;
|
||||||
|
|
||||||
|
private bool automaticRendererInUse;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig)
|
private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, IDialogOverlay? dialogOverlay, OsuGame? game, GameHost host)
|
||||||
{
|
{
|
||||||
// NOTE: Compatability mode omitted
|
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||||
|
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||||
|
|
||||||
|
SettingsEnumDropdown<RendererType> rendererDropdown;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
|
rendererDropdown = new RendererSettingsDropdown
|
||||||
|
{
|
||||||
|
LabelText = GraphicsSettingsStrings.Renderer,
|
||||||
|
Current = renderer,
|
||||||
|
Items = host.GetPreferredRenderersForCurrentPlatform().OrderBy(t => t).Where(t => t != RendererType.Vulkan),
|
||||||
|
Keywords = new[] { @"compatibility", @"directx" },
|
||||||
|
},
|
||||||
// TODO: this needs to be a custom dropdown at some point
|
// TODO: this needs to be a custom dropdown at some point
|
||||||
new SettingsEnumDropdown<FrameSync>
|
new SettingsEnumDropdown<FrameSync>
|
||||||
{
|
{
|
||||||
@ -41,6 +57,55 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
|
Current = osuConfig.GetBindable<bool>(OsuSetting.ShowFpsDisplay)
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderer.BindValueChanged(r =>
|
||||||
|
{
|
||||||
|
if (r.NewValue == host.ResolvedRenderer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Need to check startup renderer for the "automatic" case, as ResolvedRenderer above will track the final resolved renderer instead.
|
||||||
|
if (r.NewValue == RendererType.Automatic && automaticRendererInUse)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dialogOverlay?.Push(new ConfirmDialog(GraphicsSettingsStrings.ChangeRendererConfirmation, () => game?.AttemptExit(), () =>
|
||||||
|
{
|
||||||
|
renderer.Value = automaticRendererInUse ? RendererType.Automatic : host.ResolvedRenderer;
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: remove this once we support SDL+android.
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.Android)
|
||||||
|
{
|
||||||
|
rendererDropdown.Items = new[] { RendererType.Automatic, RendererType.OpenGLLegacy };
|
||||||
|
rendererDropdown.SetNoticeText("New renderer support for android is coming soon!", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private partial class RendererSettingsDropdown : SettingsEnumDropdown<RendererType>
|
||||||
|
{
|
||||||
|
protected override OsuDropdown<RendererType> CreateDropdown() => new RendererDropdown();
|
||||||
|
|
||||||
|
protected partial class RendererDropdown : DropdownControl
|
||||||
|
{
|
||||||
|
private RendererType hostResolvedRenderer;
|
||||||
|
private bool automaticRendererInUse;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(FrameworkConfigManager config, GameHost host)
|
||||||
|
{
|
||||||
|
var renderer = config.GetBindable<RendererType>(FrameworkSetting.Renderer);
|
||||||
|
automaticRendererInUse = renderer.Value == RendererType.Automatic;
|
||||||
|
hostResolvedRenderer = host.ResolvedRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override LocalisableString GenerateItemText(RendererType item)
|
||||||
|
{
|
||||||
|
if (item == RendererType.Automatic && automaticRendererInUse)
|
||||||
|
return LocalisableString.Interpolate($"{base.GenerateItemText(item)} ({hostResolvedRenderer.GetDescription()})");
|
||||||
|
|
||||||
|
return base.GenerateItemText(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,12 +6,12 @@ using osu.Game.Overlays.Dialog;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
{
|
{
|
||||||
public partial class MassDeleteConfirmationDialog : DeleteConfirmationDialog
|
public partial class MassDeleteConfirmationDialog : DangerousActionDialog
|
||||||
{
|
{
|
||||||
public MassDeleteConfirmationDialog(Action deleteAction)
|
public MassDeleteConfirmationDialog(Action deleteAction)
|
||||||
{
|
{
|
||||||
BodyText = "Everything?";
|
BodyText = "Everything?";
|
||||||
DeleteAction = deleteAction;
|
DangerousAction = deleteAction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user