1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 05:12:34 +08:00

Compare commits

...

1744 Commits

730 changed files with 15064 additions and 5597 deletions
+2
View File
@@ -191,6 +191,8 @@ csharp_style_prefer_index_operator = false:silent
csharp_style_prefer_range_operator = false:silent
csharp_style_prefer_switch_expression = false:none
csharp_style_namespace_declarations = block_scoped:warning
[*.{yaml,yml}]
insert_final_newline = true
indent_style = space
+23 -11
View File
@@ -13,17 +13,17 @@ jobs:
runs-on: ubuntu-latest
steps:
- 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.
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
- name: Install .NET 3.1.x LTS
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: "3.1.x"
- name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
@@ -77,10 +77,10 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
@@ -94,7 +94,7 @@ jobs:
# Attempt to upload results even if test fails.
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
- name: Upload Test Results
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
@@ -106,10 +106,10 @@ jobs:
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
- name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
@@ -121,14 +121,26 @@ jobs:
build-only-ios:
name: Build only (iOS)
runs-on: macos-latest
# `macos-13` is required, because Xcode 14.3 is required (see below).
# TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta)
runs-on: macos-13
timeout-minutes: 60
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
# newest Microsoft.iOS.Sdk versions require Xcode 14.3.
# 14.3 is currently not the default Xcode version (https://github.com/actions/runner-images/blob/main/images/macos/macos-13-Readme.md#xcode),
# so set it manually.
# TODO: remove when 14.3 becomes the default Xcode version.
- name: Set Xcode version
shell: bash
run: |
sudo xcode-select -s "/Applications/Xcode_14.3.app"
echo "MD_APPLE_SDK_ROOT=/Applications/Xcode_14.3.app" >> $GITHUB_ENV
- name: Install .NET 6.0.x
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
+9 -9
View File
@@ -48,8 +48,8 @@ jobs:
CONTINUE="no"
fi
echo "::set-output name=continue::${CONTINUE}"
echo "::set-output name=matrix::${MATRIX_JSON}"
echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
diffcalc:
name: Run
runs-on: self-hosted
@@ -80,34 +80,34 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
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 "::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 "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
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
- name: Checkout osu (master)
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
path: 'master/osu'
- name: Checkout osu (pr)
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
path: 'pr/osu'
repository: ${{ steps.upstreambranch.outputs.repo }}
ref: ${{ steps.upstreambranch.outputs.branchname }}
- name: Checkout osu-difficulty-calculator (master)
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: ppy/osu-difficulty-calculator
path: 'master/osu-difficulty-calculator'
- name: Checkout osu-difficulty-calculator (pr)
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: ppy/osu-difficulty-calculator
path: 'pr/osu-difficulty-calculator'
- name: Install .NET 5.0.x
uses: actions/setup-dotnet@v1
uses: actions/setup-dotnet@v3
with:
dotnet-version: "5.0.x"
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
@@ -0,0 +1,53 @@
name: Update osu-web mod definitions
on:
push:
tags:
- '*'
permissions:
contents: read # to fetch code (actions/checkout)
jobs:
update-mod-definitions:
name: Update osu-web mod definitions
runs-on: ubuntu-latest
steps:
- name: Install .NET 6.0.x
uses: actions/setup-dotnet@v3
with:
dotnet-version: "6.0.x"
- name: Checkout ppy/osu
uses: actions/checkout@v3
with:
path: osu
- name: Checkout ppy/osu-tools
uses: actions/checkout@v3
with:
repository: ppy/osu-tools
path: osu-tools
- name: Checkout ppy/osu-web
uses: actions/checkout@v3
with:
repository: ppy/osu-web
path: osu-web
- name: Setup local game checkout for tools
run: ./UseLocalOsu.sh
working-directory: ./osu-tools
- name: Regenerate mod definitions
run: dotnet run --project PerformanceCalculator -- mods > ../osu-web/database/mods.json
working-directory: ./osu-tools
- name: Create pull request with changes
uses: peter-evans/create-pull-request@v5
with:
title: Update mod definitions
body: "This PR has been auto-generated to update the mod definitions to match ppy/osu@${{ github.ref_name }}."
branch: update-mod-definitions
commit-message: Update mod definitions
path: osu-web
token: ${{ secrets.OSU_WEB_PULL_REQUEST_PAT }}
+2 -2
View File
@@ -1,7 +1,7 @@
<!-- Contains required properties for osu!framework projects. -->
<Project>
<PropertyGroup Label="C#">
<LangVersion>9.0</LangVersion>
<LangVersion>10.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -17,7 +17,7 @@
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<ItemGroup Label="Code Analysis">
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.4" PrivateAssets="All" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)CodeAnalysis\BannedSymbols.txt" />
</ItemGroup>
<PropertyGroup Label="Code Analysis">
+30 -10
View File
@@ -16,21 +16,20 @@ The future of [osu!](https://osu.ppy.sh) and the beginning of an open era! Curre
## Status
This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
This project is under constant development, but we aim to keep things in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update.
**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet.
**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passes come at the end of development, preceded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to a [stable release](https://osu.ppy.sh/home/download) of osu!. We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet.
We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project:
- Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer).
- You can learn more about our approach to [project management](https://github.com/ppy/osu/wiki/Project-management).
- Read peppy's [blog post](https://blog.ppy.sh/a-definitive-lazer-faq/) exploring where the project is currently and the roadmap going forward.
## Running osu!
If you are looking to install or test osu! without setting up a development environment, you can consume our [binary releases](https://github.com/ppy/osu/releases). Handy links below will download the latest version for your operating system of choice:
If you are looking to install or test osu! without setting up a development environment, you can consume our [releases](https://github.com/ppy/osu/releases). You can also generally download a version for your current device from the [osu! site](https://osu.ppy.sh/home/download). Failing that, you may use the links below to download the latest version for your operating system of choice:
**Latest build:**
**Latest release:**
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) |
| ------------- | ------------- | ------------- | ------------- | ------------- |
@@ -50,9 +49,8 @@ You can see some examples of custom rulesets by visiting the [custom ruleset dir
Please make sure you have the following prerequisites:
- A desktop platform with the [.NET 6.0 SDK](https://dotnet.microsoft.com/download) installed.
- When developing with mobile, [Xamarin](https://docs.microsoft.com/en-us/xamarin/) is required, which is shipped together with Visual Studio or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/).
- When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- When running on Linux, please have a system-wide FFmpeg installation available to support video decoding.
When working with the codebase, we recommend using an IDE with intelligent code completion and syntax highlighting, such as the latest version of [Visual Studio](https://visualstudio.microsoft.com/vs/), [JetBrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
### Downloading the source code
@@ -89,7 +87,29 @@ _Due to a historical feature gap between .NET Core and Xamarin, running `dotnet`
### Testing with resource/framework modifications
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be achieved by running some commands as documented on the [osu-resources](https://github.com/ppy/osu-resources/wiki/Testing-local-resources-checkout-with-other-projects) and [osu-framework](https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects) wiki pages.
Sometimes it may be necessary to cross-test changes in [osu-resources](https://github.com/ppy/osu-resources) or [osu-framework](https://github.com/ppy/osu-framework). This can be quickly achieved using included commands:
Windows:
```ps
UseLocalFramework.ps1
UseLocalResources.ps1
```
macOS / Linux:
```ps
UseLocalFramework.sh
UseLocalResources.sh
```
Note that these commands assume you have the relevant project(s) checked out in adjacent directories:
```
|- osu // this repository
|- osu-framework
|- osu-resources
```
### Code analysis
@@ -105,7 +125,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).
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
@@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj" />
@@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osuTK;
@@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
public override IEnumerable<HitSampleInfo> GetSamples() => new[]
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK)
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
};
protected override void CheckForResult(bool userTriggered, double timeOffset)
@@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj" />
@@ -9,9 +9,9 @@
<GenerateProgramFile>false</GenerateProgramFile>
</PropertyGroup>
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj" />
@@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Pippidon.UI;
using osu.Game.Rulesets.Scoring;
@@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Pippidon.Objects.Drawables
public override IEnumerable<HitSampleInfo> GetSamples() => new[]
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, SampleControlPoint.DEFAULT_BANK)
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
};
protected override void CheckForResult(bool userTriggered, double timeOffset)
+43 -5
View File
@@ -3,15 +3,53 @@
#
# 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"
dotnet remove $CSPROJ package ppy.osu.Framework;
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj;
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework;
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android;
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
$TMP=New-TemporaryFile
$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
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
+33 -6
View File
@@ -5,14 +5,41 @@
#
# 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"
dotnet remove $CSPROJ package ppy.osu.Framework
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android
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)
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
+7
View File
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "6.0.100",
"rollForward": "latestFeature"
}
}
+5 -1
View File
@@ -8,9 +8,13 @@
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.131.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.608.0" />
</ItemGroup>
<ItemGroup>
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="mailto" />
</intent>
</queries>
</manifest>
+6 -3
View File
@@ -98,7 +98,7 @@ namespace osu.Desktop
if (status.Value is UserStatusOnline && activity.Value != null)
{
presence.State = truncate(activity.Value.Status);
presence.State = truncate(activity.Value.GetStatus(privacyMode.Value == DiscordRichPresenceMode.Limited));
presence.Details = truncate(getDetails(activity.Value));
if (getBeatmap(activity.Value) is IBeatmapInfo beatmap && beatmap.OnlineID > 0)
@@ -169,7 +169,7 @@ namespace osu.Desktop
case UserActivity.InGame game:
return game.BeatmapInfo;
case UserActivity.Editing edit:
case UserActivity.EditingBeatmap edit:
return edit.BeatmapInfo;
}
@@ -183,9 +183,12 @@ namespace osu.Desktop
case UserActivity.InGame game:
return game.BeatmapInfo.ToString() ?? string.Empty;
case UserActivity.Editing edit:
case UserActivity.EditingBeatmap edit:
return edit.BeatmapInfo.ToString() ?? string.Empty;
case UserActivity.WatchingReplay watching:
return watching.BeatmapInfo.ToString();
case UserActivity.InLobby lobby:
return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value;
}
+11 -6
View File
@@ -5,7 +5,6 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Versioning;
using System.Threading.Tasks;
@@ -139,7 +138,17 @@ namespace osu.Desktop
desktopWindow.CursorState |= CursorState.Hidden;
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();
@@ -151,10 +160,6 @@ namespace osu.Desktop
{
lock (importableFiles)
{
string firstExtension = Path.GetExtension(filePaths.First());
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
importableFiles.AddRange(filePaths);
Logger.Log($"Adding {filePaths.Length} files for import");
@@ -9,6 +9,7 @@ using osu.Framework.Logging;
using osu.Game;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Play;
using Squirrel;
using Squirrel.SimpleSplat;
using LogLevel = Squirrel.SimpleSplat.LogLevel;
@@ -36,6 +37,9 @@ namespace osu.Desktop.Updater
[Resolved]
private OsuGameBase game { get; set; } = null!;
[Resolved]
private ILocalUserPlayInfo? localUserInfo { get; set; }
[BackgroundDependencyLoader]
private void load(INotificationOverlay notifications)
{
@@ -55,6 +59,10 @@ namespace osu.Desktop.Updater
try
{
// Avoid any kind of update checking while gameplay is running.
if (localUserInfo?.IsPlaying.Value == true)
return false;
updateManager ??= new GithubUpdateManager(@"https://github.com/ppy/osu", false, github_token, @"osulazer");
var info = await updateManager.CheckForUpdate(!useDeltaPatching).ConfigureAwait(false);
+2 -2
View File
@@ -26,8 +26,8 @@
<ItemGroup Label="Package References">
<PackageReference Include="Clowd.Squirrel" Version="2.9.42" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="System.IO.Packaging" Version="6.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.1.1.14" />
<PackageReference Include="System.IO.Packaging" Version="7.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.1.3.18" />
</ItemGroup>
<ItemGroup Label="Resources">
<EmbeddedResource Include="lazer.ico" />
@@ -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);
}
}
}
@@ -7,9 +7,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
<PackageReference Include="nunit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<ItemGroup>
@@ -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.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using UIKit;
using osu.Framework.iOS;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Catch.Tests.iOS
{
@@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
{
public static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
GameApplication.Main(new OsuTestBrowser());
}
}
}
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
AddAssert("end time is correct", () => Precision.AlmostEquals(lastObject.EndTime, times[1]));
AddAssert("start position is correct", () => Precision.AlmostEquals(lastObject.OriginalX, positions[0]));
AddAssert("end position is correct", () => Precision.AlmostEquals(lastObject.EndX, positions[1]));
AddAssert("default slider velocity", () => lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
AddAssert("default slider velocity", () => lastObject.SliderVelocityBindable.IsDefault);
}
[Test]
@@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
addPlacementSteps(times, positions);
addPathCheckStep(times, positions);
AddAssert("slider velocity changed", () => !lastObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
AddAssert("slider velocity changed", () => !lastObject.SliderVelocityBindable.IsDefault);
}
[Test]
@@ -108,11 +108,11 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
double[] times = { 100, 300 };
float[] positions = { 200, 300 };
addBlueprintStep(times, positions);
AddAssert("default slider velocity", () => hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
AddAssert("default slider velocity", () => hitObject.SliderVelocityBindable.IsDefault);
addDragStartStep(times[1], positions[1]);
AddMouseMoveStep(times[1], 400);
AddAssert("slider velocity changed", () => !hitObject.DifficultyControlPoint.SliderVelocityBindable.IsDefault);
AddAssert("slider velocity changed", () => !hitObject.SliderVelocityBindable.IsDefault);
}
[Test]
@@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Catch.Tests
NewCombo = i % 8 == 0,
Samples = new List<HitSampleInfo>(new[]
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, "normal", volume: 100)
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
})
});
}
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Tests
if (withModifiedSkin)
{
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
AddStep("update target", () => Player.ChildrenOfType<SkinnableTargetContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
AddStep("update target", () => Player.ChildrenOfType<SkinComponentsContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
AddStep("exit player", () => Player.Exit());
CreateTest();
}
@@ -60,26 +60,24 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test]
public void TestCatcherHyperStateReverted()
{
DrawableCatchHitObject drawableObject1 = null;
DrawableCatchHitObject drawableObject2 = null;
JudgementResult result1 = null;
JudgementResult result2 = null;
AddStep("catch hyper fruit", () =>
{
attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
result1 = attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
});
AddStep("catch normal fruit", () =>
{
attemptCatch(new Fruit(), out drawableObject2, out result2);
result2 = attemptCatch(new Fruit());
});
AddStep("revert second result", () =>
{
catcher.OnRevertResult(drawableObject2, result2);
catcher.OnRevertResult(result2);
});
checkHyperDash(true);
AddStep("revert first result", () =>
{
catcher.OnRevertResult(drawableObject1, result1);
catcher.OnRevertResult(result1);
});
checkHyperDash(false);
}
@@ -87,16 +85,15 @@ namespace osu.Game.Rulesets.Catch.Tests
[Test]
public void TestCatcherAnimationStateReverted()
{
DrawableCatchHitObject drawableObject = null;
JudgementResult result = null;
AddStep("catch kiai fruit", () =>
{
attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
result = attemptCatch(new TestKiaiFruit());
});
checkState(CatcherAnimationState.Kiai);
AddStep("revert result", () =>
{
catcher.OnRevertResult(drawableObject, result);
catcher.OnRevertResult(result);
});
checkState(CatcherAnimationState.Idle);
}
@@ -268,23 +265,19 @@ namespace osu.Game.Rulesets.Catch.Tests
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
private void attemptCatch(CatchHitObject hitObject)
{
attemptCatch(() => hitObject, 1);
}
private void attemptCatch(Func<CatchHitObject> hitObject, int count)
{
for (int i = 0; i < count; i++)
attemptCatch(hitObject(), out _, out _);
attemptCatch(hitObject());
}
private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
private JudgementResult attemptCatch(CatchHitObject hitObject)
{
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
drawableObject = createDrawableObject(hitObject);
result = createResult(hitObject);
var drawableObject = createDrawableObject(hitObject);
var result = createResult(hitObject);
applyResult(drawableObject, result);
return result;
}
private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
@@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
var xPositionData = obj as IHasXPosition;
var yPositionData = obj as IHasYPosition;
var comboData = obj as IHasCombo;
var sliderVelocityData = obj as IHasSliderVelocity;
switch (obj)
{
@@ -41,7 +42,8 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
NewCombo = comboData?.NewCombo ?? false,
ComboOffset = comboData?.ComboOffset ?? 0,
LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0,
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y
LegacyConvertedY = yPositionData?.Y ?? CatchHitObject.DEFAULT_LEGACY_CONVERT_Y,
SliderVelocity = sliderVelocityData?.SliderVelocity ?? 1
}.Yield();
case IHasDuration endTime:
@@ -17,6 +17,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private double placementStartTime;
private double placementEndTime;
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public BananaShowerPlacementBlueprint()
{
InternalChild = outline = new TimeSpanOutline();
@@ -49,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
case PlacementState.Active:
if (e.Button != MouseButton.Right) break;
EndPlacement(HitObject.Duration > 0);
EndPlacement(true);
return true;
}
@@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
public void UpdateHitObjectFromPath(JuiceStream hitObject)
{
// The SV setting may need to be changed for the current path.
var svBindable = hitObject.DifficultyControlPoint.SliderVelocityBindable;
var svBindable = hitObject.SliderVelocityBindable;
double svToVelocityFactor = hitObject.Velocity / svBindable.Value;
double requiredVelocity = path.ComputeRequiredVelocity();
@@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
private InputManager inputManager = null!;
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public JuiceStreamPlacementBlueprint()
{
InternalChildren = new Drawable[]
@@ -70,7 +72,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints
return true;
case MouseButton.Right:
EndPlacement(HitObject.Duration > 0);
EndPlacement(true);
return true;
}
@@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Catch.Edit
private void load()
{
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
RightSideToolboxContainer.Alpha = 0;
DistanceSpacingMultiplier.Disabled = true;
LayerBelowRuleset.Add(new PlayfieldBorder
@@ -133,7 +132,7 @@ namespace osu.Game.Rulesets.Catch.Edit
result.ScreenSpacePosition.X = screenSpacePosition.X;
if (snapType.HasFlagFast(SnapType.Grids))
if (snapType.HasFlagFast(SnapType.RelativeGrids))
{
if (distanceSnapGrid.IsPresent && distanceSnapGrid.GetSnappedPosition(result.ScreenSpacePosition) is SnapResult snapResult &&
Vector2.Distance(snapResult.ScreenSpacePosition, result.ScreenSpacePosition) < distance_snap_radius)
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDaycore : ModDaycore
{
public override double ScoreMultiplier => 0.3;
}
}
@@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
{
Precision = 0.1f,
MinValue = 1,
MinValue = 0,
MaxValue = 10,
ExtendedMaxValue = 11,
ReadCurrentFromDifficulty = diff => diff.CircleSize,
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Mods
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
{
Precision = 0.1f,
MinValue = 1,
MinValue = 0,
MaxValue = 10,
ExtendedMaxValue = 11,
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModDoubleTime : ModDoubleTime
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
}
}
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModHalfTime : ModHalfTime
{
public override double ScoreMultiplier => 0.3;
}
}
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Mods
{
public class CatchModNightcore : ModNightcore<CatchHitObject>
{
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
}
}
+5 -5
View File
@@ -22,11 +22,11 @@ namespace osu.Game.Rulesets.Catch.Objects
public override Judgement CreateJudgement() => new CatchBananaJudgement();
private static readonly List<HitSampleInfo> samples = new List<HitSampleInfo> { new BananaHitSampleInfo() };
private static readonly IList<HitSampleInfo> default_banana_samples = new List<HitSampleInfo> { new BananaHitSampleInfo() }.AsReadOnly();
public Banana()
{
Samples = samples;
Samples = default_banana_samples;
}
// override any external colour changes with banananana
@@ -47,18 +47,18 @@ namespace osu.Game.Rulesets.Catch.Objects
}
}
private class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
public class BananaHitSampleInfo : HitSampleInfo, IEquatable<BananaHitSampleInfo>
{
private static readonly string[] lookup_names = { "Gameplay/metronomelow", "Gameplay/catch-banana" };
public override IEnumerable<string> LookupNames => lookup_names;
public BananaHitSampleInfo(int volume = 0)
public BananaHitSampleInfo(int volume = 100)
: base(string.Empty, volume: volume)
{
}
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
=> new BananaHitSampleInfo(newVolume.GetOr(Volume));
public bool Equals(BananaHitSampleInfo? other)
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using System.Threading;
using osu.Game.Audio;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
@@ -39,6 +41,7 @@ namespace osu.Game.Rulesets.Catch.Objects
{
StartTime = time,
BananaIndex = i,
Samples = new List<HitSampleInfo> { new Banana.BananaHitSampleInfo(CreateHitSampleInfo().Volume) }
});
time += spacing;
+17 -3
View File
@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
@@ -16,7 +17,7 @@ using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Rulesets.Catch.Objects
{
public class JuiceStream : CatchHitObject, IHasPathWithRepeats
public class JuiceStream : CatchHitObject, IHasPathWithRepeats, IHasSliderVelocity
{
/// <summary>
/// Positional distance that results in a duration of one second, before any speed adjustments.
@@ -27,6 +28,19 @@ namespace osu.Game.Rulesets.Catch.Objects
public int RepeatCount { get; set; }
public BindableNumber<double> SliderVelocityBindable { get; } = new BindableDouble(1)
{
Precision = 0.01,
MinValue = 0.1,
MaxValue = 10
};
public double SliderVelocity
{
get => SliderVelocityBindable.Value;
set => SliderVelocityBindable.Value = value;
}
[JsonIgnore]
private double velocityFactor;
@@ -34,10 +48,10 @@ namespace osu.Game.Rulesets.Catch.Objects
private double tickDistanceFactor;
[JsonIgnore]
public double Velocity => velocityFactor * DifficultyControlPoint.SliderVelocity;
public double Velocity => velocityFactor * SliderVelocity;
[JsonIgnore]
public double TickDistance => tickDistanceFactor * DifficultyControlPoint.SliderVelocity;
public double TickDistance => tickDistanceFactor * SliderVelocity;
/// <summary>
/// The length of one span of this <see cref="JuiceStream"/>.
@@ -1,17 +1,30 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Scoring
{
public partial class CatchScoreProcessor : ScoreProcessor
{
private const int combo_cap = 200;
private const double combo_base = 4;
public CatchScoreProcessor()
: base(new CatchRuleset())
{
}
protected override double ClassicScoreMultiplier => 28;
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
{
return 600000 * comboProgress
+ 400000 * Accuracy.Value * accuracyProgress
+ bonusPortion;
}
protected override double GetComboScoreChange(JudgementResult result)
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(combo_cap, combo_base));
}
}
@@ -4,6 +4,7 @@
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning;
using osuTK.Graphics;
@@ -27,12 +28,12 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
{
if (lookup is GlobalSkinComponentLookup targetComponent)
if (lookup is SkinComponentsContainerLookup containerLookup)
{
switch (targetComponent.Lookup)
switch (containerLookup.Target)
{
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
var components = base.GetDrawableComponent(lookup) as SkinnableTargetComponentsContainer;
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
var components = base.GetDrawableComponent(lookup) as Container;
if (providesComboCounter && components != null)
{
@@ -63,12 +63,12 @@ namespace osu.Game.Rulesets.Catch.UI
updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value);
}
public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result)
public void OnRevertResult(JudgementResult result)
{
if (!result.Type.AffectsCombo() || !result.HasResult)
return;
updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value);
updateCombo(result.ComboAtJudgement, null);
}
private void updateCombo(int newCombo, Color4? hitObjectColour)
+2 -2
View File
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.UI
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result)
=> CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result);
private void onRevertResult(JudgementResult result)
=> CatcherArea.OnRevertResult(result);
}
}
+16 -7
View File
@@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Catch.UI
Origin = Anchor.TopCentre;
Size = new Vector2(BASE_SIZE);
if (difficulty != null)
Scale = calculateScale(difficulty);
@@ -254,7 +255,7 @@ namespace osu.Game.Rulesets.Catch.UI
}
}
public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
public void OnRevertResult(JudgementResult result)
{
var catchResult = (CatchJudgementResult)result;
@@ -268,8 +269,8 @@ namespace osu.Game.Rulesets.Catch.UI
SetHyperDashState();
}
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
caughtObjectContainer.RemoveAll(d => d.HitObject == result.HitObject, false);
droppedObjectTarget.RemoveAll(d => d.HitObject == result.HitObject, false);
}
/// <summary>
@@ -333,8 +334,11 @@ namespace osu.Game.Rulesets.Catch.UI
base.Update();
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
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.
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
@@ -414,10 +418,13 @@ namespace osu.Game.Rulesets.Catch.UI
private void clearPlate(DroppedObjectAnimation animation)
{
var droppedObjects = caughtObjectContainer.Children.Select(getDroppedObject).ToArray();
var caughtObjects = caughtObjectContainer.Children.ToArray();
caughtObjectContainer.Clear(false);
// Use the already returned PoolableDrawables for new objects
var droppedObjects = caughtObjects.Select(getDroppedObject).ToArray();
droppedObjectTarget.AddRange(droppedObjects);
foreach (var droppedObject in droppedObjects)
@@ -426,10 +433,10 @@ namespace osu.Game.Rulesets.Catch.UI
private void removeFromPlate(CaughtObject caughtObject, DroppedObjectAnimation animation)
{
var droppedObject = getDroppedObject(caughtObject);
caughtObjectContainer.Remove(caughtObject, false);
var droppedObject = getDroppedObject(caughtObject);
droppedObjectTarget.Add(droppedObject);
applyDropAnimation(droppedObject, animation);
@@ -452,6 +459,8 @@ namespace osu.Game.Rulesets.Catch.UI
break;
}
// Define lifetime start for dropped objects to be disposed correctly when rewinding replay
d.LifetimeStart = Clock.CurrentTime;
d.Expire();
}
+3 -3
View File
@@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Catch.UI
comboDisplay.OnNewResult(hitObject, result);
}
public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result)
public void OnRevertResult(JudgementResult result)
{
comboDisplay.OnRevertResult(hitObject, result);
Catcher.OnRevertResult(hitObject, result);
comboDisplay.OnRevertResult(result);
Catcher.OnRevertResult(result);
}
protected override void Update()
@@ -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.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using UIKit;
using osu.Framework.iOS;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Mania.Tests.iOS
{
@@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
{
public static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
GameApplication.Main(new OsuTestBrowser());
}
}
}
@@ -0,0 +1,67 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Tests.Editor
{
public partial class TestSceneObjectPlacement : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
[Resolved]
private OsuConfigManager config { get; set; } = null!;
[Test]
public void TestPlacementBeforeTrackStart()
{
AddStep("Seek to 0", () => EditorClock.Seek(0));
AddStep("Select note", () => InputManager.Key(Key.Number2));
AddStep("Hover negative span", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
});
AddStep("Click", () => InputManager.Click(MouseButton.Left));
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
}
[Test]
public void TestSeekOnNotePlacement()
{
double? initialTime = null;
AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, true));
placeObject();
AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking);
AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime));
}
[Test]
public void TestNoSeekOnNotePlacement()
{
double? initialTime = null;
AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, false));
placeObject();
AddAssert("not seeking", () => !EditorClock.IsSeeking);
AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime));
}
private void placeObject()
{
AddStep("select note placement tool", () => InputManager.Key(Key.Number2));
AddStep("move mouse to centre of last column", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Last().ScreenSpaceDrawQuad.Centre));
AddStep("place note", () => InputManager.Click(MouseButton.Left));
}
}
}
@@ -1,30 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Tests.Visual;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Tests.Editor
{
public partial class TestScenePlacementBeforeTrackStart : EditorTestScene
{
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
[Test]
public void TestPlacement()
{
AddStep("Seek to 0", () => EditorClock.Seek(0));
AddStep("Select note", () => InputManager.Key(Key.Number2));
AddStep("Hover negative span", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
});
AddStep("Click", () => InputManager.Click(MouseButton.Left));
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
}
}
}
@@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
@@ -40,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private const double time_tail = 4000;
private const double time_after_tail = 5250;
private List<JudgementResult> judgementResults;
private List<JudgementResult> judgementResults = new List<JudgementResult>();
/// <summary>
/// -----[ ]-----
@@ -61,6 +59,44 @@ namespace osu.Game.Rulesets.Mania.Tests
assertNoteJudgement(HitResult.IgnoreMiss);
}
/// <summary>
/// -----[ ]-----
/// x o
/// </summary>
[Test]
public void TestCorrectInput()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_head, ManiaAction.Key1),
new ManiaReplayFrame(time_tail),
});
assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Perfect);
assertNoteJudgement(HitResult.IgnoreHit);
}
/// <summary>
/// -----[ ]-----
/// x o
/// </summary>
[Test]
public void TestLateRelease()
{
performTest(new List<ReplayFrame>
{
new ManiaReplayFrame(time_head, ManiaAction.Key1),
new ManiaReplayFrame(time_after_tail),
});
assertHeadJudgement(HitResult.Perfect);
assertTickJudgement(HitResult.LargeTickHit);
assertTailJudgement(HitResult.Miss);
assertNoteJudgement(HitResult.IgnoreMiss);
}
/// <summary>
/// -----[ ]-----
/// x o
@@ -521,9 +557,9 @@ namespace osu.Game.Rulesets.Mania.Tests
private void assertLastTickJudgement(HitResult result)
=> AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type, () => Is.EqualTo(result));
private ScoreAccessibleReplayPlayer currentPlayer;
private ScoreAccessibleReplayPlayer currentPlayer = null!;
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject> beatmap = null)
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject>? beatmap = null)
{
if (beatmap == null)
{
@@ -569,15 +605,13 @@ namespace osu.Game.Rulesets.Mania.Tests
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor?.HasCompleted.Value == true);
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
}
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
{
public new ScoreProcessor ScoreProcessor => base.ScoreProcessor;
public new GameplayClockContainer GameplayClockContainer => base.GameplayClockContainer;
protected override bool PauseOnFocusLost => false;
public ScoreAccessibleReplayPlayer(Score score)
@@ -3,6 +3,9 @@
#nullable disable
using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
@@ -10,5 +13,19 @@ namespace osu.Game.Rulesets.Mania.Tests
public partial class TestSceneManiaPlayer : PlayerTestScene
{
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);
}
}
}
@@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\osu.TestProject.props" />
<ItemGroup Label="Package References">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.4.1" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.0" />
<PackageReference Include="NUnit3TestAdapter" Version="4.3.1" />
</ItemGroup>
<PropertyGroup Label="Project">
<OutputType>WinExe</OutputType>
@@ -14,7 +14,6 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Formats;
using osu.Game.Utils;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
@@ -49,15 +48,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
Debug.Assert(distanceData != null);
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
DifficultyControlPoint difficultyPoint = hitObject.DifficultyControlPoint;
double beatLength;
#pragma warning disable 618
if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint)
#pragma warning restore 618
beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier;
if (hitObject.LegacyBpmMultiplier.HasValue)
beatLength = timingPoint.BeatLength * hitObject.LegacyBpmMultiplier.Value;
else if (hitObject is IHasSliderVelocity hasSliderVelocity)
beatLength = timingPoint.BeatLength / hasSliderVelocity.SliderVelocity;
else
beatLength = timingPoint.BeatLength / difficultyPoint.SliderVelocity;
beatLength = timingPoint.BeatLength;
SpanCount = repeatsData?.SpanCount() ?? 1;
StartTime = (int)Math.Round(hitObject.StartTime);
@@ -21,18 +21,29 @@ namespace osu.Game.Rulesets.Mania.Configuration
{
base.InitialiseDefaults();
SetDefault(ManiaRulesetSetting.ScrollTime, 1500.0, DrawableManiaRuleset.MIN_TIME_RANGE, DrawableManiaRuleset.MAX_TIME_RANGE, 5);
SetDefault(ManiaRulesetSetting.ScrollSpeed, 8, 1, 40);
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
#pragma warning disable CS0618
// Although obsolete, this is still required to populate the bindable from the database in case migration is required.
SetDefault<double?>(ManiaRulesetSetting.ScrollTime, null);
if (Get<double?>(ManiaRulesetSetting.ScrollTime) is double scrollTime)
{
SetValue(ManiaRulesetSetting.ScrollSpeed, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime));
SetValue<double?>(ManiaRulesetSetting.ScrollTime, null);
}
#pragma warning restore CS0618
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting<double>(ManiaRulesetSetting.ScrollTime,
scrollTime => new SettingDescription(
rawValue: scrollTime,
new TrackedSetting<int>(ManiaRulesetSetting.ScrollSpeed,
speed => new SettingDescription(
rawValue: speed,
name: RulesetSettingsStrings.ScrollSpeed,
value: RulesetSettingsStrings.ScrollSpeedTooltip(scrollTime, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime))
value: RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(speed), speed)
)
)
};
@@ -40,7 +51,9 @@ namespace osu.Game.Rulesets.Mania.Configuration
public enum ManiaRulesetSetting
{
[Obsolete("Use ScrollSpeed instead.")] // Can be removed 2023-11-30
ScrollTime,
ScrollSpeed,
ScrollDirection,
TimingBasedNoteColouring
}
@@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
[Resolved]
private IScrollingInfo scrollingInfo { get; set; }
protected override bool IsValidForPlacement => HitObject.Duration > 0;
public HoldNotePlacementBlueprint()
: base(new HoldNote())
{
@@ -75,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return;
base.OnMouseUp(e);
EndPlacement(HitObject.Duration > 0);
EndPlacement(true);
}
private double originalStartTime;
@@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
specialStyle = new LabelledSwitchButton
{
Label = "Use special (N+1) style",
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 5k (4+1) or 8key (7+1) configurations.",
Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.",
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
}
};
+13 -31
View File
@@ -389,41 +389,23 @@ namespace osu.Game.Rulesets.Mania
return base.GetDisplayNameForHitResult(result);
}
public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
public override StatisticItem[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[]
{
new StatisticRow
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
Columns = new[]
{
new StatisticItem("Performance Breakdown", () => new PerformanceBreakdownChart(score, playableBeatmap)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y
}),
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
{
Columns = new[]
{
new StatisticItem("Timing Distribution", () => new HitEventTimingDistributionGraph(score.HitEvents)
{
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
}
},
new StatisticRow
RelativeSizeAxes = Axes.X,
Height = 250
}, true),
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
Columns = new[]
{
new StatisticItem(string.Empty, () => new SimpleStatisticTable(3, new SimpleStatisticItem[]
{
new AverageHitError(score.HitEvents),
new UnstableRate(score.HitEvents)
}), true)
}
}
new AverageHitError(score.HitEvents),
new UnstableRate(score.HitEvents)
}), true)
};
public override IRulesetFilterCriteria CreateRulesetFilterCriteria()
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
@@ -34,10 +33,10 @@ namespace osu.Game.Rulesets.Mania
LabelText = RulesetSettingsStrings.ScrollingDirection,
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
},
new SettingsSlider<double, ManiaScrollSlider>
new SettingsSlider<int, ManiaScrollSlider>
{
LabelText = RulesetSettingsStrings.ScrollSpeed,
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollTime),
Current = config.GetBindable<int>(ManiaRulesetSetting.ScrollSpeed),
KeyboardStep = 5
},
new SettingsCheckbox
@@ -48,9 +47,9 @@ namespace osu.Game.Rulesets.Mania
};
}
private partial class ManiaScrollSlider : OsuSliderBar<double>
private partial class ManiaScrollSlider : RoundedSliderBar<int>
{
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip(Current.Value, (int)Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / Current.Value));
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value);
}
}
}
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDaycore : ModDaycore
{
public override double ScoreMultiplier => 0.5;
}
}
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModDoubleTime : ModDoubleTime
{
public override double ScoreMultiplier => 1;
}
}
@@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModHalfTime : ModHalfTime
{
public override double ScoreMultiplier => 0.5;
}
}
@@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Mania.Mods
{
public class ManiaModNightcore : ModNightcore<ManiaHitObject>
{
public override double ScoreMultiplier => 1;
}
}
@@ -3,7 +3,6 @@
#nullable disable
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@@ -69,8 +68,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
private double? releaseTime;
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
public DrawableHoldNote()
: this(null)
{
@@ -221,6 +218,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Time.Current < releaseTime)
releaseTime = null;
if (Time.Current < HoldStartTime)
endHold();
// Pad the full size container so its contents (i.e. the masking container) reach under the tail.
// This is required for the tail to not be masked away, since it lies outside the bounds of the hold note.
sizingContainer.Padding = new MarginPadding
@@ -238,17 +238,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
};
// 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.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
// As the note is being held, adjust the size of the sizing container. This has two effects:
// 1. The contained masking container will mask the body and ticks.
// 2. The head note will move along with the new "head position" in the container.
if (Head.IsHit && releaseTime == null && DrawHeight > 0)
//
// As per stable, this should not apply for early hits, waiting until the object starts to touch the
// judgement area first.
if (Head.IsHit && releaseTime == null && DrawHeight > 0 && Time.Current >= HitObject.StartTime)
{
// How far past the hit target this hold note is. Always a positive value.
float yOffset = Math.Max(0, Direction.Value == ScrollingDirection.Up ? -Y : Y);
sizingContainer.Height = Math.Clamp(1 - yOffset / DrawHeight, 0, 1);
// How far past the hit target this hold note is.
float yOffset = Direction.Value == ScrollingDirection.Up ? -Y : Y;
sizingContainer.Height = 1 - yOffset / DrawHeight;
}
}
@@ -323,14 +327,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (e.Action != Action.Value)
return;
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
if (Time.Elapsed < 0)
return;
// Make sure a hold was started
if (HoldStartTime == null)
return;
// do not run any of this logic when rewinding, as it inverts order of presses/releases.
if (Time.Elapsed < 0)
return;
Tail.UpdateResult();
endHold();
@@ -351,13 +355,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
// Note: base.LoadSamples() isn't called since the slider plays the tail's hitsounds for the time being.
if (HitObject.SampleControlPoint == null)
{
throw new InvalidOperationException($"{nameof(HitObject)}s must always have an attached {nameof(HitObject.SampleControlPoint)}."
+ $" This is an indication that {nameof(HitObject.ApplyDefaults)} has not been invoked on {this}.");
}
slidingSample.Samples = HitObject.CreateSlidingSamples().Select(s => HitObject.SampleControlPoint.ApplyTo(s)).Cast<ISampleInfo>().ToArray();
slidingSample.Samples = HitObject.CreateSlidingSamples().Cast<ISampleInfo>().ToArray();
}
public override void StopAllSamples()
@@ -15,16 +15,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
public partial class DrawableHoldNoteTail : DrawableNote
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
private const double release_window_lenience = 1.5;
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
public DrawableHoldNoteTail()
: this(null)
@@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public void UpdateResult() => base.UpdateResult(true);
public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
Debug.Assert(HitObject.HitWindows != null);
// Factor in the release lenience
timeOffset /= release_window_lenience;
timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE;
if (!userTriggered)
{
@@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects
/// </summary>
public TailNote Tail { get; private set; }
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
/// <summary>
/// The time between ticks of this hold.
/// </summary>
@@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects
{
public class TailNote : Note
{
/// <summary>
/// Lenience of release hit windows. This is to make cases where the hold note release
/// is timed alongside presses of other hit objects less awkward.
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
/// </summary>
public const double RELEASE_WINDOW_LENIENCE = 1.5;
public override Judgement CreateJudgement() => new ManiaJudgement();
public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE;
}
}
@@ -10,7 +10,7 @@
["Gameplay/soft-hitnormal"],
["Gameplay/drum-hitnormal"]
],
"Samples": ["Gameplay/-hitnormal"]
"Samples": ["Gameplay/normal-hitnormal"]
}, {
"StartTime": 1875.0,
"EndTime": 2750.0,
@@ -19,7 +19,7 @@
["Gameplay/soft-hitnormal"],
["Gameplay/drum-hitnormal"]
],
"Samples": ["Gameplay/-hitnormal"]
"Samples": ["Gameplay/normal-hitnormal"]
}]
}, {
"StartTime": 3750.0,
@@ -1,23 +1,29 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Scoring
{
internal partial class ManiaScoreProcessor : ScoreProcessor
public partial class ManiaScoreProcessor : ScoreProcessor
{
private const double combo_base = 4;
public ManiaScoreProcessor()
: base(new ManiaRuleset())
{
}
protected override double DefaultAccuracyPortion => 0.99;
protected override double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion)
{
return 10000 * comboProgress
+ 990000 * Math.Pow(Accuracy.Value, 2 + 2 * Accuracy.Value) * accuracyProgress
+ bonusPortion;
}
protected override double DefaultComboPortion => 0.01;
protected override double ClassicScoreMultiplier => 16;
protected override double GetComboScoreChange(JudgementResult result)
=> Judgement.ToNumericResult(result.Type) * Math.Min(Math.Max(0.5, Math.Log(result.ComboAfterJudgement, combo_base)), Math.Log(400, combo_base));
}
}
@@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
largeFaint = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
Masking = true,
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
Blending = BlendingParameters.Additive,
@@ -80,11 +79,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
if (direction.NewValue == ScrollingDirection.Up)
{
Anchor = Anchor.TopCentre;
largeFaint.Anchor = Anchor.TopCentre;
largeFaint.Origin = Anchor.TopCentre;
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
}
else
{
Anchor = Anchor.BottomCentre;
largeFaint.Anchor = Anchor.BottomCentre;
largeFaint.Origin = Anchor.BottomCentre;
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
}
}
@@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
private void load(IScrollingInfo scrollingInfo)
{
RelativeSizeAxes = Axes.X;
Height = ArgonNotePiece.NOTE_HEIGHT;
Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO;
Masking = true;
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
@@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
{
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
private Drawable background = null!;
private Box foreground = null!;
private ArgonHoldNoteHittingLayer hittingLayer = null!;
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.
Masking = true;
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
Blending = BlendingParameters.Additive;
}
[BackgroundDependencyLoader(true)]
@@ -41,12 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
InternalChildren = new[]
{
background = new Box { RelativeSizeAxes = Axes.Both },
foreground = new Box
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Alpha = 0,
},
hittingLayer = new ArgonHoldNoteHittingLayer()
};
if (drawableObject != null)
@@ -54,44 +47,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
var holdNote = (DrawableHoldNote)drawableObject;
AccentColour.BindTo(holdNote.AccentColour);
IsHitting.BindTo(holdNote.IsHitting);
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
}
AccentColour.BindValueChanged(colour =>
{
background.Colour = colour.NewValue.Darken(1.2f);
foreground.Colour = colour.NewValue.Opacity(0.2f);
background.Colour = colour.NewValue.Darken(0.6f);
}, 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()
{
foreground.ClearTransforms();
foreground.Alpha = 0;
hittingLayer.Recycle();
}
}
}
@@ -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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osuTK;
@@ -16,47 +18,68 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
{
[Resolved]
private DrawableHitObject? drawableObject { get; set; }
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
private readonly Box shadeBackground;
private readonly Box shadeForeground;
private readonly Box foreground;
private readonly ArgonHoldNoteHittingLayer hittingLayer;
private readonly Box foregroundAdditive;
public ArgonHoldNoteTailPiece()
{
RelativeSizeAxes = Axes.X;
Height = ArgonNotePiece.NOTE_HEIGHT;
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
Masking = true;
InternalChildren = new Drawable[]
{
shadeBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.X,
Height = ArgonNotePiece.NOTE_HEIGHT,
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
Masking = true,
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,
},
hittingLayer = new ArgonHoldNoteHittingLayer(),
foregroundAdditive = new Box
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Height = 0.5f,
},
},
},
}
},
};
}
[BackgroundDependencyLoader(true)]
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(onDirectionChanged, true);
@@ -65,9 +88,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
{
accentColour.BindTo(drawableObject.AccentColour);
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)
{
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)
{
shadeBackground.Colour = accent.NewValue.Darken(1.7f);
shadeForeground.Colour = accent.NewValue.Darken(1.1f);
foreground.Colour = accent.NewValue.Darken(0.6f); // matches body
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 Box colouredBox;
private readonly Box shadow;
public ArgonNotePiece()
{
@@ -36,11 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
CornerRadius = CORNER_RADIUS;
Masking = true;
InternalChildren = new Drawable[]
InternalChildren = new[]
{
shadow = new Box
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black)
},
new Container
{
@@ -65,18 +65,22 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
RelativeSizeAxes = Axes.X,
Height = CORNER_RADIUS * 2,
},
new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Y = 4,
Icon = FontAwesome.Solid.AngleDown,
Size = new Vector2(20),
Scale = new Vector2(1, 0.7f)
}
CreateIcon(),
};
}
protected virtual Drawable CreateIcon() => new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Y = 4,
// TODO: replace with a non-squashed version.
// The 0.7f height scale should be removed.
Icon = FontAwesome.Solid.AngleDown,
Size = new Vector2(20),
Scale = new Vector2(1, 0.7f)
};
[BackgroundDependencyLoader(true)]
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
);
shadow.Colour = accent.NewValue.Darken(0.5f);
}
}
}
@@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
return new ArgonHoldNoteTailPiece();
case ManiaSkinComponents.HoldNoteHead:
return new ArgonHoldNoteHeadPiece();
case ManiaSkinComponents.Note:
return new ArgonNotePiece();
@@ -69,12 +71,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
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)
{
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
{
int column = maniaLookup.ColumnIndex ?? 0;
var stage = beatmap.GetStageForColumnIndex(column);
int columnIndex = maniaLookup.ColumnIndex ?? 0;
var stage = beatmap.GetStageForColumnIndex(columnIndex);
switch (maniaLookup.Lookup)
{
@@ -87,53 +100,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
return SkinUtils.As<TValue>(new Bindable<float>(
stage.IsSpecialColumn(column) ? 120 : 60
stage.IsSpecialColumn(columnIndex) ? 120 : 60
));
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
Color4 colour;
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();
}
}
var colour = getColourForLayout(columnIndex, stage);
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);
}
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();
}
}
}
}
@@ -2,12 +2,15 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Animations;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Testing;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
@@ -34,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
private Drawable? lightContainer;
private Drawable? light;
private LegacyNoteBodyStyle? bodyStyle;
public LegacyBodyPiece()
{
@@ -54,9 +58,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
float lightScale = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
?? 1;
float minimumColumnWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.MinimumColumnWidth)?.Value
?? 1;
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
// This animation is discarded and re-queried with the appropriate frame length afterwards.
var tmp = skin.GetAnimation(lightImage, true, false);
@@ -83,7 +84,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
};
}
bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
bodyStyle = skin.GetConfig<ManiaSkinConfigurationLookup, LegacyNoteBodyStyle>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.NoteBodyStyle))?.Value;
var wrapMode = bodyStyle == LegacyNoteBodyStyle.Stretch ? WrapMode.ClampToEdge : WrapMode.Repeat;
direction.BindTo(scrollingInfo.Direction);
isHitting.BindTo(holdNote.IsHitting);
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true).With(d =>
{
if (d == null)
return;
@@ -94,16 +102,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
d.Anchor = Anchor.TopCentre;
d.RelativeSizeAxes = Axes.Both;
d.Size = Vector2.One;
d.FillMode = FillMode.Stretch;
d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; // constant matching stable.
// Todo: Wrap?
});
if (bodySprite != null)
InternalChild = bodySprite;
direction.BindTo(scrollingInfo.Direction);
isHitting.BindTo(holdNote.IsHitting);
}
protected override void LoadComplete()
@@ -164,8 +167,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
if (bodySprite != null)
{
bodySprite.Origin = Anchor.BottomCentre;
bodySprite.Scale = new Vector2(1, -1);
bodySprite.Origin = Anchor.TopCentre;
bodySprite.Anchor = Anchor.BottomCentre; // needs to be flipped due to scale flip in Update.
}
if (light != null)
@@ -176,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
if (bodySprite != null)
{
bodySprite.Origin = Anchor.TopCentre;
bodySprite.Scale = Vector2.One;
bodySprite.Anchor = Anchor.TopCentre;
}
if (light != null)
@@ -207,6 +210,33 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
{
base.Update();
missFadeTime.Value ??= holdNote.HoldBrokenTime;
int scaleDirection = (direction.Value == ScrollingDirection.Down ? 1 : -1);
// here we go...
switch (bodyStyle)
{
case LegacyNoteBodyStyle.Stretch:
// this is how lazer works by default. nothing required.
if (bodySprite != null)
bodySprite.Scale = new Vector2(1, scaleDirection);
break;
default:
// this is where things get fucked up.
// honestly there's three modes to handle here but they seem really pointless?
// let's wait to see if anyone actually uses them in skins.
if (bodySprite != null)
{
var sprite = bodySprite as Sprite ?? bodySprite.ChildrenOfType<Sprite>().Single();
bodySprite.FillMode = FillMode.Stretch;
// i dunno this looks about right??
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
}
break;
}
}
protected override void Dispose(bool isDisposing)
@@ -33,12 +33,12 @@ namespace osu.Game.Rulesets.Mania.UI
public partial class DrawableManiaRuleset : DrawableScrollingRuleset<ManiaHitObject>
{
/// <summary>
/// The minimum time range. This occurs at a <see cref="relativeTimeRange"/> of 40.
/// The minimum time range. This occurs at a <see cref="ManiaRulesetSetting.ScrollSpeed"/> of 40.
/// </summary>
public const double MIN_TIME_RANGE = 290;
/// <summary>
/// The maximum time range. This occurs at a <see cref="relativeTimeRange"/> of 1.
/// The maximum time range. This occurs with a <see cref="ManiaRulesetSetting.ScrollSpeed"/> of 1.
/// </summary>
public const double MAX_TIME_RANGE = 11485;
@@ -69,7 +69,8 @@ namespace osu.Game.Rulesets.Mania.UI
protected override ScrollVisualisationMethod VisualisationMethod => scrollMethod;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
private readonly BindableDouble configTimeRange = new BindableDouble();
private readonly BindableInt configScrollSpeed = new BindableInt();
private double smoothTimeRange;
// Stores the current speed adjustment active in gameplay.
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
@@ -78,6 +79,9 @@ namespace osu.Game.Rulesets.Mania.UI
: base(ruleset, beatmap, mods)
{
BarLines = new BarLineGenerator<BarLine>(Beatmap).BarLines;
TimeRange.MinValue = 1;
TimeRange.MaxValue = MAX_TIME_RANGE;
}
[BackgroundDependencyLoader]
@@ -104,30 +108,28 @@ namespace osu.Game.Rulesets.Mania.UI
Config.BindWith(ManiaRulesetSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
Config.BindWith(ManiaRulesetSetting.ScrollTime, configTimeRange);
TimeRange.MinValue = configTimeRange.MinValue;
TimeRange.MaxValue = configTimeRange.MaxValue;
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
configScrollSpeed.BindValueChanged(speed => this.TransformTo(nameof(smoothTimeRange), ComputeScrollTime(speed.NewValue), 200, Easing.OutQuint));
TimeRange.Value = smoothTimeRange = ComputeScrollTime(configScrollSpeed.Value);
}
protected override void AdjustScrollSpeed(int amount)
{
this.TransformTo(nameof(relativeTimeRange), relativeTimeRange + amount, 200, Easing.OutQuint);
}
private double relativeTimeRange
{
get => MAX_TIME_RANGE / configTimeRange.Value;
set => configTimeRange.Value = MAX_TIME_RANGE / value;
}
protected override void AdjustScrollSpeed(int amount) => configScrollSpeed.Value += amount;
protected override void Update()
{
base.Update();
updateTimeRange();
}
private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
private void updateTimeRange() => TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value;
/// <summary>
/// Computes a scroll time (in milliseconds) from a scroll speed in the range of 1-40.
/// </summary>
/// <param name="scrollSpeed">The scroll speed.</param>
/// <returns>The scroll time.</returns>
public static double ComputeScrollTime(int scrollSpeed) => MAX_TIME_RANGE / scrollSpeed;
public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer();
@@ -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.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using UIKit;
using osu.Framework.iOS;
using osu.Game.Tests;
namespace osu.Game.Rulesets.Osu.Tests.iOS
{
@@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS
{
public static void Main(string[] args)
{
UIApplication.Main(args, null, typeof(AppDelegate));
GameApplication.Main(new OsuTestBrowser());
}
}
}
@@ -138,8 +138,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
var mergedSlider = (Slider)EditorBeatmap.SelectedHitObjects.First();
return slider1 is not null && mergedSlider.HeadCircle.Samples.SequenceEqual(slider1.HeadCircle.Samples)
&& mergedSlider.TailCircle.Samples.SequenceEqual(slider1.TailCircle.Samples)
&& mergedSlider.Samples.SequenceEqual(slider1.Samples)
&& mergedSlider.SampleControlPoint.IsRedundant(slider1.SampleControlPoint);
&& mergedSlider.Samples.SequenceEqual(slider1.Samples);
});
AddAssert("slider end is at same completion for last slider", () =>
@@ -181,10 +181,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
{
if (slider is null) return;
slider.SampleControlPoint.SampleBank = "soft";
slider.SampleControlPoint.SampleVolume = 70;
sample = new HitSampleInfo("hitwhistle");
slider.Samples.Add(sample);
sample = new HitSampleInfo("hitwhistle", HitSampleInfo.BANK_SOFT, volume: 70);
slider.Samples.Add(sample.With());
});
AddStep("select added slider", () =>
@@ -207,9 +205,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
AddAssert("sliders have hitsounds", hasHitsounds);
bool hasHitsounds() => sample is not null &&
EditorBeatmap.HitObjects.All(o => o.SampleControlPoint.SampleBank == "soft" &&
o.SampleControlPoint.SampleVolume == 70 &&
o.Samples.Contains(sample));
EditorBeatmap.HitObjects.All(o => o.Samples.Contains(sample));
}
private bool sliderCreatedFor(Slider s, double startTime, double endTime, params (Vector2 pos, PathType? pathType)[] expectedControlPoints)
@@ -199,8 +199,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
Precision.AlmostEquals(circle.StartTime, time, 1)
&& Precision.AlmostEquals(circle.Position, position, 0.01f)
&& circle.NewCombo == startsNewCombo
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples)
&& circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint);
&& circle.Samples.SequenceEqual(slider.HeadCircle.Samples);
}
private bool sliderRestored(Slider slider)
@@ -0,0 +1,31 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
using osuTK.Input;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModAutopilot : OsuModTestScene
{
[Test]
public void TestInstantResume()
{
CreateModTest(new ModTestData
{
Mod = new OsuModAutopilot(),
PassCondition = () => true,
Autoplay = false,
});
AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value);
AddStep("press pause", () => InputManager.PressKey(Key.Escape));
AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value);
AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape));
AddStep("press resume", () => InputManager.PressKey(Key.Escape));
AddUntilStep("wait for resume", () => !Player.IsResuming);
AddAssert("resumed", () => !Player.GameplayClockContainer.IsPaused.Value);
}
}
}
@@ -17,6 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModAutoplay : OsuModTestScene
{
[Test]
public void TestCursorPositionStoredToJudgement()
{
CreateModTest(new ModTestData
{
Autoplay = true,
PassCondition = () =>
Player.ScoreProcessor.JudgedHits >= 1
&& Player.ScoreProcessor.HitEvents.Any(e => e.Position != null)
});
}
[Test]
public void TestSpmUnaffectedByRateAdjust()
=> runSpmTest(new OsuModDaycore
@@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModBubbles : OsuModTestScene
{
[Test]
public void TestOsuModBubbles() => CreateModTest(new ModTestData
{
Mod = new OsuModBubbles(),
Autoplay = true,
PassCondition = () => true
});
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

@@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@@ -18,6 +19,7 @@ using osu.Framework.Testing.Input;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Configuration;
using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Screens.Play;
@@ -40,6 +42,8 @@ namespace osu.Game.Rulesets.Osu.Tests
private Drawable background;
private readonly Bindable<bool> ripples = new Bindable<bool>();
public TestSceneGameplayCursor()
{
var ruleset = new OsuRuleset();
@@ -57,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Tests
});
});
AddToggleStep("ripples", v => ripples.Value = v);
AddSliderStep("circle size", 0f, 10f, 0f, val =>
{
config.SetValue(OsuSetting.AutoCursorSize, true);
@@ -67,6 +73,13 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("test cursor container", () => loadContent(false));
}
[BackgroundDependencyLoader]
private void load()
{
var rulesetConfig = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
rulesetConfig.BindWith(OsuRulesetSetting.ShowCursorRipples, ripples);
}
[TestCase(1, 1)]
[TestCase(5, 1)]
[TestCase(10, 1)]
@@ -0,0 +1,156 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using osuTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public partial class TestSceneHitCircleLateFade : OsuTestScene
{
private float? alphaAtMiss;
[Test]
public void TestHitCircleClassicMod()
{
AddStep("Create hit circle", () =>
{
SelectedMods.Value = new Mod[] { new OsuModClassic() };
createCircle();
});
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
}
[Test]
public void TestHitCircleClassicAndFullHiddenMods()
{
AddStep("Create hit circle", () =>
{
SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModClassic() };
createCircle();
});
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
}
[Test]
public void TestHitCircleClassicAndApproachCircleOnlyHiddenMods()
{
AddStep("Create hit circle", () =>
{
SelectedMods.Value = new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, new OsuModClassic() };
createCircle();
});
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
}
[Test]
public void TestHitCircleNoMod()
{
AddStep("Create hit circle", () =>
{
SelectedMods.Value = Array.Empty<Mod>();
createCircle();
});
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
AddAssert("Opaque when missed", () => alphaAtMiss == 1);
}
[Test]
public void TestSliderClassicMod()
{
AddStep("Create slider", () =>
{
SelectedMods.Value = new Mod[] { new OsuModClassic() };
createSlider();
});
AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
AddAssert("Head circle transparent when missed", () => alphaAtMiss == 0);
}
[Test]
public void TestSliderNoMod()
{
AddStep("Create slider", () =>
{
SelectedMods.Value = Array.Empty<Mod>();
createSlider();
});
AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1);
}
private void createCircle()
{
alphaAtMiss = null;
DrawableHitCircle drawableHitCircle = new DrawableHitCircle(new HitCircle
{
StartTime = Time.Current + 500,
Position = new Vector2(250)
});
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
mod.ApplyToDrawableHitObject(drawableHitCircle);
drawableHitCircle.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
drawableHitCircle.OnNewResult += (_, _) =>
{
alphaAtMiss = drawableHitCircle.Alpha;
};
Child = drawableHitCircle;
}
private void createSlider()
{
alphaAtMiss = null;
DrawableSlider drawableSlider = new DrawableSlider(new Slider
{
StartTime = Time.Current + 500,
Position = new Vector2(250),
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
new Vector2(0, 100),
})
});
drawableSlider.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
drawableSlider.OnLoadComplete += _ =>
{
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
mod.ApplyToDrawableHitObject(drawableSlider.HeadCircle);
drawableSlider.HeadCircle.OnNewResult += (_, _) =>
{
alphaAtMiss = drawableSlider.HeadCircle.Alpha;
};
};
Child = drawableSlider;
}
}
}
@@ -439,7 +439,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
public TestSlider()
{
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f };
SliderVelocity = 0.1f;
DefaultsApplied += _ =>
{
@@ -21,7 +21,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
@@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests
[Resolved]
private OsuConfigManager config { get; set; } = null!;
private TestActionKeyCounter leftKeyCounter = null!;
private DefaultKeyCounter leftKeyCounter = null!;
private TestActionKeyCounter rightKeyCounter = null!;
private DefaultKeyCounter rightKeyCounter = null!;
private OsuInputManager osuInputManager = null!;
@@ -59,14 +59,14 @@ namespace osu.Game.Rulesets.Osu.Tests
Origin = Anchor.Centre,
Children = new Drawable[]
{
leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
Depth = float.MinValue,
X = -100,
},
rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
@@ -150,6 +150,42 @@ namespace osu.Game.Rulesets.Osu.Tests
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]
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
{
@@ -562,8 +598,8 @@ namespace osu.Game.Rulesets.Osu.Tests
private void assertKeyCounter(int left, int right)
{
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses.Value, () => Is.EqualTo(left));
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses.Value, () => Is.EqualTo(right));
}
private void releaseAllTouches()
@@ -579,11 +615,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler<OsuAction>
public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler<OsuAction>
{
public OsuAction Action { get; }
public TestActionKeyCounter(OsuAction action)
public TestActionKeyCounterTrigger(OsuAction action)
: base(action.ToString())
{
Action = action;
@@ -593,8 +629,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
if (e.Action == Action)
{
IsLit = true;
Increment();
Activate();
}
return false;
@@ -602,7 +637,8 @@ namespace osu.Game.Rulesets.Osu.Tests
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
{
if (e.Action == Action) IsLit = false;
if (e.Action == Action)
Deactivate();
}
}
@@ -15,6 +15,10 @@ using osuTK.Graphics;
using osu.Game.Rulesets.Mods;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Testing;
using osu.Game.Beatmaps.Legacy;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
@@ -22,6 +26,7 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Configuration;
namespace osu.Game.Rulesets.Osu.Tests
{
@@ -30,6 +35,27 @@ namespace osu.Game.Rulesets.Osu.Tests
{
private int depthIndex;
private readonly BindableBool snakingIn = new BindableBool();
private readonly BindableBool snakingOut = new BindableBool();
[SetUpSteps]
public void SetUpSteps()
{
AddToggleStep("toggle snaking", v =>
{
snakingIn.Value = v;
snakingOut.Value = v;
});
}
[BackgroundDependencyLoader]
private void load()
{
var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
}
[Test]
public void TestVariousSliders()
{
@@ -7,7 +7,6 @@ using NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -47,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = time_slider_start,
Position = new Vector2(0, 0),
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = velocity },
SliderVelocity = velocity,
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
@@ -8,7 +8,6 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Replays;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
@@ -350,7 +349,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = time_slider_start,
Position = new Vector2(0, 0),
DifficultyControlPoint = new DifficultyControlPoint { SliderVelocity = 0.1f },
SliderVelocity = 0.1f,
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,

Some files were not shown because too many files have changed in this diff Show More