mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 15:47:26 +08:00
Merge branch 'master' into fl-skill
This commit is contained in:
commit
aafbc3e0e4
163
.github/workflows/test-diffcalc.yml
vendored
Normal file
163
.github/workflows/test-diffcalc.yml
vendored
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
# Listens for new PR comments containing !pp check [id], and runs a diffcalc comparison against master.
|
||||||
|
# Usage:
|
||||||
|
# !pp check 0 | Runs only the osu! ruleset.
|
||||||
|
# !pp check 0 2 | Runs only the osu! and catch rulesets.
|
||||||
|
#
|
||||||
|
|
||||||
|
name: Diffcalc Consistency Checks
|
||||||
|
on:
|
||||||
|
issue_comment:
|
||||||
|
types: [ created ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
DB_USER: root
|
||||||
|
DB_HOST: 127.0.0.1
|
||||||
|
CONCURRENCY: 4
|
||||||
|
ALLOW_DOWNLOAD: 1
|
||||||
|
SAVE_DOWNLOADED: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
diffcalc:
|
||||||
|
name: Diffcalc
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
if: |
|
||||||
|
github.event.issue.pull_request &&
|
||||||
|
contains(github.event.comment.body, '!pp check') &&
|
||||||
|
(github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'OWNER')
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
ruleset:
|
||||||
|
- { name: osu, id: 0 }
|
||||||
|
- { name: taiko, id: 1 }
|
||||||
|
- { name: catch, id: 2 }
|
||||||
|
- { name: mania, id: 3 }
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql:8.0
|
||||||
|
env:
|
||||||
|
MYSQL_ALLOW_EMPTY_PASSWORD: yes
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Verify ruleset
|
||||||
|
if: contains(github.event.comment.body, matrix.ruleset.id) == false
|
||||||
|
run: |
|
||||||
|
echo "${{ github.event.comment.body }} doesn't contain ${{ matrix.ruleset.id }}"
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
- name: Verify MySQL connection from host
|
||||||
|
run: |
|
||||||
|
sudo apt-get install -y mysql-client
|
||||||
|
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "SHOW DATABASES"
|
||||||
|
|
||||||
|
- name: Create directory structure
|
||||||
|
run: |
|
||||||
|
mkdir -p $GITHUB_WORKSPACE/master/
|
||||||
|
mkdir -p $GITHUB_WORKSPACE/pr/
|
||||||
|
|
||||||
|
# Checkout osu
|
||||||
|
- name: Checkout osu (master)
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: ppy/osu
|
||||||
|
path: 'master/osu'
|
||||||
|
- name: Checkout osu (pr)
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
path: 'pr/osu'
|
||||||
|
|
||||||
|
# Checkout osu-difficulty-calculator
|
||||||
|
- name: Checkout osu-difficulty-calculator (master)
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: ppy/osu-difficulty-calculator
|
||||||
|
path: 'master/osu-difficulty-calculator'
|
||||||
|
- name: Checkout osu-difficulty-calculator (pr)
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
repository: ppy/osu-difficulty-calculator
|
||||||
|
path: 'pr/osu-difficulty-calculator'
|
||||||
|
|
||||||
|
- name: Install .NET 5.0.x
|
||||||
|
uses: actions/setup-dotnet@v1
|
||||||
|
with:
|
||||||
|
dotnet-version: "5.0.x"
|
||||||
|
|
||||||
|
# Sanity checks to make sure diffcalc is not run when incompatible.
|
||||||
|
- name: Build diffcalc (master)
|
||||||
|
run: |
|
||||||
|
cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator
|
||||||
|
./UseLocalOsu.sh
|
||||||
|
dotnet build
|
||||||
|
- name: Build diffcalc (pr)
|
||||||
|
run: |
|
||||||
|
cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator
|
||||||
|
./UseLocalOsu.sh
|
||||||
|
dotnet build
|
||||||
|
|
||||||
|
# Initial data imports
|
||||||
|
- name: Download + import data
|
||||||
|
run: |
|
||||||
|
PERFORMANCE_DATA_NAME=$(curl https://data.ppy.sh/ | grep performance_${{ matrix.ruleset.name }}_top | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
|
||||||
|
BEATMAPS_DATA_NAME=$(curl https://data.ppy.sh/ | grep osu_files | tail -1 | awk -F "\"" '{print $2}' | sed 's/\.tar\.bz2//g')
|
||||||
|
|
||||||
|
# Set env variable for further steps.
|
||||||
|
echo "BEATMAPS_PATH=$GITHUB_WORKSPACE/$BEATMAPS_DATA_NAME" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
cd $GITHUB_WORKSPACE
|
||||||
|
|
||||||
|
wget https://data.ppy.sh/$PERFORMANCE_DATA_NAME.tar.bz2
|
||||||
|
wget https://data.ppy.sh/$BEATMAPS_DATA_NAME.tar.bz2
|
||||||
|
tar -xf $PERFORMANCE_DATA_NAME.tar.bz2
|
||||||
|
tar -xf $BEATMAPS_DATA_NAME.tar.bz2
|
||||||
|
|
||||||
|
cd $GITHUB_WORKSPACE/$PERFORMANCE_DATA_NAME
|
||||||
|
|
||||||
|
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_master"
|
||||||
|
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "CREATE DATABASE osu_pr"
|
||||||
|
|
||||||
|
cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_master
|
||||||
|
cat *.sql | mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} --database=osu_pr
|
||||||
|
|
||||||
|
# Run diffcalc
|
||||||
|
- name: Run diffcalc (master)
|
||||||
|
env:
|
||||||
|
DB_NAME: osu_master
|
||||||
|
run: |
|
||||||
|
cd $GITHUB_WORKSPACE/master/osu-difficulty-calculator/osu.Server.DifficultyCalculator
|
||||||
|
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
|
||||||
|
- name: Run diffcalc (pr)
|
||||||
|
env:
|
||||||
|
DB_NAME: osu_pr
|
||||||
|
run: |
|
||||||
|
cd $GITHUB_WORKSPACE/pr/osu-difficulty-calculator/osu.Server.DifficultyCalculator
|
||||||
|
dotnet run -c:Release -- all -m ${{ matrix.ruleset.id }} -ac -c ${{ env.CONCURRENCY }}
|
||||||
|
|
||||||
|
# Print diffs
|
||||||
|
- name: Print diffs
|
||||||
|
run: |
|
||||||
|
mysql --host ${{ env.DB_HOST }} -u${{ env.DB_USER }} -e "
|
||||||
|
SELECT
|
||||||
|
m.beatmap_id,
|
||||||
|
m.mods,
|
||||||
|
m.diff_unified as 'sr_master',
|
||||||
|
p.diff_unified as 'sr_pr',
|
||||||
|
(p.diff_unified - m.diff_unified) as 'diff'
|
||||||
|
FROM osu_master.osu_beatmap_difficulty m
|
||||||
|
JOIN osu_pr.osu_beatmap_difficulty p
|
||||||
|
ON m.beatmap_id = p.beatmap_id
|
||||||
|
AND m.mode = p.mode
|
||||||
|
AND m.mods = p.mods
|
||||||
|
WHERE abs(m.diff_unified - p.diff_unified) > 0.1
|
||||||
|
ORDER BY abs(m.diff_unified - p.diff_unified)
|
||||||
|
DESC
|
||||||
|
LIMIT 10000;"
|
||||||
|
|
||||||
|
# Todo: Run ppcalc
|
@ -1,8 +1,8 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
|
||||||
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll" />
|
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0/osu.Game.Benchmarks.dll" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="--filter *" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0" />
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0" />
|
||||||
<option name="PASS_PARENT_ENVS" value="1" />
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
<option name="USE_MONO" value="0" />
|
<option name="USE_MONO" value="0" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
<option name="PROJECT_TFM" value="net5.0" />
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
<method v="2">
|
<method v="2">
|
||||||
<option name="Build" enabled="true" />
|
<option name="Build" />
|
||||||
</method>
|
</method>
|
||||||
</configuration>
|
</configuration>
|
||||||
</component>
|
</component>
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
# osu!
|
# osu!
|
||||||
|
|
||||||
[![Build status](https://ci.appveyor.com/api/projects/status/u2p01nx7l6og8buh?svg=true)](https://ci.appveyor.com/project/peppy/osu)
|
[![Build status](https://github.com/ppy/osu/actions/workflows/ci.yml/badge.svg?branch=master&event=push)](https://github.com/ppy/osu/actions/workflows/ci.yml)
|
||||||
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
|
[![GitHub release](https://img.shields.io/github/release/ppy/osu.svg)](https://github.com/ppy/osu/releases/latest)
|
||||||
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
|
[![CodeFactor](https://www.codefactor.io/repository/github/ppy/osu/badge)](https://www.codefactor.io/repository/github/ppy/osu)
|
||||||
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
|
[![dev chat](https://discordapp.com/api/guilds/188630481301012481/widget.png?style=shield)](https://discord.gg/ppy)
|
||||||
@ -31,12 +31,11 @@ If you are looking to install or test osu! without setting up a development envi
|
|||||||
|
|
||||||
**Latest build:**
|
**Latest build:**
|
||||||
|
|
||||||
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
|
||||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||||
|
|
||||||
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
|
||||||
|
|
||||||
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
|
|
||||||
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
|
||||||
|
|
||||||
## Developing a custom ruleset
|
## Developing a custom ruleset
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -51,8 +51,8 @@
|
|||||||
<Reference Include="Java.Interop" />
|
<Reference Include="Java.Interop" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.808.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.907.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.807.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.907.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
|
@ -20,8 +20,21 @@ namespace osu.Android
|
|||||||
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
[Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-archive")]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")]
|
||||||
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[] { "application/zip", "application/octet-stream", "application/download", "application/x-zip", "application/x-zip-compressed", "application/x-osu-archive" })]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")]
|
||||||
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")]
|
||||||
|
[IntentFilter(new[] { Intent.ActionSend, Intent.ActionSendMultiple }, Categories = new[] { Intent.CategoryDefault }, DataMimeTypes = new[]
|
||||||
|
{
|
||||||
|
"application/zip",
|
||||||
|
"application/octet-stream",
|
||||||
|
"application/download",
|
||||||
|
"application/x-zip",
|
||||||
|
"application/x-zip-compressed",
|
||||||
|
// newer official mime types (see https://osu.ppy.sh/wiki/en/osu%21_File_Formats).
|
||||||
|
"application/x-osu-beatmap-archive",
|
||||||
|
"application/x-osu-skin-archive",
|
||||||
|
"application/x-osu-replay",
|
||||||
|
})]
|
||||||
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
|
[IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryBrowsable, Intent.CategoryDefault }, DataSchemes = new[] { "osu", "osump" })]
|
||||||
public class OsuGameActivity : AndroidGameActivity
|
public class OsuGameActivity : AndroidGameActivity
|
||||||
{
|
{
|
||||||
@ -66,12 +79,14 @@ namespace osu.Android
|
|||||||
case Intent.ActionSendMultiple:
|
case Intent.ActionSendMultiple:
|
||||||
{
|
{
|
||||||
var uris = new List<Uri>();
|
var uris = new List<Uri>();
|
||||||
|
|
||||||
for (int i = 0; i < intent.ClipData?.ItemCount; i++)
|
for (int i = 0; i < intent.ClipData?.ItemCount; i++)
|
||||||
{
|
{
|
||||||
var content = intent.ClipData?.GetItemAt(i);
|
var content = intent.ClipData?.GetItemAt(i);
|
||||||
if (content != null)
|
if (content != null)
|
||||||
uris.Add(content.Uri);
|
uris.Add(content.Uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleImportFromUris(uris.ToArray());
|
handleImportFromUris(uris.ToArray());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -139,8 +139,8 @@ namespace osu.Desktop
|
|||||||
{
|
{
|
||||||
switch (activity)
|
switch (activity)
|
||||||
{
|
{
|
||||||
case UserActivity.SoloGame solo:
|
case UserActivity.InGame game:
|
||||||
return solo.Beatmap.ToString();
|
return game.Beatmap.ToString();
|
||||||
|
|
||||||
case UserActivity.Editing edit:
|
case UserActivity.Editing edit:
|
||||||
return edit.Beatmap.ToString();
|
return edit.Beatmap.ToString();
|
||||||
|
@ -5,23 +5,23 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game;
|
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
|
||||||
namespace osu.Desktop.Windows
|
namespace osu.Desktop.Windows
|
||||||
{
|
{
|
||||||
public class GameplayWinKeyBlocker : Component
|
public class GameplayWinKeyBlocker : Component
|
||||||
{
|
{
|
||||||
private Bindable<bool> disableWinKey;
|
private Bindable<bool> disableWinKey;
|
||||||
private Bindable<bool> localUserPlaying;
|
private IBindable<bool> localUserPlaying;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private GameHost host { get; set; }
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuGame game, OsuConfigManager config)
|
private void load(ILocalUserPlayInfo localUserInfo, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
localUserPlaying = game.LocalUserPlaying.GetBoundCopy();
|
localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy();
|
||||||
localUserPlaying.BindValueChanged(_ => updateBlocking());
|
localUserPlaying.BindValueChanged(_ => updateBlocking());
|
||||||
|
|
||||||
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
disableWinKey = config.GetBindable<bool>(OsuSetting.GameplayDisableWinKey);
|
||||||
|
34
osu.Game.Benchmarks/BenchmarkMod.cs
Normal file
34
osu.Game.Benchmarks/BenchmarkMod.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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 BenchmarkDotNet.Attributes;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkMod : BenchmarkTest
|
||||||
|
{
|
||||||
|
private OsuModDoubleTime mod;
|
||||||
|
|
||||||
|
[Params(1, 10, 100)]
|
||||||
|
public int Times { get; set; }
|
||||||
|
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
base.SetUp();
|
||||||
|
mod = new OsuModDoubleTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public int ModHashCode()
|
||||||
|
{
|
||||||
|
var hashCode = new HashCode();
|
||||||
|
|
||||||
|
for (int i = 0; i < Times; i++)
|
||||||
|
hashCode.Add(mod);
|
||||||
|
|
||||||
|
return hashCode.ToHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
62
osu.Game.Benchmarks/BenchmarkRuleset.cs
Normal file
62
osu.Game.Benchmarks/BenchmarkRuleset.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// 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 BenchmarkDotNet.Engines;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
|
||||||
|
namespace osu.Game.Benchmarks
|
||||||
|
{
|
||||||
|
public class BenchmarkRuleset : BenchmarkTest
|
||||||
|
{
|
||||||
|
private OsuRuleset ruleset;
|
||||||
|
private APIMod apiModDoubleTime;
|
||||||
|
private APIMod apiModDifficultyAdjust;
|
||||||
|
|
||||||
|
public override void SetUp()
|
||||||
|
{
|
||||||
|
base.SetUp();
|
||||||
|
ruleset = new OsuRuleset();
|
||||||
|
apiModDoubleTime = new APIMod { Acronym = "DT" };
|
||||||
|
apiModDifficultyAdjust = new APIMod { Acronym = "DA" };
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkToModDoubleTime()
|
||||||
|
{
|
||||||
|
apiModDoubleTime.ToMod(ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkToModDifficultyAdjust()
|
||||||
|
{
|
||||||
|
apiModDifficultyAdjust.ToMod(ruleset);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkGetAllMods()
|
||||||
|
{
|
||||||
|
ruleset.CreateAllMods().Consume(new Consumer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkGetAllModsForReference()
|
||||||
|
{
|
||||||
|
ruleset.AllMods.Consume(new Consumer());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkGetForAcronym()
|
||||||
|
{
|
||||||
|
ruleset.CreateModFromAcronym("DT");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Benchmark]
|
||||||
|
public void BenchmarkGetForType()
|
||||||
|
{
|
||||||
|
ruleset.CreateMod<ModDoubleTime>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using BenchmarkDotNet.Configs;
|
||||||
using BenchmarkDotNet.Running;
|
using BenchmarkDotNet.Running;
|
||||||
|
|
||||||
namespace osu.Game.Benchmarks
|
namespace osu.Game.Benchmarks
|
||||||
@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks
|
|||||||
{
|
{
|
||||||
BenchmarkSwitcher
|
BenchmarkSwitcher
|
||||||
.FromAssembly(typeof(Program).Assembly)
|
.FromAssembly(typeof(Program).Assembly)
|
||||||
.Run(args);
|
.Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.0" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
|
||||||
<PackageReference Include="nunit" Version="3.13.2" />
|
<PackageReference Include="nunit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@ -210,9 +210,9 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor
|
|||||||
new Vector2(50, 200),
|
new Vector2(50, 200),
|
||||||
}), 0.5);
|
}), 0.5);
|
||||||
AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count);
|
AddAssert("1 vertex per 1 nested HO", () => getVertices().Count == hitObject.NestedHitObjects.Count);
|
||||||
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type.Value == PathType.PerfectCurve);
|
AddAssert("slider path not yet changed", () => hitObject.Path.ControlPoints[0].Type == PathType.PerfectCurve);
|
||||||
addAddVertexSteps(150, 150);
|
addAddVertexSteps(150, 150);
|
||||||
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type.Value == PathType.Linear);
|
AddAssert("slider path change to linear", () => hitObject.Path.ControlPoints[0].Type == PathType.Linear);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () =>
|
private void addBlueprintStep(double time, float x, SliderPath sliderPath, double velocity) => AddStep("add selection blueprint", () =>
|
||||||
|
@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
} while (rng.Next(2) != 0);
|
} while (rng.Next(2) != 0);
|
||||||
|
|
||||||
int length = sliderPath.ControlPoints.Count - start + 1;
|
int length = sliderPath.ControlPoints.Count - start + 1;
|
||||||
sliderPath.ControlPoints[start].Type.Value = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier;
|
sliderPath.ControlPoints[start].Type = length <= 2 ? PathType.Linear : length == 3 ? PathType.PerfectCurve : PathType.Bezier;
|
||||||
} while (rng.Next(3) != 0);
|
} while (rng.Next(3) != 0);
|
||||||
|
|
||||||
if (rng.Next(5) == 0)
|
if (rng.Next(5) == 0)
|
||||||
@ -210,13 +210,13 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
path.ConvertToSliderPath(sliderPath, sliderStartY);
|
path.ConvertToSliderPath(sliderPath, sliderStartY);
|
||||||
Assert.That(sliderPath.Distance, Is.EqualTo(path.Distance).Within(1e-3));
|
Assert.That(sliderPath.Distance, Is.EqualTo(path.Distance).Within(1e-3));
|
||||||
Assert.That(sliderPath.ControlPoints[0].Position.Value.X, Is.EqualTo(path.Vertices[0].X));
|
Assert.That(sliderPath.ControlPoints[0].Position.X, Is.EqualTo(path.Vertices[0].X));
|
||||||
assertInvariants(path.Vertices, true);
|
assertInvariants(path.Vertices, true);
|
||||||
|
|
||||||
foreach (var point in sliderPath.ControlPoints)
|
foreach (var point in sliderPath.ControlPoints)
|
||||||
{
|
{
|
||||||
Assert.That(point.Type.Value, Is.EqualTo(PathType.Linear).Or.Null);
|
Assert.That(point.Type, Is.EqualTo(PathType.Linear).Or.Null);
|
||||||
Assert.That(sliderStartY + point.Position.Value.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT));
|
Assert.That(sliderStartY + point.Position.Y, Is.InRange(0, JuiceStreamPath.OSU_PLAYFIELD_HEIGHT));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 10; i++)
|
for (int i = 0; i < 10; i++)
|
||||||
|
120
osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
Normal file
120
osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Tests.Mods
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class CatchModMirrorTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestModMirror()
|
||||||
|
{
|
||||||
|
IBeatmap original = createBeatmap(false);
|
||||||
|
IBeatmap mirrored = createBeatmap(true);
|
||||||
|
|
||||||
|
assertEffectivePositionsMirrored(original, mirrored);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IBeatmap createBeatmap(bool withMirrorMod)
|
||||||
|
{
|
||||||
|
var beatmap = createRawBeatmap();
|
||||||
|
var mirrorMod = new CatchModMirror();
|
||||||
|
|
||||||
|
var beatmapProcessor = new CatchBeatmapProcessor(beatmap);
|
||||||
|
beatmapProcessor.PreProcess();
|
||||||
|
|
||||||
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
beatmapProcessor.PostProcess();
|
||||||
|
|
||||||
|
if (withMirrorMod)
|
||||||
|
mirrorMod.ApplyToBeatmap(beatmap);
|
||||||
|
|
||||||
|
return beatmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IBeatmap createRawBeatmap() => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects = new List<HitObject>
|
||||||
|
{
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
OriginalX = 150,
|
||||||
|
StartTime = 0
|
||||||
|
},
|
||||||
|
new Fruit
|
||||||
|
{
|
||||||
|
OriginalX = 450,
|
||||||
|
StartTime = 500
|
||||||
|
},
|
||||||
|
new JuiceStream
|
||||||
|
{
|
||||||
|
OriginalX = 250,
|
||||||
|
Path = new SliderPath
|
||||||
|
{
|
||||||
|
ControlPoints =
|
||||||
|
{
|
||||||
|
new PathControlPoint(new Vector2(-100, 1)),
|
||||||
|
new PathControlPoint(new Vector2(0, 2)),
|
||||||
|
new PathControlPoint(new Vector2(100, 3)),
|
||||||
|
new PathControlPoint(new Vector2(0, 4))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StartTime = 1000,
|
||||||
|
},
|
||||||
|
new BananaShower
|
||||||
|
{
|
||||||
|
StartTime = 5000,
|
||||||
|
Duration = 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static void assertEffectivePositionsMirrored(IBeatmap original, IBeatmap mirrored)
|
||||||
|
{
|
||||||
|
if (original.HitObjects.Count != mirrored.HitObjects.Count)
|
||||||
|
Assert.Fail($"Top-level object count mismatch (original: {original.HitObjects.Count}, mirrored: {mirrored.HitObjects.Count})");
|
||||||
|
|
||||||
|
for (int i = 0; i < original.HitObjects.Count; ++i)
|
||||||
|
{
|
||||||
|
var originalObject = (CatchHitObject)original.HitObjects[i];
|
||||||
|
var mirroredObject = (CatchHitObject)mirrored.HitObjects[i];
|
||||||
|
|
||||||
|
// banana showers themselves are exempt, as we only really care about their nested bananas' positions.
|
||||||
|
if (!effectivePositionMirrored(originalObject, mirroredObject) && !(originalObject is BananaShower))
|
||||||
|
Assert.Fail($"{originalObject.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalObject, mirroredObject)})");
|
||||||
|
|
||||||
|
if (originalObject.NestedHitObjects.Count != mirroredObject.NestedHitObjects.Count)
|
||||||
|
Assert.Fail($"{originalObject.GetType().Name} nested object count mismatch (original: {originalObject.NestedHitObjects.Count}, mirrored: {mirroredObject.NestedHitObjects.Count})");
|
||||||
|
|
||||||
|
for (int j = 0; j < originalObject.NestedHitObjects.Count; ++j)
|
||||||
|
{
|
||||||
|
var originalNested = (CatchHitObject)originalObject.NestedHitObjects[j];
|
||||||
|
var mirroredNested = (CatchHitObject)mirroredObject.NestedHitObjects[j];
|
||||||
|
|
||||||
|
if (!effectivePositionMirrored(originalNested, mirroredNested))
|
||||||
|
Assert.Fail($"{originalObject.GetType().Name}'s nested {originalNested.GetType().Name} at time {originalObject.StartTime} is not mirrored ({printEffectivePositions(originalNested, mirroredNested)})");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string printEffectivePositions(CatchHitObject original, CatchHitObject mirrored)
|
||||||
|
=> $"original X: {original.EffectiveX}, mirrored X is: {mirrored.EffectiveX}, mirrored X should be: {CatchPlayfield.WIDTH - original.EffectiveX}";
|
||||||
|
|
||||||
|
private static bool effectivePositionMirrored(CatchHitObject original, CatchHitObject mirrored)
|
||||||
|
=> Precision.AlmostEquals(original.EffectiveX, CatchPlayfield.WIDTH - mirrored.EffectiveX);
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
AddSliderStep<float>("circle size", 0, 8, 5, createCatcher);
|
||||||
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
AddToggleStep("hyper dash", t => this.ChildrenOfType<TestCatcherArea>().ForEach(area => area.ToggleHyperDash(t)));
|
||||||
|
AddToggleStep("toggle hit lighting", lighting => config.SetValue(OsuSetting.HitLighting, lighting));
|
||||||
|
|
||||||
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
|
AddStep("catch centered fruit", () => attemptCatch(new Fruit()));
|
||||||
AddStep("catch many random fruit", () =>
|
AddStep("catch many random fruit", () =>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
|||||||
|
|
||||||
case JuiceStream juiceStream:
|
case JuiceStream juiceStream:
|
||||||
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
|
// Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead.
|
||||||
lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.Value.X;
|
lastPosition = juiceStream.OriginalX + juiceStream.Path.ControlPoints[^1].Position.X;
|
||||||
|
|
||||||
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
|
// Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead.
|
||||||
lastStartTime = juiceStream.StartTime;
|
lastStartTime = juiceStream.StartTime;
|
||||||
|
@ -117,6 +117,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
{
|
{
|
||||||
new CatchModDifficultyAdjust(),
|
new CatchModDifficultyAdjust(),
|
||||||
new CatchModClassic(),
|
new CatchModClassic(),
|
||||||
|
new CatchModMirror(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.Automation:
|
case ModType.Automation:
|
||||||
|
@ -9,6 +9,7 @@ namespace osu.Game.Rulesets.Catch
|
|||||||
Banana,
|
Banana,
|
||||||
Droplet,
|
Droplet,
|
||||||
Catcher,
|
Catcher,
|
||||||
CatchComboCounter
|
CatchComboCounter,
|
||||||
|
HitExplosion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
namespace osu.Game.Rulesets.Catch.Difficulty.Skills
|
||||||
{
|
{
|
||||||
public class Movement : StrainSkill
|
public class Movement : StrainDecaySkill
|
||||||
{
|
{
|
||||||
private const float absolute_player_positioning_error = 16f;
|
private const float absolute_player_positioning_error = 16f;
|
||||||
private const float normalized_hitobject_radius = 41.0f;
|
private const float normalized_hitobject_radius = 41.0f;
|
||||||
|
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints.Components
|
|||||||
path.ConvertFromSliderPath(sliderPath);
|
path.ConvertFromSliderPath(sliderPath);
|
||||||
|
|
||||||
// If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices.
|
// If the original slider path has non-linear type segments, resample the vertices at nested hit object times to reduce the number of vertices.
|
||||||
if (sliderPath.ControlPoints.Any(p => p.Type.Value != null && p.Type.Value != PathType.Linear))
|
if (sliderPath.ControlPoints.Any(p => p.Type != null && p.Type != PathType.Linear))
|
||||||
{
|
{
|
||||||
path.ResampleVertices(hitObject.NestedHitObjects
|
path.ResampleVertices(hitObject.NestedHitObjects
|
||||||
.Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used.
|
.Skip(1).TakeWhile(h => !(h is Fruit)) // Only droplets in the first span are used.
|
||||||
|
@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Catch.Edit
|
|||||||
juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX);
|
juiceStream.OriginalX = selectionRange.GetFlippedPosition(juiceStream.OriginalX);
|
||||||
|
|
||||||
foreach (var point in juiceStream.Path.ControlPoints)
|
foreach (var point in juiceStream.Path.ControlPoints)
|
||||||
point.Position.Value *= new Vector2(-1, 1);
|
point.Position *= new Vector2(-1, 1);
|
||||||
|
|
||||||
EditorBeatmap.Update(juiceStream);
|
EditorBeatmap.Update(juiceStream);
|
||||||
return true;
|
return true;
|
||||||
|
87
osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
Normal file
87
osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
// 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 osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Mods
|
||||||
|
{
|
||||||
|
public class CatchModMirror : ModMirror, IApplicableToBeatmap
|
||||||
|
{
|
||||||
|
public override string Description => "Fruits are flipped horizontally.";
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// <see cref="IApplicableToBeatmap"/> is used instead of <see cref="IApplicableToHitObject"/>,
|
||||||
|
/// as <see cref="CatchBeatmapProcessor"/> applies offsets in <see cref="CatchBeatmapProcessor.PostProcess"/>.
|
||||||
|
/// <see cref="IApplicableToBeatmap"/> runs after post-processing, while <see cref="IApplicableToHitObject"/> runs before it.
|
||||||
|
/// </remarks>
|
||||||
|
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
foreach (var hitObject in beatmap.HitObjects)
|
||||||
|
applyToHitObject(hitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyToHitObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
var catchObject = (CatchHitObject)hitObject;
|
||||||
|
|
||||||
|
switch (catchObject)
|
||||||
|
{
|
||||||
|
case Fruit fruit:
|
||||||
|
mirrorEffectiveX(fruit);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JuiceStream juiceStream:
|
||||||
|
mirrorEffectiveX(juiceStream);
|
||||||
|
mirrorJuiceStreamPath(juiceStream);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BananaShower bananaShower:
|
||||||
|
mirrorBananaShower(bananaShower);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the effective X position of <paramref name="catchObject"/> and its nested hit objects.
|
||||||
|
/// </summary>
|
||||||
|
private static void mirrorEffectiveX(CatchHitObject catchObject)
|
||||||
|
{
|
||||||
|
catchObject.OriginalX = CatchPlayfield.WIDTH - catchObject.OriginalX;
|
||||||
|
catchObject.XOffset = -catchObject.XOffset;
|
||||||
|
|
||||||
|
foreach (var nested in catchObject.NestedHitObjects.Cast<CatchHitObject>())
|
||||||
|
{
|
||||||
|
nested.OriginalX = CatchPlayfield.WIDTH - nested.OriginalX;
|
||||||
|
nested.XOffset = -nested.XOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors the path of the <paramref name="juiceStream"/>.
|
||||||
|
/// </summary>
|
||||||
|
private static void mirrorJuiceStreamPath(JuiceStream juiceStream)
|
||||||
|
{
|
||||||
|
var controlPoints = juiceStream.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
|
||||||
|
foreach (var point in controlPoints)
|
||||||
|
point.Position = new Vector2(-point.Position.X, point.Position.Y);
|
||||||
|
|
||||||
|
juiceStream.Path = new SliderPath(controlPoints, juiceStream.Path.ExpectedDistance.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mirrors X positions of all bananas in the <paramref name="bananaShower"/>.
|
||||||
|
/// </summary>
|
||||||
|
private static void mirrorBananaShower(BananaShower bananaShower)
|
||||||
|
{
|
||||||
|
foreach (var banana in bananaShower.NestedHitObjects.OfType<Banana>())
|
||||||
|
banana.XOffset = CatchPlayfield.WIDTH - banana.XOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
|
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
|
||||||
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ namespace osu.Game.Rulesets.Catch.Objects
|
|||||||
|
|
||||||
for (int i = 1; i < vertices.Count; i++)
|
for (int i = 1; i < vertices.Count; i++)
|
||||||
{
|
{
|
||||||
sliderPath.ControlPoints[^1].Type.Value = PathType.Linear;
|
sliderPath.ControlPoints[^1].Type = PathType.Linear;
|
||||||
|
|
||||||
float deltaX = vertices[i].X - lastPosition.X;
|
float deltaX = vertices[i].X - lastPosition.X;
|
||||||
double length = vertices[i].Distance - currentDistance;
|
double length = vertices[i].Distance - currentDistance;
|
||||||
|
129
osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
Normal file
129
osu.Game.Rulesets.Catch/Skinning/Default/DefaultHitExplosion.cs
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Effects;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Default
|
||||||
|
{
|
||||||
|
public class DefaultHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
|
{
|
||||||
|
private CircularContainer largeFaint;
|
||||||
|
private CircularContainer smallFaint;
|
||||||
|
private CircularContainer directionalGlow1;
|
||||||
|
private CircularContainer directionalGlow2;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Size = new Vector2(20);
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
|
||||||
|
// scale roughly in-line with visual appearance of notes
|
||||||
|
const float initial_height = 10;
|
||||||
|
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
largeFaint = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
smallFaint = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow1 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
},
|
||||||
|
directionalGlow2 = new CircularContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
Size = new Vector2(0.01f, initial_height),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(HitExplosionEntry entry)
|
||||||
|
{
|
||||||
|
X = entry.Position;
|
||||||
|
Scale = new Vector2(entry.HitObject.Scale);
|
||||||
|
setColour(entry.ObjectColour);
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||||
|
applyTransforms(entry.HitObject.RandomSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyTransforms(int randomSeed)
|
||||||
|
{
|
||||||
|
const double duration = 400;
|
||||||
|
|
||||||
|
// we want our size to be very small so the glow dominates it.
|
||||||
|
largeFaint.Size = new Vector2(0.8f);
|
||||||
|
largeFaint
|
||||||
|
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
||||||
|
.FadeOut(duration * 2);
|
||||||
|
|
||||||
|
const float angle_variangle = 15; // should be less than 45
|
||||||
|
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
||||||
|
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
||||||
|
|
||||||
|
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setColour(Color4 objectColour)
|
||||||
|
{
|
||||||
|
const float roundness = 100;
|
||||||
|
|
||||||
|
largeFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
||||||
|
Roundness = 160,
|
||||||
|
Radius = 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
smallFaint.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
||||||
|
Roundness = 20,
|
||||||
|
Radius = 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
||||||
|
Roundness = roundness,
|
||||||
|
Radius = 40,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -70,13 +70,11 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
|
|
||||||
if (version < 2.3m)
|
if (version < 2.3m)
|
||||||
{
|
{
|
||||||
if (GetTexture(@"fruit-ryuuta") != null ||
|
if (hasOldStyleCatcherSprite())
|
||||||
GetTexture(@"fruit-ryuuta-0") != null)
|
|
||||||
return new LegacyCatcherOld();
|
return new LegacyCatcherOld();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GetTexture(@"fruit-catcher-idle") != null ||
|
if (hasNewStyleCatcherSprite())
|
||||||
GetTexture(@"fruit-catcher-idle-0") != null)
|
|
||||||
return new LegacyCatcherNew();
|
return new LegacyCatcherNew();
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -86,12 +84,26 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
|||||||
return new LegacyCatchComboCounter(Skin);
|
return new LegacyCatchComboCounter(Skin);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
case CatchSkinComponents.HitExplosion:
|
||||||
|
if (hasOldStyleCatcherSprite() || hasNewStyleCatcherSprite())
|
||||||
|
return new LegacyHitExplosion();
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool hasOldStyleCatcherSprite() =>
|
||||||
|
GetTexture(@"fruit-ryuuta") != null
|
||||||
|
|| GetTexture(@"fruit-ryuuta-0") != null;
|
||||||
|
|
||||||
|
private bool hasNewStyleCatcherSprite() =>
|
||||||
|
GetTexture(@"fruit-catcher-idle") != null
|
||||||
|
|| GetTexture(@"fruit-catcher-idle-0") != null;
|
||||||
|
|
||||||
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup)
|
||||||
{
|
{
|
||||||
switch (lookup)
|
switch (lookup)
|
||||||
|
@ -0,0 +1,94 @@
|
|||||||
|
// 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.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Catch.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||||
|
{
|
||||||
|
public class LegacyHitExplosion : CompositeDrawable, IHitExplosion
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private Catcher catcher { get; set; }
|
||||||
|
|
||||||
|
private const float catch_margin = (1 - Catcher.ALLOWED_CATCH_RANGE) / 2;
|
||||||
|
|
||||||
|
private readonly Sprite explosion1;
|
||||||
|
private readonly Sprite explosion2;
|
||||||
|
|
||||||
|
public LegacyHitExplosion()
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Scale = new Vector2(0.5f);
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
explosion1 = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Rotation = -90
|
||||||
|
},
|
||||||
|
explosion2 = new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomCentre,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Alpha = 0,
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Rotation = -90
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(SkinManager skins)
|
||||||
|
{
|
||||||
|
var defaultLegacySkin = skins.DefaultLegacySkin;
|
||||||
|
|
||||||
|
// sprite names intentionally swapped to match stable member naming / ease of cross-referencing
|
||||||
|
explosion1.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-2");
|
||||||
|
explosion2.Texture = defaultLegacySkin.GetTexture("scoreboard-explosion-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(HitExplosionEntry entry)
|
||||||
|
{
|
||||||
|
Colour = entry.ObjectColour;
|
||||||
|
|
||||||
|
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
||||||
|
{
|
||||||
|
float halfCatchWidth = catcher.CatchWidth / 2;
|
||||||
|
float explosionOffset = Math.Clamp(entry.Position, -halfCatchWidth + catch_margin * 3, halfCatchWidth - catch_margin * 3);
|
||||||
|
|
||||||
|
if (!(entry.HitObject is Droplet))
|
||||||
|
{
|
||||||
|
float scale = Math.Clamp(entry.JudgementResult.ComboAtJudgement / 200f, 0.35f, 1.125f);
|
||||||
|
|
||||||
|
explosion1.Scale = new Vector2(1, 0.9f);
|
||||||
|
explosion1.Position = new Vector2(explosionOffset, 0);
|
||||||
|
|
||||||
|
explosion1.FadeOutFromOne(300);
|
||||||
|
explosion1.ScaleTo(new Vector2(16 * scale, 1.1f), 160, Easing.Out);
|
||||||
|
}
|
||||||
|
|
||||||
|
explosion2.Scale = new Vector2(0.9f, 1);
|
||||||
|
explosion2.Position = new Vector2(explosionOffset, 0);
|
||||||
|
|
||||||
|
explosion2.FadeOutFromOne(700);
|
||||||
|
explosion2.ScaleTo(new Vector2(0.9f, 1.3f), 500, Easing.Out);
|
||||||
|
|
||||||
|
this.Delay(700).FadeOutFromOne();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,6 +23,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
public class Catcher : SkinReloadableDrawable
|
public class Catcher : SkinReloadableDrawable
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -106,7 +107,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Width of the area that can be used to attempt catches during gameplay.
|
/// Width of the area that can be used to attempt catches during gameplay.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
public readonly float CatchWidth;
|
||||||
|
|
||||||
private readonly SkinnableCatcher body;
|
private readonly SkinnableCatcher body;
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
Scale = calculateScale(difficulty);
|
Scale = calculateScale(difficulty);
|
||||||
|
|
||||||
catchWidth = CalculateCatchWidth(Scale);
|
CatchWidth = CalculateCatchWidth(Scale);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -193,7 +194,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (!(hitObject is PalpableCatchHitObject fruit))
|
if (!(hitObject is PalpableCatchHitObject fruit))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
float halfCatchWidth = catchWidth * 0.5f;
|
float halfCatchWidth = CatchWidth * 0.5f;
|
||||||
return fruit.EffectiveX >= X - halfCatchWidth &&
|
return fruit.EffectiveX >= X - halfCatchWidth &&
|
||||||
fruit.EffectiveX <= X + halfCatchWidth;
|
fruit.EffectiveX <= X + halfCatchWidth;
|
||||||
}
|
}
|
||||||
@ -216,7 +217,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
placeCaughtObject(palpableObject, positionInStack);
|
placeCaughtObject(palpableObject, positionInStack);
|
||||||
|
|
||||||
if (hitLighting.Value)
|
if (hitLighting.Value)
|
||||||
addLighting(hitObject, positionInStack.X, drawableObject.AccentColour.Value);
|
addLighting(result, drawableObject.AccentColour.Value, positionInStack.X);
|
||||||
}
|
}
|
||||||
|
|
||||||
// droplet doesn't affect the catcher state
|
// droplet doesn't affect the catcher state
|
||||||
@ -365,8 +366,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addLighting(CatchHitObject hitObject, float x, Color4 colour) =>
|
private void addLighting(JudgementResult judgementResult, Color4 colour, float x) =>
|
||||||
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, x, hitObject.Scale, colour, hitObject.RandomSeed));
|
hitExplosionContainer.Add(new HitExplosionEntry(Time.Current, judgementResult, colour, x));
|
||||||
|
|
||||||
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
private CaughtObject getCaughtObject(PalpableCatchHitObject source)
|
||||||
{
|
{
|
||||||
|
@ -1,129 +1,56 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Game.Rulesets.Catch.Skinning.Default;
|
||||||
using osu.Framework.Graphics.Effects;
|
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
public class HitExplosion : PoolableDrawableWithLifetime<HitExplosionEntry>
|
||||||
{
|
{
|
||||||
private readonly CircularContainer largeFaint;
|
private readonly SkinnableDrawable skinnableExplosion;
|
||||||
private readonly CircularContainer smallFaint;
|
|
||||||
private readonly CircularContainer directionalGlow1;
|
|
||||||
private readonly CircularContainer directionalGlow2;
|
|
||||||
|
|
||||||
public HitExplosion()
|
public HitExplosion()
|
||||||
{
|
{
|
||||||
Size = new Vector2(20);
|
RelativeSizeAxes = Axes.Both;
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.BottomCentre;
|
||||||
Origin = Anchor.BottomCentre;
|
Origin = Anchor.BottomCentre;
|
||||||
|
|
||||||
// scale roughly in-line with visual appearance of notes
|
InternalChild = skinnableExplosion = new SkinnableDrawable(new CatchSkinComponent(CatchSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
|
||||||
const float initial_height = 10;
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
{
|
||||||
largeFaint = new CircularContainer
|
CentreComponent = false,
|
||||||
{
|
Anchor = Anchor.BottomCentre,
|
||||||
Anchor = Anchor.Centre,
|
Origin = Anchor.BottomCentre
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
smallFaint = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
directionalGlow1 = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
},
|
|
||||||
directionalGlow2 = new CircularContainer
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Masking = true,
|
|
||||||
Size = new Vector2(0.01f, initial_height),
|
|
||||||
Blending = BlendingParameters.Additive,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnApply(HitExplosionEntry entry)
|
protected override void OnApply(HitExplosionEntry entry)
|
||||||
{
|
{
|
||||||
X = entry.Position;
|
base.OnApply(entry);
|
||||||
Scale = new Vector2(entry.Scale);
|
if (IsLoaded)
|
||||||
setColour(entry.ObjectColour);
|
apply(entry);
|
||||||
|
|
||||||
using (BeginAbsoluteSequence(entry.LifetimeStart))
|
|
||||||
applyTransforms(entry.RNGSeed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyTransforms(int randomSeed)
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
apply(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void apply(HitExplosionEntry? entry)
|
||||||
|
{
|
||||||
|
if (entry == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ApplyTransformsAt(double.MinValue, true);
|
||||||
ClearTransforms(true);
|
ClearTransforms(true);
|
||||||
|
|
||||||
const double duration = 400;
|
(skinnableExplosion.Drawable as IHitExplosion)?.Animate(entry);
|
||||||
|
LifetimeEnd = skinnableExplosion.Drawable.LatestTransformEndTime;
|
||||||
// we want our size to be very small so the glow dominates it.
|
|
||||||
largeFaint.Size = new Vector2(0.8f);
|
|
||||||
largeFaint
|
|
||||||
.ResizeTo(largeFaint.Size * new Vector2(5, 1), duration, Easing.OutQuint)
|
|
||||||
.FadeOut(duration * 2);
|
|
||||||
|
|
||||||
const float angle_variangle = 15; // should be less than 45
|
|
||||||
directionalGlow1.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 4);
|
|
||||||
directionalGlow2.Rotation = StatelessRNG.NextSingle(-angle_variangle, angle_variangle, randomSeed, 5);
|
|
||||||
|
|
||||||
this.FadeInFromZero(50).Then().FadeOut(duration, Easing.Out).Expire();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setColour(Color4 objectColour)
|
|
||||||
{
|
|
||||||
const float roundness = 100;
|
|
||||||
|
|
||||||
largeFaint.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.1f, objectColour, Color4.White, 0, 1).Opacity(0.3f),
|
|
||||||
Roundness = 160,
|
|
||||||
Radius = 200,
|
|
||||||
};
|
|
||||||
|
|
||||||
smallFaint.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.6f, objectColour, Color4.White, 0, 1),
|
|
||||||
Roundness = 20,
|
|
||||||
Radius = 50,
|
|
||||||
};
|
|
||||||
|
|
||||||
directionalGlow1.EdgeEffect = directionalGlow2.EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Colour = Interpolation.ValueAt(0.4f, objectColour, Color4.White, 0, 1),
|
|
||||||
Roundness = roundness,
|
|
||||||
Radius = 40,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public HitExplosionContainer()
|
public HitExplosionContainer()
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
AddInternal(pool = new DrawablePool<HitExplosion>(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,24 +2,42 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Catch.UI
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
{
|
{
|
||||||
public class HitExplosionEntry : LifetimeEntry
|
public class HitExplosionEntry : LifetimeEntry
|
||||||
{
|
{
|
||||||
public readonly float Position;
|
/// <summary>
|
||||||
public readonly float Scale;
|
/// The judgement result that triggered this explosion.
|
||||||
public readonly Color4 ObjectColour;
|
/// </summary>
|
||||||
public readonly int RNGSeed;
|
public JudgementResult JudgementResult { get; }
|
||||||
|
|
||||||
public HitExplosionEntry(double startTime, float position, float scale, Color4 objectColour, int rngSeed)
|
/// <summary>
|
||||||
|
/// The hitobject which triggered this explosion.
|
||||||
|
/// </summary>
|
||||||
|
public CatchHitObject HitObject => (CatchHitObject)JudgementResult.HitObject;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The accent colour of the object caught.
|
||||||
|
/// </summary>
|
||||||
|
public Color4 ObjectColour { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The position at which the object was caught.
|
||||||
|
/// </summary>
|
||||||
|
public float Position { get; }
|
||||||
|
|
||||||
|
public HitExplosionEntry(double startTime, JudgementResult judgementResult, Color4 objectColour, float position)
|
||||||
{
|
{
|
||||||
LifetimeStart = startTime;
|
LifetimeStart = startTime;
|
||||||
Position = position;
|
Position = position;
|
||||||
Scale = scale;
|
JudgementResult = judgementResult;
|
||||||
ObjectColour = objectColour;
|
ObjectColour = objectColour;
|
||||||
RNGSeed = rngSeed;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
Normal file
18
osu.Game.Rulesets.Catch/UI/IHitExplosion.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Catch.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Common interface for all hit explosion skinnables.
|
||||||
|
/// </summary>
|
||||||
|
public interface IHitExplosion
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Begins animating this <see cref="IHitExplosion"/>.
|
||||||
|
/// </summary>
|
||||||
|
void Animate(HitExplosionEntry entry);
|
||||||
|
}
|
||||||
|
}
|
@ -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 System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Screens.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
|
{
|
||||||
|
public class TestSceneManiaComposeScreen : EditorClockTestScene
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skins { get; set; }
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("setup compose screen", () =>
|
||||||
|
{
|
||||||
|
var editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition { Columns = 4 }))
|
||||||
|
{
|
||||||
|
BeatmapInfo = { Ruleset = new ManiaRuleset().RulesetInfo },
|
||||||
|
};
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[]
|
||||||
|
{
|
||||||
|
(typeof(EditorBeatmap), editorBeatmap),
|
||||||
|
(typeof(IBeatSnapProvider), editorBeatmap),
|
||||||
|
},
|
||||||
|
Child = new ComposeScreen { State = { Value = Visibility.Visible } },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for composer", () => this.ChildrenOfType<HitObjectComposer>().SingleOrDefault()?.IsLoaded == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDefaultSkin()
|
||||||
|
{
|
||||||
|
AddStep("set default skin", () => skins.CurrentSkinInfo.Value = SkinInfo.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestLegacySkin()
|
||||||
|
{
|
||||||
|
AddStep("set legacy skin", () => skins.CurrentSkinInfo.Value = DefaultLegacySkin.Info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -10,7 +10,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
||||||
{
|
{
|
||||||
public class Strain : StrainSkill
|
public class Strain : StrainDecaySkill
|
||||||
{
|
{
|
||||||
private const double individual_decay_base = 0.125;
|
private const double individual_decay_base = 0.125;
|
||||||
private const double overall_decay_base = 0.30;
|
private const double overall_decay_base = 0.30;
|
||||||
@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty.Skills
|
|||||||
return individualStrain + overallStrain - CurrentStrain;
|
return individualStrain + overallStrain - CurrentStrain;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double GetPeakStrain(double offset)
|
protected override double CalculateInitialStrain(double offset)
|
||||||
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
|
=> applyDecay(individualStrain, offset - Previous[0].StartTime, individual_decay_base)
|
||||||
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);
|
+ applyDecay(overallStrain, offset - Previous[0].StartTime, overall_decay_base);
|
||||||
|
|
||||||
|
46
osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs
Normal file
46
osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||||
|
{
|
||||||
|
public class ManiaSetupSection : RulesetSetupSection
|
||||||
|
{
|
||||||
|
private LabelledSwitchButton specialStyle;
|
||||||
|
|
||||||
|
public ManiaSetupSection()
|
||||||
|
: base(new ManiaRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
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.",
|
||||||
|
Current = { Value = Beatmap.BeatmapInfo.SpecialStyle }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
specialStyle.Current.BindValueChanged(_ => updateBeatmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBeatmap()
|
||||||
|
{
|
||||||
|
Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -27,11 +27,13 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Configuration;
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Difficulty;
|
using osu.Game.Rulesets.Mania.Difficulty;
|
||||||
using osu.Game.Rulesets.Mania.Edit;
|
using osu.Game.Rulesets.Mania.Edit;
|
||||||
|
using osu.Game.Rulesets.Mania.Edit.Setup;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
using osu.Game.Rulesets.Mania.Skinning.Legacy;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania
|
namespace osu.Game.Rulesets.Mania
|
||||||
@ -390,6 +392,8 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
return new ManiaFilterCriteria();
|
return new ManiaFilterCriteria();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override RulesetSetupSection CreateEditorSetupSection() => new ManiaSetupSection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum PlayfieldType
|
public enum PlayfieldType
|
||||||
|
@ -275,9 +275,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
beginHoldAt(Time.Current - Head.HitObject.StartTime);
|
beginHoldAt(Time.Current - Head.HitObject.StartTime);
|
||||||
Head.UpdateResult();
|
|
||||||
|
|
||||||
return true;
|
return Head.UpdateResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void beginHoldAt(double timeOffset)
|
private void beginHoldAt(double timeOffset)
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public bool UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,6 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Rulesets.Mania.UI;
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
@ -29,11 +28,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
[Resolved(canBeNull: true)]
|
[Resolved(canBeNull: true)]
|
||||||
private ManiaPlayfield playfield { get; set; }
|
private ManiaPlayfield playfield { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the samples that are played by this object during gameplay.
|
|
||||||
/// </summary>
|
|
||||||
public ISampleInfo[] GetGameplaySamples() => Samples.Samples;
|
|
||||||
|
|
||||||
protected override float SamplePlaybackPosition
|
protected override float SamplePlaybackPosition
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -19,6 +18,7 @@ using osuTK;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
{
|
{
|
||||||
@ -28,12 +28,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
public const float COLUMN_WIDTH = 80;
|
public const float COLUMN_WIDTH = 80;
|
||||||
public const float SPECIAL_COLUMN_WIDTH = 70;
|
public const float SPECIAL_COLUMN_WIDTH = 70;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// For hitsounds played by this <see cref="Column"/> (i.e. not as a result of hitting a hitobject),
|
|
||||||
/// a certain number of samples are allowed to be played concurrently so that it feels better when spam-pressing the key.
|
|
||||||
/// </summary>
|
|
||||||
private const int max_concurrent_hitsounds = OsuGameBase.SAMPLE_CONCURRENCY;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The index of this column as part of the whole playfield.
|
/// The index of this column as part of the whole playfield.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -45,10 +39,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
internal readonly Container TopLevelContainer;
|
internal readonly Container TopLevelContainer;
|
||||||
private readonly DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
private readonly DrawablePool<PoolableHitExplosion> hitExplosionPool;
|
||||||
private readonly OrderedHitPolicy hitPolicy;
|
private readonly OrderedHitPolicy hitPolicy;
|
||||||
private readonly Container<SkinnableSound> hitSounds;
|
|
||||||
|
|
||||||
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
||||||
|
|
||||||
|
private readonly GameplaySampleTriggerSource sampleTriggerSource;
|
||||||
|
|
||||||
public Column(int index)
|
public Column(int index)
|
||||||
{
|
{
|
||||||
Index = index;
|
Index = index;
|
||||||
@ -64,6 +58,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
InternalChildren = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
hitExplosionPool = new DrawablePool<PoolableHitExplosion>(5),
|
hitExplosionPool = new DrawablePool<PoolableHitExplosion>(5),
|
||||||
|
sampleTriggerSource = new GameplaySampleTriggerSource(HitObjectContainer),
|
||||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||||
background.CreateProxy(),
|
background.CreateProxy(),
|
||||||
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
||||||
@ -72,12 +67,6 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
background,
|
background,
|
||||||
hitSounds = new Container<SkinnableSound>
|
|
||||||
{
|
|
||||||
Name = "Column samples pool",
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Children = Enumerable.Range(0, max_concurrent_hitsounds).Select(_ => new SkinnableSound()).ToArray()
|
|
||||||
},
|
|
||||||
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -133,29 +122,12 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
HitObjectArea.Explosions.Add(hitExplosionPool.Get(e => e.Apply(result)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int nextHitSoundIndex;
|
|
||||||
|
|
||||||
public bool OnPressed(ManiaAction action)
|
public bool OnPressed(ManiaAction action)
|
||||||
{
|
{
|
||||||
if (action != Action.Value)
|
if (action != Action.Value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var nextObject =
|
sampleTriggerSource.Play();
|
||||||
HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
|
|
||||||
// fallback to non-alive objects to find next off-screen object
|
|
||||||
HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
|
|
||||||
HitObjectContainer.Objects.LastOrDefault();
|
|
||||||
|
|
||||||
if (nextObject is DrawableManiaHitObject maniaObject)
|
|
||||||
{
|
|
||||||
var hitSound = hitSounds[nextHitSoundIndex];
|
|
||||||
|
|
||||||
hitSound.Samples = maniaObject.GetGameplaySamples();
|
|
||||||
hitSound.Play();
|
|
||||||
|
|
||||||
nextHitSoundIndex = (nextHitSoundIndex + 1) % max_concurrent_hitsounds;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public override void PlayAnimation()
|
public override void PlayAnimation()
|
||||||
{
|
{
|
||||||
base.PlayAnimation();
|
|
||||||
|
|
||||||
switch (Result)
|
switch (Result)
|
||||||
{
|
{
|
||||||
case HitResult.None:
|
case HitResult.None:
|
||||||
case HitResult.Miss:
|
case HitResult.Miss:
|
||||||
|
base.PlayAnimation();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -52,6 +51,8 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
this.Delay(50)
|
this.Delay(50)
|
||||||
.ScaleTo(0.75f, 250)
|
.ScaleTo(0.75f, 250)
|
||||||
.FadeOut(200);
|
.FadeOut(200);
|
||||||
|
|
||||||
|
// osu!mania uses a custom fade length, so the base call is intentionally omitted.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
AddAssert("last connection displayed", () =>
|
AddAssert("last connection displayed", () =>
|
||||||
{
|
{
|
||||||
var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position.Value == new Vector2(300));
|
var lastConnection = visualiser.Connections.Last(c => c.ControlPoint.Position == new Vector2(300));
|
||||||
return lastConnection.DrawWidth > 50;
|
return lastConnection.DrawWidth > 50;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -166,14 +166,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
AddStep($"move mouse to control point {index}", () =>
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
{
|
{
|
||||||
Vector2 position = slider.Path.ControlPoints[index].Position.Value;
|
Vector2 position = slider.Path.ControlPoints[index].Position;
|
||||||
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position));
|
InputManager.MoveMouseTo(visualiser.Pieces[0].Parent.ToScreenSpace(position));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertControlPointPathType(int controlPointIndex, PathType? type)
|
private void assertControlPointPathType(int controlPointIndex, PathType? type)
|
||||||
{
|
{
|
||||||
AddAssert($"point {controlPointIndex} is {type}", () => slider.Path.ControlPoints[controlPointIndex].Type.Value == type);
|
AddAssert($"point {controlPointIndex} is {type}", () => slider.Path.ControlPoints[controlPointIndex].Type == type);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addContextMenuItemStep(string contextMenuText)
|
private void addContextMenuItemStep(string contextMenuText)
|
||||||
|
@ -108,9 +108,9 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestDragControlPointPathAfterChangingType()
|
public void TestDragControlPointPathAfterChangingType()
|
||||||
{
|
{
|
||||||
AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type.Value = PathType.Bezier);
|
AddStep("change type to bezier", () => slider.Path.ControlPoints[2].Type = PathType.Bezier);
|
||||||
AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10))));
|
AddStep("add point", () => slider.Path.ControlPoints.Add(new PathControlPoint(new Vector2(500, 10))));
|
||||||
AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type.Value = PathType.PerfectCurve);
|
AddStep("change type to perfect", () => slider.Path.ControlPoints[3].Type = PathType.PerfectCurve);
|
||||||
|
|
||||||
moveMouseToControlPoint(4);
|
moveMouseToControlPoint(4);
|
||||||
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
AddStep("hold", () => InputManager.PressButton(MouseButton.Left));
|
||||||
@ -137,15 +137,15 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
AddStep($"move mouse to control point {index}", () =>
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
{
|
{
|
||||||
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
|
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position;
|
||||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type.Value == type);
|
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => slider.Path.ControlPoints[index].Type == type);
|
||||||
|
|
||||||
private void assertControlPointPosition(int index, Vector2 position) =>
|
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position.Value, 1));
|
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, slider.Path.ControlPoints[index].Position, 1));
|
||||||
|
|
||||||
private class TestSliderBlueprint : SliderSelectionBlueprint
|
private class TestSliderBlueprint : SliderSelectionBlueprint
|
||||||
{
|
{
|
||||||
|
@ -385,10 +385,10 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
|
|
||||||
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected);
|
private void assertControlPointCount(int expected) => AddAssert($"has {expected} control points", () => getSlider().Path.ControlPoints.Count == expected);
|
||||||
|
|
||||||
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type.Value == type);
|
private void assertControlPointType(int index, PathType type) => AddAssert($"control point {index} is {type}", () => getSlider().Path.ControlPoints[index].Type == type);
|
||||||
|
|
||||||
private void assertControlPointPosition(int index, Vector2 position) =>
|
private void assertControlPointPosition(int index, Vector2 position) =>
|
||||||
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position.Value, 1));
|
AddAssert($"control point {index} at {position}", () => Precision.AlmostEquals(position, getSlider().Path.ControlPoints[index].Position, 1));
|
||||||
|
|
||||||
private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;
|
private Slider getSlider() => HitObjectContainer.Count > 0 ? ((DrawableSlider)HitObjectContainer[0]).HitObject : null;
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
|||||||
{
|
{
|
||||||
AddStep($"move mouse to control point {index}", () =>
|
AddStep($"move mouse to control point {index}", () =>
|
||||||
{
|
{
|
||||||
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position.Value;
|
Vector2 position = slider.Position + slider.Path.ControlPoints[index].Position;
|
||||||
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="Moq" Version="4.16.1" />
|
<PackageReference Include="Moq" Version="4.16.1" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
|
@ -10,7 +10,7 @@ using osu.Framework.Utils;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||||
{
|
{
|
||||||
public abstract class OsuStrainSkill : StrainSkill
|
public abstract class OsuStrainSkill : StrainDecaySkill
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
/// The number of sections with the highest strains, which the peak strain reductions will apply to.
|
||||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void updateConnectingPath()
|
private void updateConnectingPath()
|
||||||
{
|
{
|
||||||
Position = slider.StackedPosition + ControlPoint.Position.Value;
|
Position = slider.StackedPosition + ControlPoint.Position;
|
||||||
|
|
||||||
path.ClearVertices();
|
path.ClearVertices();
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
path.AddVertex(Vector2.Zero);
|
path.AddVertex(Vector2.Zero);
|
||||||
path.AddVertex(slider.Path.ControlPoints[nextIndex].Position.Value - ControlPoint.Position.Value);
|
path.AddVertex(slider.Path.ControlPoints[nextIndex].Position - ControlPoint.Position);
|
||||||
|
|
||||||
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
private IBindable<Vector2> sliderPosition;
|
private IBindable<Vector2> sliderPosition;
|
||||||
private IBindable<float> sliderScale;
|
private IBindable<float> sliderScale;
|
||||||
private IBindable<Vector2> controlPointPosition;
|
|
||||||
|
|
||||||
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
public PathControlPointPiece(Slider slider, PathControlPoint controlPoint)
|
||||||
{
|
{
|
||||||
@ -69,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
updatePathType();
|
updatePathType();
|
||||||
});
|
});
|
||||||
|
|
||||||
controlPoint.Type.BindValueChanged(_ => updateMarkerDisplay());
|
controlPoint.Changed += updateMarkerDisplay;
|
||||||
|
|
||||||
Origin = Anchor.Centre;
|
Origin = Anchor.Centre;
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
@ -117,9 +116,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
sliderPosition = slider.PositionBindable.GetBoundCopy();
|
sliderPosition = slider.PositionBindable.GetBoundCopy();
|
||||||
sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
|
sliderPosition.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
controlPointPosition = ControlPoint.Position.GetBoundCopy();
|
|
||||||
controlPointPosition.BindValueChanged(_ => updateMarkerDisplay());
|
|
||||||
|
|
||||||
sliderScale = slider.ScaleBindable.GetBoundCopy();
|
sliderScale = slider.ScaleBindable.GetBoundCopy();
|
||||||
sliderScale.BindValueChanged(_ => updateMarkerDisplay());
|
sliderScale.BindValueChanged(_ => updateMarkerDisplay());
|
||||||
|
|
||||||
@ -174,8 +170,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
if (e.Button == MouseButton.Left)
|
if (e.Button == MouseButton.Left)
|
||||||
{
|
{
|
||||||
dragStartPosition = ControlPoint.Position.Value;
|
dragStartPosition = ControlPoint.Position;
|
||||||
dragPathType = PointsInSegment[0].Type.Value;
|
dragPathType = PointsInSegment[0].Type;
|
||||||
|
|
||||||
changeHandler?.BeginChange();
|
changeHandler?.BeginChange();
|
||||||
return true;
|
return true;
|
||||||
@ -186,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
protected override void OnDrag(DragEvent e)
|
protected override void OnDrag(DragEvent e)
|
||||||
{
|
{
|
||||||
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position.Value).ToArray();
|
Vector2[] oldControlPoints = slider.Path.ControlPoints.Select(cp => cp.Position).ToArray();
|
||||||
var oldPosition = slider.Position;
|
var oldPosition = slider.Position;
|
||||||
var oldStartTime = slider.StartTime;
|
var oldStartTime = slider.StartTime;
|
||||||
|
|
||||||
@ -202,15 +198,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
// Since control points are relative to the position of the slider, they all need to be offset backwards by the delta
|
||||||
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
for (int i = 1; i < slider.Path.ControlPoints.Count; i++)
|
||||||
slider.Path.ControlPoints[i].Position.Value -= movementDelta;
|
slider.Path.ControlPoints[i].Position -= movementDelta;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
ControlPoint.Position.Value = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
ControlPoint.Position = dragStartPosition + (e.MousePosition - e.MouseDownPosition);
|
||||||
|
|
||||||
if (!slider.Path.HasValidLength)
|
if (!slider.Path.HasValidLength)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < slider.Path.ControlPoints.Count; i++)
|
for (var i = 0; i < slider.Path.ControlPoints.Count; i++)
|
||||||
slider.Path.ControlPoints[i].Position.Value = oldControlPoints[i];
|
slider.Path.ControlPoints[i].Position = oldControlPoints[i];
|
||||||
|
|
||||||
slider.Position = oldPosition;
|
slider.Position = oldPosition;
|
||||||
slider.StartTime = oldStartTime;
|
slider.StartTime = oldStartTime;
|
||||||
@ -218,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
// Maintain the path type in case it got defaulted to bezier at some point during the drag.
|
||||||
PointsInSegment[0].Type.Value = dragPathType;
|
PointsInSegment[0].Type = dragPathType;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
protected override void OnDragEnd(DragEndEvent e) => changeHandler?.EndChange();
|
||||||
@ -230,19 +226,19 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void updatePathType()
|
private void updatePathType()
|
||||||
{
|
{
|
||||||
if (ControlPoint.Type.Value != PathType.PerfectCurve)
|
if (ControlPoint.Type != PathType.PerfectCurve)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (PointsInSegment.Count > 3)
|
if (PointsInSegment.Count > 3)
|
||||||
ControlPoint.Type.Value = PathType.Bezier;
|
ControlPoint.Type = PathType.Bezier;
|
||||||
|
|
||||||
if (PointsInSegment.Count != 3)
|
if (PointsInSegment.Count != 3)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position.Value).ToArray();
|
ReadOnlySpan<Vector2> points = PointsInSegment.Select(p => p.Position).ToArray();
|
||||||
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
RectangleF boundingBox = PathApproximator.CircularArcBoundingBox(points);
|
||||||
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
if (boundingBox.Width >= 640 || boundingBox.Height >= 480)
|
||||||
ControlPoint.Type.Value = PathType.Bezier;
|
ControlPoint.Type = PathType.Bezier;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -250,7 +246,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void updateMarkerDisplay()
|
private void updateMarkerDisplay()
|
||||||
{
|
{
|
||||||
Position = slider.StackedPosition + ControlPoint.Position.Value;
|
Position = slider.StackedPosition + ControlPoint.Position;
|
||||||
|
|
||||||
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
markerRing.Alpha = IsSelected.Value ? 1 : 0;
|
||||||
|
|
||||||
@ -265,7 +261,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
|
|
||||||
private Color4 getColourFromNodeType()
|
private Color4 getColourFromNodeType()
|
||||||
{
|
{
|
||||||
if (!(ControlPoint.Type.Value is PathType pathType))
|
if (!(ControlPoint.Type is PathType pathType))
|
||||||
return colours.Yellow;
|
return colours.Yellow;
|
||||||
|
|
||||||
switch (pathType)
|
switch (pathType)
|
||||||
@ -284,6 +280,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LocalisableString TooltipText => ControlPoint.Type.Value.ToString() ?? string.Empty;
|
public LocalisableString TooltipText => ControlPoint.Type.ToString() ?? string.Empty;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,12 +173,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
int thirdPointIndex = indexInSegment + 2;
|
int thirdPointIndex = indexInSegment + 2;
|
||||||
|
|
||||||
if (piece.PointsInSegment.Count > thirdPointIndex + 1)
|
if (piece.PointsInSegment.Count > thirdPointIndex + 1)
|
||||||
piece.PointsInSegment[thirdPointIndex].Type.Value = piece.PointsInSegment[0].Type.Value;
|
piece.PointsInSegment[thirdPointIndex].Type = piece.PointsInSegment[0].Type;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
piece.ControlPoint.Type.Value = type;
|
piece.ControlPoint.Type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
|
|||||||
private MenuItem createMenuItemForPathType(PathType? type)
|
private MenuItem createMenuItemForPathType(PathType? type)
|
||||||
{
|
{
|
||||||
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
int totalCount = Pieces.Count(p => p.IsSelected.Value);
|
||||||
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type.Value == type);
|
int countOfState = Pieces.Where(p => p.IsSelected.Value).Count(p => p.ControlPoint.Type == type);
|
||||||
|
|
||||||
var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ =>
|
var item = new TernaryStateRadioMenuItem(type == null ? "Inherit" : type.ToString().Humanize(), MenuItemType.Standard, _ =>
|
||||||
{
|
{
|
||||||
|
@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
Debug.Assert(lastPoint != null);
|
Debug.Assert(lastPoint != null);
|
||||||
|
|
||||||
segmentStart = lastPoint;
|
segmentStart = lastPoint;
|
||||||
segmentStart.Type.Value = PathType.Linear;
|
segmentStart.Type = PathType.Linear;
|
||||||
|
|
||||||
currentSegmentLength = 1;
|
currentSegmentLength = 1;
|
||||||
}
|
}
|
||||||
@ -153,15 +153,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
case 1:
|
case 1:
|
||||||
case 2:
|
case 2:
|
||||||
segmentStart.Type.Value = PathType.Linear;
|
segmentStart.Type = PathType.Linear;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 3:
|
case 3:
|
||||||
segmentStart.Type.Value = PathType.PerfectCurve;
|
segmentStart.Type = PathType.PerfectCurve;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
segmentStart.Type.Value = PathType.Bezier;
|
segmentStart.Type = PathType.Bezier;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,7 +173,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
// The cursor does not overlap a previous control point, so it can be added if not already existing.
|
// The cursor does not overlap a previous control point, so it can be added if not already existing.
|
||||||
if (cursor == null)
|
if (cursor == null)
|
||||||
{
|
{
|
||||||
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = { Value = Vector2.Zero } });
|
HitObject.Path.ControlPoints.Add(cursor = new PathControlPoint { Position = Vector2.Zero });
|
||||||
|
|
||||||
// The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier).
|
// The path type should be adjusted in the progression of updatePathType() (Linear -> PC -> Bezier).
|
||||||
currentSegmentLength++;
|
currentSegmentLength++;
|
||||||
@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the cursor position.
|
// Update the cursor position.
|
||||||
cursor.Position.Value = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
cursor.Position = ToLocalSpace(inputManager.CurrentState.Mouse.Position) - HitObject.Position;
|
||||||
}
|
}
|
||||||
else if (cursor != null)
|
else if (cursor != null)
|
||||||
{
|
{
|
||||||
|
@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
Debug.Assert(placementControlPointIndex != null);
|
Debug.Assert(placementControlPointIndex != null);
|
||||||
|
|
||||||
HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position.Value = e.MousePosition - HitObject.Position;
|
HitObject.Path.ControlPoints[placementControlPointIndex.Value].Position = e.MousePosition - HitObject.Position;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDragEnd(DragEndEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
@ -182,7 +182,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
for (int i = 0; i < controlPoints.Count - 1; i++)
|
for (int i = 0; i < controlPoints.Count - 1; i++)
|
||||||
{
|
{
|
||||||
float dist = new Line(controlPoints[i].Position.Value, controlPoints[i + 1].Position.Value).DistanceToPoint(position);
|
float dist = new Line(controlPoints[i].Position, controlPoints[i + 1].Position).DistanceToPoint(position);
|
||||||
|
|
||||||
if (dist < minDistance)
|
if (dist < minDistance)
|
||||||
{
|
{
|
||||||
@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move the control points from the insertion index onwards to make room for the insertion
|
// Move the control points from the insertion index onwards to make room for the insertion
|
||||||
controlPoints.Insert(insertionIndex, new PathControlPoint { Position = { Value = position } });
|
controlPoints.Insert(insertionIndex, new PathControlPoint { Position = position });
|
||||||
|
|
||||||
return insertionIndex;
|
return insertionIndex;
|
||||||
}
|
}
|
||||||
@ -207,8 +207,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
{
|
{
|
||||||
// The first control point in the slider must have a type, so take it from the previous "first" one
|
// The first control point in the slider must have a type, so take it from the previous "first" one
|
||||||
// Todo: Should be handled within SliderPath itself
|
// Todo: Should be handled within SliderPath itself
|
||||||
if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type.Value == null)
|
if (c == controlPoints[0] && controlPoints.Count > 1 && controlPoints[1].Type == null)
|
||||||
controlPoints[1].Type.Value = controlPoints[0].Type.Value;
|
controlPoints[1].Type = controlPoints[0].Type;
|
||||||
|
|
||||||
controlPoints.Remove(c);
|
controlPoints.Remove(c);
|
||||||
}
|
}
|
||||||
@ -222,9 +222,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
|
|||||||
|
|
||||||
// The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position
|
// The path will have a non-zero offset if the head is removed, but sliders don't support this behaviour since the head is positioned at the slider's position
|
||||||
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
|
// So the slider needs to be offset by this amount instead, and all control points offset backwards such that the path is re-positioned at (0, 0)
|
||||||
Vector2 first = controlPoints[0].Position.Value;
|
Vector2 first = controlPoints[0].Position;
|
||||||
foreach (var c in controlPoints)
|
foreach (var c in controlPoints)
|
||||||
c.Position.Value -= first;
|
c.Position -= first;
|
||||||
HitObject.Position += first;
|
HitObject.Position += first;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,11 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
protected override GameplayCursorContainer CreateCursor() => null;
|
protected override GameplayCursorContainer CreateCursor() => null;
|
||||||
|
|
||||||
|
public OsuEditorPlayfield()
|
||||||
|
{
|
||||||
|
HitPolicy = new AnyOrderHitPolicy();
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuConfigManager config)
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
@ -58,6 +63,8 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (hitObject is DrawableHitCircle circle)
|
if (hitObject is DrawableHitCircle circle)
|
||||||
|
{
|
||||||
|
using (circle.BeginAbsoluteSequence(circle.HitStateUpdateTime))
|
||||||
{
|
{
|
||||||
circle.ApproachCircle
|
circle.ApproachCircle
|
||||||
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
|
.FadeOutFromOne(EDITOR_HIT_OBJECT_FADE_OUT_EXTENSION * 4)
|
||||||
@ -65,6 +72,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
|
circle.ApproachCircle.ScaleTo(1.1f, 300, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hitObject is IHasMainCirclePiece mainPieceContainer)
|
if (hitObject is IHasMainCirclePiece mainPieceContainer)
|
||||||
{
|
{
|
||||||
|
@ -98,9 +98,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
foreach (var point in slider.Path.ControlPoints)
|
||||||
{
|
{
|
||||||
point.Position.Value = new Vector2(
|
point.Position = new Vector2(
|
||||||
(direction == Direction.Horizontal ? -1 : 1) * point.Position.Value.X,
|
(direction == Direction.Horizontal ? -1 : 1) * point.Position.X,
|
||||||
(direction == Direction.Vertical ? -1 : 1) * point.Position.Value.Y
|
(direction == Direction.Vertical ? -1 : 1) * point.Position.Y
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
if (h is IHasPath path)
|
if (h is IHasPath path)
|
||||||
{
|
{
|
||||||
foreach (var point in path.Path.ControlPoints)
|
foreach (var point in path.Path.ControlPoints)
|
||||||
point.Position.Value = RotatePointAroundOrigin(point.Position.Value, Vector2.Zero, delta);
|
point.Position = RotatePointAroundOrigin(point.Position, Vector2.Zero, delta);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,9 +163,9 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
private void scaleSlider(Slider slider, Vector2 scale)
|
private void scaleSlider(Slider slider, Vector2 scale)
|
||||||
{
|
{
|
||||||
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type.Value).ToList();
|
referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type).ToList();
|
||||||
|
|
||||||
Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position.Value));
|
Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position));
|
||||||
|
|
||||||
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
// Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0.
|
||||||
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size;
|
||||||
@ -178,13 +178,13 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
foreach (var point in slider.Path.ControlPoints)
|
||||||
{
|
{
|
||||||
oldControlPoints.Enqueue(point.Position.Value);
|
oldControlPoints.Enqueue(point.Position);
|
||||||
point.Position.Value *= pathRelativeDeltaScale;
|
point.Position *= pathRelativeDeltaScale;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
// Maintain the path types in case they were defaulted to bezier at some point during scaling
|
||||||
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
|
for (int i = 0; i < slider.Path.ControlPoints.Count; ++i)
|
||||||
slider.Path.ControlPoints[i].Type.Value = referencePathTypes[i];
|
slider.Path.ControlPoints[i].Type = referencePathTypes[i];
|
||||||
|
|
||||||
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
//if sliderhead or sliderend end up outside playfield, revert scaling.
|
||||||
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider });
|
||||||
@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var point in slider.Path.ControlPoints)
|
foreach (var point in slider.Path.ControlPoints)
|
||||||
point.Position.Value = oldControlPoints.Dequeue();
|
point.Position = oldControlPoints.Dequeue();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale)
|
||||||
|
52
osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs
Normal file
52
osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||||
|
{
|
||||||
|
public class OsuSetupSection : RulesetSetupSection
|
||||||
|
{
|
||||||
|
private LabelledSliderBar<float> stackLeniency;
|
||||||
|
|
||||||
|
public OsuSetupSection()
|
||||||
|
: base(new OsuRuleset().RulesetInfo)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
stackLeniency = new LabelledSliderBar<float>
|
||||||
|
{
|
||||||
|
Label = "Stack Leniency",
|
||||||
|
Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
|
||||||
|
Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency)
|
||||||
|
{
|
||||||
|
Default = 0.7f,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 1,
|
||||||
|
Precision = 0.1f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
stackLeniency.Current.BindValueChanged(_ => updateBeatmap());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateBeatmap()
|
||||||
|
{
|
||||||
|
Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||||
{
|
{
|
||||||
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield.HitObjectContainer, drawableRuleset.Beatmap));
|
drawableRuleset.Overlays.Add(blinds = new DrawableOsuBlinds(drawableRuleset.Playfield, drawableRuleset.Beatmap));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
public void ApplyToHealthProcessor(HealthProcessor healthProcessor)
|
||||||
@ -128,8 +129,21 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
float start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
float start, end;
|
||||||
float end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
|
||||||
|
if (Precision.AlmostEquals(restrictTo.Rotation, 0))
|
||||||
|
{
|
||||||
|
start = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopLeft).X;
|
||||||
|
end = Parent.ToLocalSpace(restrictTo.ScreenSpaceDrawQuad.TopRight).X;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float center = restrictTo.ToSpaceOfOtherDrawable(restrictTo.OriginPosition, Parent).X;
|
||||||
|
float halfDiagonal = (restrictTo.DrawSize / 2).LengthFast;
|
||||||
|
|
||||||
|
start = center - halfDiagonal;
|
||||||
|
end = center + halfDiagonal;
|
||||||
|
}
|
||||||
|
|
||||||
float rawWidth = end - start;
|
float rawWidth = end - start;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -20,8 +21,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
{
|
{
|
||||||
Start = start;
|
Start = start;
|
||||||
LifetimeStart = Start.StartTime;
|
LifetimeStart = Start.StartTime;
|
||||||
|
|
||||||
bindEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private OsuHitObject? end;
|
private OsuHitObject? end;
|
||||||
@ -41,31 +40,39 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool wasBound;
|
||||||
|
|
||||||
private void bindEvents()
|
private void bindEvents()
|
||||||
{
|
{
|
||||||
UnbindEvents();
|
UnbindEvents();
|
||||||
|
|
||||||
|
if (End == null)
|
||||||
|
return;
|
||||||
|
|
||||||
// Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects.
|
// Note: Positions are bound for instantaneous feedback from positional changes from the editor, before ApplyDefaults() is called on hitobjects.
|
||||||
Start.DefaultsApplied += onDefaultsApplied;
|
Start.DefaultsApplied += onDefaultsApplied;
|
||||||
Start.PositionBindable.ValueChanged += onPositionChanged;
|
Start.PositionBindable.ValueChanged += onPositionChanged;
|
||||||
|
|
||||||
if (End != null)
|
|
||||||
{
|
|
||||||
End.DefaultsApplied += onDefaultsApplied;
|
End.DefaultsApplied += onDefaultsApplied;
|
||||||
End.PositionBindable.ValueChanged += onPositionChanged;
|
End.PositionBindable.ValueChanged += onPositionChanged;
|
||||||
}
|
|
||||||
|
wasBound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnbindEvents()
|
public void UnbindEvents()
|
||||||
{
|
{
|
||||||
|
if (!wasBound)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Debug.Assert(End != null);
|
||||||
|
|
||||||
Start.DefaultsApplied -= onDefaultsApplied;
|
Start.DefaultsApplied -= onDefaultsApplied;
|
||||||
Start.PositionBindable.ValueChanged -= onPositionChanged;
|
Start.PositionBindable.ValueChanged -= onPositionChanged;
|
||||||
|
|
||||||
if (End != null)
|
|
||||||
{
|
|
||||||
End.DefaultsApplied -= onDefaultsApplied;
|
End.DefaultsApplied -= onDefaultsApplied;
|
||||||
End.PositionBindable.ValueChanged -= onPositionChanged;
|
End.PositionBindable.ValueChanged -= onPositionChanged;
|
||||||
}
|
|
||||||
|
wasBound = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDefaultsApplied(HitObject obj) => refreshLifetimes();
|
private void onDefaultsApplied(HitObject obj) => refreshLifetimes();
|
||||||
|
@ -74,10 +74,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
public override void PlayAnimation()
|
public override void PlayAnimation()
|
||||||
{
|
{
|
||||||
base.PlayAnimation();
|
|
||||||
|
|
||||||
if (Result != HitResult.Miss)
|
if (Result != HitResult.Miss)
|
||||||
JudgementText.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint);
|
{
|
||||||
|
JudgementText
|
||||||
|
.ScaleTo(new Vector2(0.8f, 1))
|
||||||
|
.ScaleTo(new Vector2(1.2f, 1), 1800, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.PlayAnimation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
|
|
||||||
if (value != null)
|
if (value != null)
|
||||||
{
|
{
|
||||||
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position.Value, c.Type.Value)));
|
path.ControlPoints.AddRange(value.ControlPoints.Select(c => new PathControlPoint(c.Position, c.Type)));
|
||||||
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
path.ExpectedDistance.Value = value.ExpectedDistance.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,9 +30,11 @@ using osu.Game.Skinning;
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
|
using osu.Game.Rulesets.Osu.Edit.Setup;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Statistics;
|
using osu.Game.Rulesets.Osu.Statistics;
|
||||||
|
using osu.Game.Screens.Edit.Setup;
|
||||||
using osu.Game.Screens.Ranking.Statistics;
|
using osu.Game.Screens.Ranking.Statistics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu
|
namespace osu.Game.Rulesets.Osu
|
||||||
@ -305,5 +307,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override RulesetSetupSection CreateEditorSetupSection() => new OsuSetupSection();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
pathVersion.BindValueChanged(_ => Refresh());
|
pathVersion.BindValueChanged(_ => Refresh());
|
||||||
|
|
||||||
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
accentColour = drawableObject.AccentColour.GetBoundCopy();
|
||||||
accentColour.BindValueChanged(accent => updateAccentColour(skin, accent.NewValue), true);
|
accentColour.BindValueChanged(accent => AccentColour = GetBodyAccentColour(skin, accent.NewValue), true);
|
||||||
|
|
||||||
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
|
config?.BindWith(OsuRulesetSetting.SnakingInSliders, SnakingIn);
|
||||||
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut);
|
config?.BindWith(OsuRulesetSetting.SnakingOutSliders, configSnakingOut);
|
||||||
@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateAccentColour(ISkinSource skin, Color4 defaultAccentColour)
|
protected virtual Color4 GetBodyAccentColour(ISkinSource skin, Color4 hitObjectAccentColour) =>
|
||||||
=> AccentColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? defaultAccentColour;
|
skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? hitObjectAccentColour;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A <see cref="SliderBody"/> which changes its curve depending on the snaking progress.
|
/// A <see cref="SliderBody"/> which changes its curve depending on the snaking progress.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SnakingSliderBody : SliderBody, ISliderProgress
|
public abstract class SnakingSliderBody : SliderBody, ISliderProgress
|
||||||
{
|
{
|
||||||
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
|
public readonly List<Vector2> CurrentCurve = new List<Vector2>();
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Input.Events;
|
|||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
{
|
{
|
||||||
@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
private double lastTrailTime;
|
private double lastTrailTime;
|
||||||
private IBindable<float> cursorSize;
|
private IBindable<float> cursorSize;
|
||||||
|
|
||||||
|
private Vector2? currentPosition;
|
||||||
|
|
||||||
public LegacyCursorTrail(ISkin skin)
|
public LegacyCursorTrail(ISkin skin)
|
||||||
{
|
{
|
||||||
this.skin = skin;
|
this.skin = skin;
|
||||||
@ -54,22 +57,35 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override double FadeDuration => disjointTrail ? 150 : 500;
|
protected override double FadeDuration => disjointTrail ? 150 : 500;
|
||||||
|
protected override float FadeExponent => 1;
|
||||||
|
|
||||||
protected override bool InterpolateMovements => !disjointTrail;
|
protected override bool InterpolateMovements => !disjointTrail;
|
||||||
|
|
||||||
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
|
protected override float IntervalMultiplier => 1 / Math.Max(cursorSize.Value, 1);
|
||||||
|
protected override bool AvoidDrawingNearCursor => !disjointTrail;
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!disjointTrail || !currentPosition.HasValue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
|
||||||
|
{
|
||||||
|
lastTrailTime = Time.Current;
|
||||||
|
AddTrail(currentPosition.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
if (!disjointTrail)
|
if (!disjointTrail)
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
|
|
||||||
if (Time.Current - lastTrailTime >= disjoint_trail_time_separation)
|
currentPosition = e.ScreenSpaceMousePosition;
|
||||||
{
|
|
||||||
lastTrailTime = Time.Current;
|
|
||||||
return base.OnMouseMove(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Intentionally block the base call as we're adding the trails ourselves.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Utils;
|
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Skinning.Default;
|
using osu.Game.Rulesets.Osu.Skinning.Default;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Utils;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||||
@ -14,6 +15,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
{
|
{
|
||||||
protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath();
|
protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath();
|
||||||
|
|
||||||
|
protected override Color4 GetBodyAccentColour(ISkinSource skin, Color4 hitObjectAccentColour)
|
||||||
|
{
|
||||||
|
// legacy skins use a constant value for slider track alpha, regardless of the source colour.
|
||||||
|
return base.GetBodyAccentColour(skin, hitObjectAccentColour).Opacity(0.7f);
|
||||||
|
}
|
||||||
|
|
||||||
private class LegacyDrawableSliderPath : DrawableSliderPath
|
private class LegacyDrawableSliderPath : DrawableSliderPath
|
||||||
{
|
{
|
||||||
private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
|
private const float shadow_portion = 1 - (OsuLegacySkinTransformer.LEGACY_CIRCLE_RADIUS / OsuHitObject.OBJECT_RADIUS);
|
||||||
@ -22,8 +29,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
// Roughly matches osu!stable's slider border portions.
|
// Roughly matches osu!stable's slider border portions.
|
||||||
=> base.CalculatedBorderPortion * 0.77f;
|
=> base.CalculatedBorderPortion * 0.77f;
|
||||||
|
|
||||||
public new Color4 AccentColour => new Color4(base.AccentColour.R, base.AccentColour.G, base.AccentColour.B, base.AccentColour.A * 0.70f);
|
|
||||||
|
|
||||||
protected override Color4 ColourAt(float position)
|
protected override Color4 ColourAt(float position)
|
||||||
{
|
{
|
||||||
float realBorderPortion = shadow_portion + CalculatedBorderPortion;
|
float realBorderPortion = shadow_portion + CalculatedBorderPortion;
|
||||||
@ -40,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
Color4 outerColour = AccentColour.Darken(0.1f);
|
Color4 outerColour = AccentColour.Darken(0.1f);
|
||||||
Color4 innerColour = lighten(AccentColour, 0.5f);
|
Color4 innerColour = lighten(AccentColour, 0.5f);
|
||||||
|
|
||||||
return Interpolation.ValueAt(position / realGradientPortion, outerColour, innerColour, 0, 1);
|
return LegacyUtils.InterpolateNonLinear(position / realGradientPortion, outerColour, innerColour, 0, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
22
osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
Normal file
22
osu.Game.Rulesets.Osu/UI/AnyOrderHitPolicy.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An <see cref="IHitPolicy"/> which allows hitobjects to be hit in any order.
|
||||||
|
/// </summary>
|
||||||
|
public class AnyOrderHitPolicy : IHitPolicy
|
||||||
|
{
|
||||||
|
public IHitObjectContainer HitObjectContainer { get; set; }
|
||||||
|
|
||||||
|
public bool IsHittable(DrawableHitObject hitObject, double time) => true;
|
||||||
|
|
||||||
|
public void HandleHit(DrawableHitObject hitObject)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,11 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
{
|
{
|
||||||
private const int max_sprites = 2048;
|
private const int max_sprites = 2048;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An exponentiating factor to ease the trail fade.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual float FadeExponent => 1.7f;
|
||||||
|
|
||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private int currentIndex;
|
private int currentIndex;
|
||||||
private IShader shader;
|
private IShader shader;
|
||||||
@ -133,6 +138,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
protected virtual bool InterpolateMovements => true;
|
protected virtual bool InterpolateMovements => true;
|
||||||
|
|
||||||
protected virtual float IntervalMultiplier => 1.0f;
|
protected virtual float IntervalMultiplier => 1.0f;
|
||||||
|
protected virtual bool AvoidDrawingNearCursor => false;
|
||||||
|
|
||||||
private Vector2? lastPosition;
|
private Vector2? lastPosition;
|
||||||
private readonly InputResampler resampler = new InputResampler();
|
private readonly InputResampler resampler = new InputResampler();
|
||||||
@ -141,45 +147,47 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||||
{
|
{
|
||||||
Vector2 pos = e.ScreenSpaceMousePosition;
|
AddTrail(e.ScreenSpaceMousePosition);
|
||||||
|
|
||||||
if (lastPosition == null)
|
|
||||||
{
|
|
||||||
lastPosition = pos;
|
|
||||||
resampler.AddPosition(lastPosition.Value);
|
|
||||||
return base.OnMouseMove(e);
|
return base.OnMouseMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Vector2 pos2 in resampler.AddPosition(pos))
|
protected void AddTrail(Vector2 position)
|
||||||
|
{
|
||||||
|
if (InterpolateMovements)
|
||||||
|
{
|
||||||
|
if (!lastPosition.HasValue)
|
||||||
|
{
|
||||||
|
lastPosition = position;
|
||||||
|
resampler.AddPosition(lastPosition.Value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Vector2 pos2 in resampler.AddPosition(position))
|
||||||
{
|
{
|
||||||
Trace.Assert(lastPosition.HasValue);
|
Trace.Assert(lastPosition.HasValue);
|
||||||
|
|
||||||
if (InterpolateMovements)
|
|
||||||
{
|
|
||||||
// ReSharper disable once PossibleInvalidOperationException
|
|
||||||
Vector2 pos1 = lastPosition.Value;
|
Vector2 pos1 = lastPosition.Value;
|
||||||
Vector2 diff = pos2 - pos1;
|
Vector2 diff = pos2 - pos1;
|
||||||
float distance = diff.Length;
|
float distance = diff.Length;
|
||||||
Vector2 direction = diff / distance;
|
Vector2 direction = diff / distance;
|
||||||
|
|
||||||
float interval = partSize.X / 2.5f * IntervalMultiplier;
|
float interval = partSize.X / 2.5f * IntervalMultiplier;
|
||||||
|
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
|
||||||
|
|
||||||
for (float d = interval; d < distance; d += interval)
|
for (float d = interval; d < stopAt; d += interval)
|
||||||
{
|
{
|
||||||
lastPosition = pos1 + direction * d;
|
lastPosition = pos1 + direction * d;
|
||||||
addPart(lastPosition.Value);
|
addPart(lastPosition.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
lastPosition = pos2;
|
lastPosition = position;
|
||||||
addPart(lastPosition.Value);
|
addPart(lastPosition.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnMouseMove(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addPart(Vector2 screenSpacePosition)
|
private void addPart(Vector2 screenSpacePosition)
|
||||||
{
|
{
|
||||||
parts[currentIndex].Position = screenSpacePosition;
|
parts[currentIndex].Position = screenSpacePosition;
|
||||||
@ -206,10 +214,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
private Texture texture;
|
private Texture texture;
|
||||||
|
|
||||||
private float time;
|
private float time;
|
||||||
|
private float fadeExponent;
|
||||||
|
|
||||||
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
private readonly TrailPart[] parts = new TrailPart[max_sprites];
|
||||||
private Vector2 size;
|
private Vector2 size;
|
||||||
|
|
||||||
private Vector2 originPosition;
|
private Vector2 originPosition;
|
||||||
|
|
||||||
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
private readonly QuadBatch<TexturedTrailVertex> vertexBatch = new QuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||||
@ -227,6 +235,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
texture = Source.texture;
|
texture = Source.texture;
|
||||||
size = Source.partSize;
|
size = Source.partSize;
|
||||||
time = Source.time;
|
time = Source.time;
|
||||||
|
fadeExponent = Source.FadeExponent;
|
||||||
|
|
||||||
originPosition = Vector2.Zero;
|
originPosition = Vector2.Zero;
|
||||||
|
|
||||||
@ -249,6 +258,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
shader.Bind();
|
shader.Bind();
|
||||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||||
|
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
||||||
|
|
||||||
texture.TextureGL.Bind();
|
texture.TextureGL.Bind();
|
||||||
|
|
||||||
|
@ -119,9 +119,9 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||||
|
|
||||||
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
|
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
|
||||||
foreach (var point in controlPoints)
|
foreach (var point in controlPoints)
|
||||||
point.Position.Value = new Vector2(-point.Position.Value.X, point.Position.Value.Y);
|
point.Position = new Vector2(-point.Position.X, point.Position.Y);
|
||||||
|
|
||||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||||
}
|
}
|
||||||
@ -140,9 +140,9 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
|
|
||||||
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position.Value, p.Type.Value)).ToArray();
|
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
|
||||||
foreach (var point in controlPoints)
|
foreach (var point in controlPoints)
|
||||||
point.Position.Value = new Vector2(point.Position.Value.X, -point.Position.Value.Y);
|
point.Position = new Vector2(point.Position.X, -point.Position.Y);
|
||||||
|
|
||||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new TaikoPlayfield(new ControlPointInfo()),
|
new TaikoPlayfield(),
|
||||||
hoc = new ScrollingHitObjectContainer()
|
hoc = new ScrollingHitObjectContainer()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new TaikoPlayfield(new ControlPointInfo()),
|
new TaikoPlayfield(),
|
||||||
hoc = new ScrollingHitObjectContainer()
|
hoc = new ScrollingHitObjectContainer()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,6 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Rulesets.Taiko.UI;
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -17,6 +16,13 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
var playfield = new TaikoPlayfield();
|
||||||
|
|
||||||
|
var beatmap = CreateWorkingBeatmap(new TaikoRuleset().RulesetInfo).GetPlayableBeatmap(new TaikoRuleset().RulesetInfo);
|
||||||
|
|
||||||
|
foreach (var h in beatmap.HitObjects)
|
||||||
|
playfield.Add(h);
|
||||||
|
|
||||||
SetContents(_ => new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
SetContents(_ => new TaikoInputManager(new TaikoRuleset().RulesetInfo)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -25,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(200),
|
Size = new Vector2(200),
|
||||||
Child = new InputDrum(new ControlPointInfo())
|
Child = new InputDrum(playfield.HitObjectContainer)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
Beatmap.Value.Track.Start();
|
Beatmap.Value.Track.Start();
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield(new ControlPointInfo())
|
AddStep("Load playfield", () => SetContents(_ => new TaikoPlayfield
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<Import Project="..\osu.TestProject.props" />
|
<Import Project="..\osu.TestProject.props" />
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
<PackageReference Include="Appveyor.TestLogger" Version="2.0.0" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.13.2" />
|
<PackageReference Include="NUnit" Version="3.13.2" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.0.0" />
|
||||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||||
|
@ -1,104 +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.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Audio;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Skinning;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Audio
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Stores samples for the input drum.
|
|
||||||
/// The lifetime of the samples is adjusted so that they are only alive during the appropriate sample control point.
|
|
||||||
/// </summary>
|
|
||||||
public class DrumSampleContainer : LifetimeManagementContainer
|
|
||||||
{
|
|
||||||
private readonly ControlPointInfo controlPoints;
|
|
||||||
private readonly Dictionary<double, DrumSample> mappings = new Dictionary<double, DrumSample>();
|
|
||||||
|
|
||||||
private readonly IBindableList<SampleControlPoint> samplePoints = new BindableList<SampleControlPoint>();
|
|
||||||
|
|
||||||
public DrumSampleContainer(ControlPointInfo controlPoints)
|
|
||||||
{
|
|
||||||
this.controlPoints = controlPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
samplePoints.BindTo(controlPoints.SamplePoints);
|
|
||||||
samplePoints.BindCollectionChanged((_, __) => recreateMappings(), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recreateMappings()
|
|
||||||
{
|
|
||||||
mappings.Clear();
|
|
||||||
ClearInternal();
|
|
||||||
|
|
||||||
SampleControlPoint[] points = samplePoints.Count == 0
|
|
||||||
? new[] { controlPoints.SamplePointAt(double.MinValue) }
|
|
||||||
: samplePoints.ToArray();
|
|
||||||
|
|
||||||
for (int i = 0; i < points.Length; i++)
|
|
||||||
{
|
|
||||||
var samplePoint = points[i];
|
|
||||||
|
|
||||||
var lifetimeStart = i > 0 ? samplePoint.Time : double.MinValue;
|
|
||||||
var lifetimeEnd = i + 1 < points.Length ? points[i + 1].Time : double.MaxValue;
|
|
||||||
|
|
||||||
AddInternal(mappings[samplePoint.Time] = new DrumSample(samplePoint)
|
|
||||||
{
|
|
||||||
LifetimeStart = lifetimeStart,
|
|
||||||
LifetimeEnd = lifetimeEnd
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DrumSample SampleAt(double time) => mappings[controlPoints.SamplePointAt(time).Time];
|
|
||||||
|
|
||||||
public class DrumSample : CompositeDrawable
|
|
||||||
{
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
|
||||||
|
|
||||||
public PausableSkinnableSound Centre { get; private set; }
|
|
||||||
public PausableSkinnableSound Rim { get; private set; }
|
|
||||||
|
|
||||||
private readonly SampleControlPoint samplePoint;
|
|
||||||
|
|
||||||
private Bindable<string> sampleBank;
|
|
||||||
private BindableNumber<int> sampleVolume;
|
|
||||||
|
|
||||||
public DrumSample(SampleControlPoint samplePoint)
|
|
||||||
{
|
|
||||||
this.samplePoint = samplePoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
sampleBank = samplePoint.SampleBankBindable.GetBoundCopy();
|
|
||||||
sampleBank.BindValueChanged(_ => recreate());
|
|
||||||
|
|
||||||
sampleVolume = samplePoint.SampleVolumeBindable.GetBoundCopy();
|
|
||||||
sampleVolume.BindValueChanged(_ => recreate());
|
|
||||||
|
|
||||||
recreate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recreate()
|
|
||||||
{
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
Centre = new PausableSkinnableSound(samplePoint.GetSampleInfo()),
|
|
||||||
Rim = new PausableSkinnableSound(samplePoint.GetSampleInfo(HitSampleInfo.HIT_CLAP))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,12 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
{
|
{
|
||||||
internal class TaikoBeatmapConverter : BeatmapConverter<TaikoHitObject>
|
internal class TaikoBeatmapConverter : BeatmapConverter<TaikoHitObject>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// osu! is generally slower than taiko, so a factor is added to increase
|
|
||||||
/// speed. This must be used everywhere slider length or beat length is used.
|
|
||||||
/// </summary>
|
|
||||||
public const float LEGACY_VELOCITY_MULTIPLIER = 1.4f;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Because swells are easier in taiko than spinners are in osu!,
|
/// Because swells are easier in taiko than spinners are in osu!,
|
||||||
/// legacy taiko multiplies a factor when converting the number of required hits.
|
/// legacy taiko multiplies a factor when converting the number of required hits.
|
||||||
@ -51,11 +45,13 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
public override bool CanConvert() => true;
|
public override bool CanConvert() => true;
|
||||||
|
|
||||||
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
protected override Beatmap<TaikoHitObject> ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (!(original.BeatmapInfo.BaseDifficulty is TaikoMutliplierAppliedDifficulty))
|
||||||
{
|
{
|
||||||
// Rewrite the beatmap info to add the slider velocity multiplier
|
// Rewrite the beatmap info to add the slider velocity multiplier
|
||||||
original.BeatmapInfo = original.BeatmapInfo.Clone();
|
original.BeatmapInfo = original.BeatmapInfo.Clone();
|
||||||
original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
|
original.BeatmapInfo.BaseDifficulty = new TaikoMutliplierAppliedDifficulty(original.BeatmapInfo.BaseDifficulty);
|
||||||
original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER;
|
}
|
||||||
|
|
||||||
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
|
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, cancellationToken);
|
||||||
|
|
||||||
@ -155,7 +151,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
|
|
||||||
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
|
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
|
||||||
int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
|
int spans = (obj as IHasRepeats)?.SpanCount() ?? 1;
|
||||||
double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER;
|
double distance = distanceData.Distance * spans * LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||||
|
|
||||||
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime);
|
||||||
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
|
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
|
||||||
@ -194,5 +190,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Beatmap<TaikoHitObject> CreateBeatmap() => new TaikoBeatmap();
|
protected override Beatmap<TaikoHitObject> CreateBeatmap() => new TaikoBeatmap();
|
||||||
|
|
||||||
|
private class TaikoMutliplierAppliedDifficulty : BeatmapDifficulty
|
||||||
|
{
|
||||||
|
public TaikoMutliplierAppliedDifficulty(BeatmapDifficulty difficulty)
|
||||||
|
{
|
||||||
|
difficulty.CopyTo(this);
|
||||||
|
SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the colour coefficient of taiko difficulty.
|
/// Calculates the colour coefficient of taiko difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Colour : StrainSkill
|
public class Colour : StrainDecaySkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 0.4;
|
protected override double StrainDecayBase => 0.4;
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the rhythm coefficient of taiko difficulty.
|
/// Calculates the rhythm coefficient of taiko difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Rhythm : StrainSkill
|
public class Rhythm : StrainDecaySkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 10;
|
protected override double SkillMultiplier => 10;
|
||||||
protected override double StrainDecayBase => 0;
|
protected override double StrainDecayBase => 0;
|
||||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
|
/// The reference play style chosen uses two hands, with full alternating (the hand changes after every hit).
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class Stamina : StrainSkill
|
public class Stamina : StrainDecaySkill
|
||||||
{
|
{
|
||||||
protected override double SkillMultiplier => 1;
|
protected override double SkillMultiplier => 1;
|
||||||
protected override double StrainDecayBase => 0.4;
|
protected override double StrainDecayBase => 0.4;
|
||||||
|
@ -6,10 +6,10 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Formats;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
|
||||||
using osu.Game.Rulesets.Taiko.Judgements;
|
using osu.Game.Rulesets.Taiko.Judgements;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
|||||||
double IHasDistance.Distance => Duration * Velocity;
|
double IHasDistance.Distance => Duration * Velocity;
|
||||||
|
|
||||||
SliderPath IHasPath.Path
|
SliderPath IHasPath.Path
|
||||||
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER);
|
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER);
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Rulesets.Taiko.Audio;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.Taiko.UI;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -111,7 +112,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
public readonly Sprite Centre;
|
public readonly Sprite Centre;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrumSampleContainer sampleContainer { get; set; }
|
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
|
||||||
|
|
||||||
public LegacyHalfDrum(bool flipped)
|
public LegacyHalfDrum(bool flipped)
|
||||||
{
|
{
|
||||||
@ -143,17 +144,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
public bool OnPressed(TaikoAction action)
|
public bool OnPressed(TaikoAction action)
|
||||||
{
|
{
|
||||||
Drawable target = null;
|
Drawable target = null;
|
||||||
var drumSample = sampleContainer.SampleAt(Time.Current);
|
|
||||||
|
|
||||||
if (action == CentreAction)
|
if (action == CentreAction)
|
||||||
{
|
{
|
||||||
target = Centre;
|
target = Centre;
|
||||||
drumSample.Centre?.Play();
|
sampleTriggerSource.Play(HitType.Centre);
|
||||||
}
|
}
|
||||||
else if (action == RimAction)
|
else if (action == RimAction)
|
||||||
{
|
{
|
||||||
target = Rim;
|
target = Rim;
|
||||||
drumSample.Rim?.Play();
|
sampleTriggerSource.Play(HitType.Rim);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.UI
|
namespace osu.Game.Rulesets.Taiko.UI
|
||||||
{
|
{
|
||||||
@ -16,5 +17,26 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
this.MoveToY(-100, 500);
|
this.MoveToY(-100, 500);
|
||||||
base.ApplyHitAnimations();
|
base.ApplyHitAnimations();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateDefaultJudgement(HitResult result) => new TaikoJudgementPiece(result);
|
||||||
|
|
||||||
|
private class TaikoJudgementPiece : DefaultJudgementPiece
|
||||||
|
{
|
||||||
|
public TaikoJudgementPiece(HitResult result)
|
||||||
|
: base(result)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void PlayAnimation()
|
||||||
|
{
|
||||||
|
if (Result != HitResult.Miss)
|
||||||
|
{
|
||||||
|
JudgementText.ScaleTo(0.9f);
|
||||||
|
JudgementText.ScaleTo(1, 500, Easing.OutElastic);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.PlayAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
|
||||||
|
|
||||||
protected override Playfield CreatePlayfield() => new TaikoPlayfield(Beatmap.ControlPointInfo);
|
protected override Playfield CreatePlayfield() => new TaikoPlayfield();
|
||||||
|
|
||||||
public override DrawableHitObject<TaikoHitObject> CreateDrawableRepresentation(TaikoHitObject h) => null;
|
public override DrawableHitObject<TaikoHitObject> CreateDrawableRepresentation(TaikoHitObject h) => null;
|
||||||
|
|
||||||
|
30
osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs
Normal file
30
osu.Game.Rulesets.Taiko/UI/DrumSampleTriggerSource.cs
Normal file
@ -0,0 +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.Audio;
|
||||||
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Taiko.UI
|
||||||
|
{
|
||||||
|
public class DrumSampleTriggerSource : GameplaySampleTriggerSource
|
||||||
|
{
|
||||||
|
public DrumSampleTriggerSource(HitObjectContainer hitObjectContainer)
|
||||||
|
: base(hitObjectContainer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Play(HitType hitType)
|
||||||
|
{
|
||||||
|
var hitObject = GetMostValidObject();
|
||||||
|
|
||||||
|
if (hitObject == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
PlaySamples(new ISampleInfo[] { hitObject.SampleControlPoint.GetSampleInfo(hitType == HitType.Rim ? HitSampleInfo.HIT_CLAP : HitSampleInfo.HIT_NORMAL) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Play() => throw new InvalidOperationException(@"Use override with HitType parameter instead");
|
||||||
|
}
|
||||||
|
}
|
@ -2,18 +2,18 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osuTK;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Taiko.Audio;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.UI
|
namespace osu.Game.Rulesets.Taiko.UI
|
||||||
{
|
{
|
||||||
@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
private const float middle_split = 0.025f;
|
private const float middle_split = 0.025f;
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private DrumSampleContainer sampleContainer;
|
private DrumSampleTriggerSource sampleTriggerSource;
|
||||||
|
|
||||||
public InputDrum(ControlPointInfo controlPoints)
|
public InputDrum(HitObjectContainer hitObjectContainer)
|
||||||
{
|
{
|
||||||
sampleContainer = new DrumSampleContainer(controlPoints);
|
sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer);
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
sampleContainer
|
sampleTriggerSource
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
private readonly Sprite centreHit;
|
private readonly Sprite centreHit;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private DrumSampleContainer sampleContainer { get; set; }
|
private DrumSampleTriggerSource sampleTriggerSource { get; set; }
|
||||||
|
|
||||||
public TaikoHalfDrum(bool flipped)
|
public TaikoHalfDrum(bool flipped)
|
||||||
{
|
{
|
||||||
@ -156,21 +156,19 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
Drawable target = null;
|
Drawable target = null;
|
||||||
Drawable back = null;
|
Drawable back = null;
|
||||||
|
|
||||||
var drumSample = sampleContainer.SampleAt(Time.Current);
|
|
||||||
|
|
||||||
if (action == CentreAction)
|
if (action == CentreAction)
|
||||||
{
|
{
|
||||||
target = centreHit;
|
target = centreHit;
|
||||||
back = centre;
|
back = centre;
|
||||||
|
|
||||||
drumSample.Centre?.Play();
|
sampleTriggerSource.Play(HitType.Centre);
|
||||||
}
|
}
|
||||||
else if (action == RimAction)
|
else if (action == RimAction)
|
||||||
{
|
{
|
||||||
target = rimHit;
|
target = rimHit;
|
||||||
back = rim;
|
back = rim;
|
||||||
|
|
||||||
drumSample.Rim?.Play();
|
sampleTriggerSource.Play(HitType.Rim);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (target != null)
|
if (target != null)
|
||||||
|
@ -8,7 +8,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -27,8 +26,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
{
|
{
|
||||||
public class TaikoPlayfield : ScrollingPlayfield
|
public class TaikoPlayfield : ScrollingPlayfield
|
||||||
{
|
{
|
||||||
private readonly ControlPointInfo controlPoints;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
|
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -56,11 +53,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
private Container hitTargetOffsetContent;
|
private Container hitTargetOffsetContent;
|
||||||
|
|
||||||
public TaikoPlayfield(ControlPointInfo controlPoints)
|
|
||||||
{
|
|
||||||
this.controlPoints = controlPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -131,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()),
|
new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()),
|
||||||
new InputDrum(controlPoints)
|
new InputDrum(HitObjectContainer)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
|
@ -1,56 +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 NUnit.Framework;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
using osu.Game.Rulesets;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Beatmaps
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class BeatmapDifficultyCacheTest
|
|
||||||
{
|
|
||||||
[Test]
|
|
||||||
public void TestKeyEqualsWithDifferentModInstances()
|
|
||||||
{
|
|
||||||
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
|
||||||
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
|
||||||
|
|
||||||
Assert.That(key1, Is.EqualTo(key2));
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestKeyEqualsWithDifferentModOrder()
|
|
||||||
{
|
|
||||||
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
|
||||||
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
|
|
||||||
|
|
||||||
Assert.That(key1, Is.EqualTo(key2));
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestCase(1.3, DifficultyRating.Easy)]
|
|
||||||
[TestCase(1.993, DifficultyRating.Easy)]
|
|
||||||
[TestCase(1.998, DifficultyRating.Normal)]
|
|
||||||
[TestCase(2.4, DifficultyRating.Normal)]
|
|
||||||
[TestCase(2.693, DifficultyRating.Normal)]
|
|
||||||
[TestCase(2.698, DifficultyRating.Hard)]
|
|
||||||
[TestCase(3.5, DifficultyRating.Hard)]
|
|
||||||
[TestCase(3.993, DifficultyRating.Hard)]
|
|
||||||
[TestCase(3.997, DifficultyRating.Insane)]
|
|
||||||
[TestCase(5.0, DifficultyRating.Insane)]
|
|
||||||
[TestCase(5.292, DifficultyRating.Insane)]
|
|
||||||
[TestCase(5.297, DifficultyRating.Expert)]
|
|
||||||
[TestCase(6.2, DifficultyRating.Expert)]
|
|
||||||
[TestCase(6.493, DifficultyRating.Expert)]
|
|
||||||
[TestCase(6.498, DifficultyRating.ExpertPlus)]
|
|
||||||
[TestCase(8.3, DifficultyRating.ExpertPlus)]
|
|
||||||
public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
|
|
||||||
{
|
|
||||||
var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
|
|
||||||
|
|
||||||
Assert.AreEqual(expectedBracket, actualBracket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,15 +3,12 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using NUnit.Framework;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Game.Tests.Resources;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Beatmaps.Timing;
|
using osu.Game.Beatmaps.Timing;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Rulesets.Catch;
|
using osu.Game.Rulesets.Catch;
|
||||||
@ -19,9 +16,13 @@ using osu.Game.Rulesets.Catch.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Legacy;
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Tests.Resources;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Beatmaps.Formats
|
namespace osu.Game.Tests.Beatmaps.Formats
|
||||||
{
|
{
|
||||||
@ -58,12 +59,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
|
Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile);
|
||||||
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
||||||
Assert.AreEqual(164471, metadata.PreviewTime);
|
Assert.AreEqual(164471, metadata.PreviewTime);
|
||||||
Assert.IsFalse(beatmapInfo.Countdown);
|
|
||||||
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
||||||
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
||||||
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
|
||||||
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
Assert.IsFalse(beatmapInfo.SpecialStyle);
|
||||||
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
|
||||||
|
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||||
|
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +167,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
{
|
{
|
||||||
var beatmap = decoder.Decode(stream);
|
var beatmap = decoder.Decode(stream);
|
||||||
var controlPoints = beatmap.ControlPointInfo;
|
var controlPoints = (LegacyControlPointInfo)beatmap.ControlPointInfo;
|
||||||
|
|
||||||
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
|
Assert.AreEqual(4, controlPoints.TimingPoints.Count);
|
||||||
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
|
Assert.AreEqual(5, controlPoints.DifficultyPoints.Count);
|
||||||
@ -239,7 +241,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
using (var resStream = TestResources.OpenResource("overlapping-control-points.osu"))
|
using (var resStream = TestResources.OpenResource("overlapping-control-points.osu"))
|
||||||
using (var stream = new LineBufferedReader(resStream))
|
using (var stream = new LineBufferedReader(resStream))
|
||||||
{
|
{
|
||||||
var controlPoints = decoder.Decode(stream).ControlPointInfo;
|
var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
|
||||||
|
|
||||||
Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(4));
|
Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(4));
|
||||||
Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3));
|
Assert.That(controlPoints.DifficultyPoints.Count, Is.EqualTo(3));
|
||||||
@ -665,111 +667,111 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
// Multi-segment
|
// Multi-segment
|
||||||
var first = ((IHasPath)decoded.HitObjects[0]).Path;
|
var first = ((IHasPath)decoded.HitObjects[0]).Path;
|
||||||
|
|
||||||
Assert.That(first.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
Assert.That(first.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
Assert.That(first.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve));
|
Assert.That(first.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve));
|
||||||
Assert.That(first.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244)));
|
Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
|
||||||
Assert.That(first.ControlPoints[1].Type.Value, Is.EqualTo(null));
|
Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
Assert.That(first.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3)));
|
Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
|
||||||
Assert.That(first.ControlPoints[2].Type.Value, Is.EqualTo(PathType.Bezier));
|
Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier));
|
||||||
Assert.That(first.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(68, 15)));
|
Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15)));
|
||||||
Assert.That(first.ControlPoints[3].Type.Value, Is.EqualTo(null));
|
Assert.That(first.ControlPoints[3].Type, Is.EqualTo(null));
|
||||||
Assert.That(first.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(259, -132)));
|
Assert.That(first.ControlPoints[4].Position, Is.EqualTo(new Vector2(259, -132)));
|
||||||
Assert.That(first.ControlPoints[4].Type.Value, Is.EqualTo(null));
|
Assert.That(first.ControlPoints[4].Type, Is.EqualTo(null));
|
||||||
Assert.That(first.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(92, -107)));
|
Assert.That(first.ControlPoints[5].Position, Is.EqualTo(new Vector2(92, -107)));
|
||||||
Assert.That(first.ControlPoints[5].Type.Value, Is.EqualTo(null));
|
Assert.That(first.ControlPoints[5].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
// Single-segment
|
// Single-segment
|
||||||
var second = ((IHasPath)decoded.HitObjects[1]).Path;
|
var second = ((IHasPath)decoded.HitObjects[1]).Path;
|
||||||
|
|
||||||
Assert.That(second.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
Assert.That(second.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
Assert.That(second.ControlPoints[0].Type.Value, Is.EqualTo(PathType.PerfectCurve));
|
Assert.That(second.ControlPoints[0].Type, Is.EqualTo(PathType.PerfectCurve));
|
||||||
Assert.That(second.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(161, -244)));
|
Assert.That(second.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244)));
|
||||||
Assert.That(second.ControlPoints[1].Type.Value, Is.EqualTo(null));
|
Assert.That(second.ControlPoints[1].Type, Is.EqualTo(null));
|
||||||
Assert.That(second.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(376, -3)));
|
Assert.That(second.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3)));
|
||||||
Assert.That(second.ControlPoints[2].Type.Value, Is.EqualTo(null));
|
Assert.That(second.ControlPoints[2].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
// Implicit multi-segment
|
// Implicit multi-segment
|
||||||
var third = ((IHasPath)decoded.HitObjects[2]).Path;
|
var third = ((IHasPath)decoded.HitObjects[2]).Path;
|
||||||
|
|
||||||
Assert.That(third.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
Assert.That(third.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
Assert.That(third.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier));
|
Assert.That(third.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier));
|
||||||
Assert.That(third.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(0, 192)));
|
Assert.That(third.ControlPoints[1].Position, Is.EqualTo(new Vector2(0, 192)));
|
||||||
Assert.That(third.ControlPoints[1].Type.Value, Is.EqualTo(null));
|
Assert.That(third.ControlPoints[1].Type, Is.EqualTo(null));
|
||||||
Assert.That(third.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(224, 192)));
|
Assert.That(third.ControlPoints[2].Position, Is.EqualTo(new Vector2(224, 192)));
|
||||||
Assert.That(third.ControlPoints[2].Type.Value, Is.EqualTo(null));
|
Assert.That(third.ControlPoints[2].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
Assert.That(third.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(224, 0)));
|
Assert.That(third.ControlPoints[3].Position, Is.EqualTo(new Vector2(224, 0)));
|
||||||
Assert.That(third.ControlPoints[3].Type.Value, Is.EqualTo(PathType.Bezier));
|
Assert.That(third.ControlPoints[3].Type, Is.EqualTo(PathType.Bezier));
|
||||||
Assert.That(third.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(224, -192)));
|
Assert.That(third.ControlPoints[4].Position, Is.EqualTo(new Vector2(224, -192)));
|
||||||
Assert.That(third.ControlPoints[4].Type.Value, Is.EqualTo(null));
|
Assert.That(third.ControlPoints[4].Type, Is.EqualTo(null));
|
||||||
Assert.That(third.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(480, -192)));
|
Assert.That(third.ControlPoints[5].Position, Is.EqualTo(new Vector2(480, -192)));
|
||||||
Assert.That(third.ControlPoints[5].Type.Value, Is.EqualTo(null));
|
Assert.That(third.ControlPoints[5].Type, Is.EqualTo(null));
|
||||||
Assert.That(third.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(480, 0)));
|
Assert.That(third.ControlPoints[6].Position, Is.EqualTo(new Vector2(480, 0)));
|
||||||
Assert.That(third.ControlPoints[6].Type.Value, Is.EqualTo(null));
|
Assert.That(third.ControlPoints[6].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
// Last control point duplicated
|
// Last control point duplicated
|
||||||
var fourth = ((IHasPath)decoded.HitObjects[3]).Path;
|
var fourth = ((IHasPath)decoded.HitObjects[3]).Path;
|
||||||
|
|
||||||
Assert.That(fourth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
Assert.That(fourth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
Assert.That(fourth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier));
|
Assert.That(fourth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier));
|
||||||
Assert.That(fourth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1)));
|
Assert.That(fourth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1)));
|
||||||
Assert.That(fourth.ControlPoints[1].Type.Value, Is.EqualTo(null));
|
Assert.That(fourth.ControlPoints[1].Type, Is.EqualTo(null));
|
||||||
Assert.That(fourth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2)));
|
Assert.That(fourth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2)));
|
||||||
Assert.That(fourth.ControlPoints[2].Type.Value, Is.EqualTo(null));
|
Assert.That(fourth.ControlPoints[2].Type, Is.EqualTo(null));
|
||||||
Assert.That(fourth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
Assert.That(fourth.ControlPoints[3].Position, Is.EqualTo(new Vector2(3, 3)));
|
||||||
Assert.That(fourth.ControlPoints[3].Type.Value, Is.EqualTo(null));
|
Assert.That(fourth.ControlPoints[3].Type, Is.EqualTo(null));
|
||||||
Assert.That(fourth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
Assert.That(fourth.ControlPoints[4].Position, Is.EqualTo(new Vector2(3, 3)));
|
||||||
Assert.That(fourth.ControlPoints[4].Type.Value, Is.EqualTo(null));
|
Assert.That(fourth.ControlPoints[4].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
// Last control point in segment duplicated
|
// Last control point in segment duplicated
|
||||||
var fifth = ((IHasPath)decoded.HitObjects[4]).Path;
|
var fifth = ((IHasPath)decoded.HitObjects[4]).Path;
|
||||||
|
|
||||||
Assert.That(fifth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
Assert.That(fifth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
Assert.That(fifth.ControlPoints[0].Type.Value, Is.EqualTo(PathType.Bezier));
|
Assert.That(fifth.ControlPoints[0].Type, Is.EqualTo(PathType.Bezier));
|
||||||
Assert.That(fifth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(1, 1)));
|
Assert.That(fifth.ControlPoints[1].Position, Is.EqualTo(new Vector2(1, 1)));
|
||||||
Assert.That(fifth.ControlPoints[1].Type.Value, Is.EqualTo(null));
|
Assert.That(fifth.ControlPoints[1].Type, Is.EqualTo(null));
|
||||||
Assert.That(fifth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(2, 2)));
|
Assert.That(fifth.ControlPoints[2].Position, Is.EqualTo(new Vector2(2, 2)));
|
||||||
Assert.That(fifth.ControlPoints[2].Type.Value, Is.EqualTo(null));
|
Assert.That(fifth.ControlPoints[2].Type, Is.EqualTo(null));
|
||||||
Assert.That(fifth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
Assert.That(fifth.ControlPoints[3].Position, Is.EqualTo(new Vector2(3, 3)));
|
||||||
Assert.That(fifth.ControlPoints[3].Type.Value, Is.EqualTo(null));
|
Assert.That(fifth.ControlPoints[3].Type, Is.EqualTo(null));
|
||||||
Assert.That(fifth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(3, 3)));
|
Assert.That(fifth.ControlPoints[4].Position, Is.EqualTo(new Vector2(3, 3)));
|
||||||
Assert.That(fifth.ControlPoints[4].Type.Value, Is.EqualTo(null));
|
Assert.That(fifth.ControlPoints[4].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
Assert.That(fifth.ControlPoints[5].Position.Value, Is.EqualTo(new Vector2(4, 4)));
|
Assert.That(fifth.ControlPoints[5].Position, Is.EqualTo(new Vector2(4, 4)));
|
||||||
Assert.That(fifth.ControlPoints[5].Type.Value, Is.EqualTo(PathType.Bezier));
|
Assert.That(fifth.ControlPoints[5].Type, Is.EqualTo(PathType.Bezier));
|
||||||
Assert.That(fifth.ControlPoints[6].Position.Value, Is.EqualTo(new Vector2(5, 5)));
|
Assert.That(fifth.ControlPoints[6].Position, Is.EqualTo(new Vector2(5, 5)));
|
||||||
Assert.That(fifth.ControlPoints[6].Type.Value, Is.EqualTo(null));
|
Assert.That(fifth.ControlPoints[6].Type, Is.EqualTo(null));
|
||||||
|
|
||||||
// Implicit perfect-curve multi-segment(Should convert to bezier to match stable)
|
// Implicit perfect-curve multi-segment(Should convert to bezier to match stable)
|
||||||
var sixth = ((IHasPath)decoded.HitObjects[5]).Path;
|
var sixth = ((IHasPath)decoded.HitObjects[5]).Path;
|
||||||
|
|
||||||
Assert.That(sixth.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
Assert.That(sixth.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
Assert.That(sixth.ControlPoints[0].Type.Value == PathType.Bezier);
|
Assert.That(sixth.ControlPoints[0].Type == PathType.Bezier);
|
||||||
Assert.That(sixth.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145)));
|
Assert.That(sixth.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145)));
|
||||||
Assert.That(sixth.ControlPoints[1].Type.Value == null);
|
Assert.That(sixth.ControlPoints[1].Type == null);
|
||||||
Assert.That(sixth.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75)));
|
Assert.That(sixth.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75)));
|
||||||
|
|
||||||
Assert.That(sixth.ControlPoints[2].Type.Value == PathType.Bezier);
|
Assert.That(sixth.ControlPoints[2].Type == PathType.Bezier);
|
||||||
Assert.That(sixth.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145)));
|
Assert.That(sixth.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145)));
|
||||||
Assert.That(sixth.ControlPoints[3].Type.Value == null);
|
Assert.That(sixth.ControlPoints[3].Type == null);
|
||||||
Assert.That(sixth.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20)));
|
Assert.That(sixth.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20)));
|
||||||
Assert.That(sixth.ControlPoints[4].Type.Value == null);
|
Assert.That(sixth.ControlPoints[4].Type == null);
|
||||||
|
|
||||||
// Explicit perfect-curve multi-segment(Should not convert to bezier)
|
// Explicit perfect-curve multi-segment(Should not convert to bezier)
|
||||||
var seventh = ((IHasPath)decoded.HitObjects[6]).Path;
|
var seventh = ((IHasPath)decoded.HitObjects[6]).Path;
|
||||||
|
|
||||||
Assert.That(seventh.ControlPoints[0].Position.Value, Is.EqualTo(Vector2.Zero));
|
Assert.That(seventh.ControlPoints[0].Position, Is.EqualTo(Vector2.Zero));
|
||||||
Assert.That(seventh.ControlPoints[0].Type.Value == PathType.PerfectCurve);
|
Assert.That(seventh.ControlPoints[0].Type == PathType.PerfectCurve);
|
||||||
Assert.That(seventh.ControlPoints[1].Position.Value, Is.EqualTo(new Vector2(75, 145)));
|
Assert.That(seventh.ControlPoints[1].Position, Is.EqualTo(new Vector2(75, 145)));
|
||||||
Assert.That(seventh.ControlPoints[1].Type.Value == null);
|
Assert.That(seventh.ControlPoints[1].Type == null);
|
||||||
Assert.That(seventh.ControlPoints[2].Position.Value, Is.EqualTo(new Vector2(170, 75)));
|
Assert.That(seventh.ControlPoints[2].Position, Is.EqualTo(new Vector2(170, 75)));
|
||||||
|
|
||||||
Assert.That(seventh.ControlPoints[2].Type.Value == PathType.PerfectCurve);
|
Assert.That(seventh.ControlPoints[2].Type == PathType.PerfectCurve);
|
||||||
Assert.That(seventh.ControlPoints[3].Position.Value, Is.EqualTo(new Vector2(300, 145)));
|
Assert.That(seventh.ControlPoints[3].Position, Is.EqualTo(new Vector2(300, 145)));
|
||||||
Assert.That(seventh.ControlPoints[3].Type.Value == null);
|
Assert.That(seventh.ControlPoints[3].Type == null);
|
||||||
Assert.That(seventh.ControlPoints[4].Position.Value, Is.EqualTo(new Vector2(410, 20)));
|
Assert.That(seventh.ControlPoints[4].Position, Is.EqualTo(new Vector2(410, 20)));
|
||||||
Assert.That(seventh.ControlPoints[4].Type.Value == null);
|
Assert.That(seventh.ControlPoints[4].Type == null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ using osu.Framework.IO.Stores;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Beatmaps.Formats;
|
using osu.Game.Beatmaps.Formats;
|
||||||
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.IO.Serialization;
|
using osu.Game.IO.Serialization;
|
||||||
using osu.Game.Rulesets.Catch;
|
using osu.Game.Rulesets.Catch;
|
||||||
@ -49,6 +50,63 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(allBeatmaps))]
|
||||||
|
public void TestEncodeDecodeStabilityDoubleConvert(string name)
|
||||||
|
{
|
||||||
|
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
|
||||||
|
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
|
||||||
|
|
||||||
|
// run an extra convert. this is expected to be stable.
|
||||||
|
decodedAfterEncode.beatmap = convert(decodedAfterEncode.beatmap);
|
||||||
|
|
||||||
|
sort(decoded.beatmap);
|
||||||
|
sort(decodedAfterEncode.beatmap);
|
||||||
|
|
||||||
|
Assert.That(decodedAfterEncode.beatmap.Serialize(), Is.EqualTo(decoded.beatmap.Serialize()));
|
||||||
|
Assert.IsTrue(areComboColoursEqual(decodedAfterEncode.beatmapSkin.Configuration, decoded.beatmapSkin.Configuration));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCaseSource(nameof(allBeatmaps))]
|
||||||
|
public void TestEncodeDecodeStabilityWithNonLegacyControlPoints(string name)
|
||||||
|
{
|
||||||
|
var decoded = decodeFromLegacy(beatmaps_resource_store.GetStream(name), name);
|
||||||
|
|
||||||
|
// we are testing that the transfer of relevant data to hitobjects (from legacy control points) sticks through encode/decode.
|
||||||
|
// before the encode step, the legacy information is removed here.
|
||||||
|
decoded.beatmap.ControlPointInfo = removeLegacyControlPointTypes(decoded.beatmap.ControlPointInfo);
|
||||||
|
|
||||||
|
var decodedAfterEncode = decodeFromLegacy(encodeToLegacy(decoded), name);
|
||||||
|
|
||||||
|
// in this process, we may lose some detail in the control points section.
|
||||||
|
// let's focus on only the hitobjects.
|
||||||
|
var originalHitObjects = decoded.beatmap.HitObjects.Serialize();
|
||||||
|
var newHitObjects = decodedAfterEncode.beatmap.HitObjects.Serialize();
|
||||||
|
|
||||||
|
Assert.That(newHitObjects, Is.EqualTo(originalHitObjects));
|
||||||
|
|
||||||
|
ControlPointInfo removeLegacyControlPointTypes(ControlPointInfo controlPointInfo)
|
||||||
|
{
|
||||||
|
// emulate non-legacy control points by cloning the non-legacy portion.
|
||||||
|
// the assertion is that the encoder can recreate this losslessly from hitobject data.
|
||||||
|
Assert.IsInstanceOf<LegacyControlPointInfo>(controlPointInfo);
|
||||||
|
|
||||||
|
var newControlPoints = new ControlPointInfo();
|
||||||
|
|
||||||
|
foreach (var point in controlPointInfo.AllControlPoints)
|
||||||
|
{
|
||||||
|
// completely ignore "legacy" types, which have been moved to HitObjects.
|
||||||
|
// even though these would mostly be ignored by the Add call, they will still be available in groups,
|
||||||
|
// which isn't what we want to be testing here.
|
||||||
|
if (point is SampleControlPoint)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
newControlPoints.Add(point.Time, point.DeepClone());
|
||||||
|
}
|
||||||
|
|
||||||
|
return newControlPoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
|
public void TestEncodeMultiSegmentSliderWithFloatingPointError()
|
||||||
{
|
{
|
||||||
@ -116,7 +174,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
|
private MemoryStream encodeToLegacy((IBeatmap beatmap, ISkin beatmapSkin) fullBeatmap)
|
||||||
{
|
{
|
||||||
var (beatmap, beatmapSkin) = fullBeatmap;
|
var (beatmap, beatmapSkin) = fullBeatmap;
|
||||||
var stream = new MemoryStream();
|
var stream = new MemoryStream();
|
||||||
@ -169,7 +227,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
|
|
||||||
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
protected override Track GetBeatmapTrack() => throw new NotImplementedException();
|
||||||
|
|
||||||
protected override ISkin GetSkin() => throw new NotImplementedException();
|
protected internal override ISkin GetSkin() => throw new NotImplementedException();
|
||||||
|
|
||||||
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
public override Stream GetStream(string storagePath) => throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
@ -50,12 +50,13 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
|||||||
var beatmap = decodeAsJson(normal);
|
var beatmap = decodeAsJson(normal);
|
||||||
var beatmapInfo = beatmap.BeatmapInfo;
|
var beatmapInfo = beatmap.BeatmapInfo;
|
||||||
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
Assert.AreEqual(0, beatmapInfo.AudioLeadIn);
|
||||||
Assert.AreEqual(false, beatmapInfo.Countdown);
|
|
||||||
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
Assert.AreEqual(0.7f, beatmapInfo.StackLeniency);
|
||||||
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
|
Assert.AreEqual(false, beatmapInfo.SpecialStyle);
|
||||||
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
Assert.IsTrue(beatmapInfo.RulesetID == 0);
|
||||||
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
|
Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks);
|
||||||
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
|
Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard);
|
||||||
|
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
|
||||||
|
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
173
osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
Normal file
173
osu.Game.Tests/Beatmaps/TestSceneBeatmapDifficultyCache.cs
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// 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 System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Tests.Beatmaps.IO;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Beatmaps
|
||||||
|
{
|
||||||
|
[HeadlessTest]
|
||||||
|
public class TestSceneBeatmapDifficultyCache : OsuTestScene
|
||||||
|
{
|
||||||
|
public const double BASE_STARS = 5.55;
|
||||||
|
|
||||||
|
private BeatmapSetInfo importedSet;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private BeatmapManager beatmaps { get; set; }
|
||||||
|
|
||||||
|
private TestBeatmapDifficultyCache difficultyCache;
|
||||||
|
|
||||||
|
private IBindable<StarDifficulty?> starDifficultyBindable;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuGameBase osu)
|
||||||
|
{
|
||||||
|
importedSet = ImportBeatmapTest.LoadQuickOszIntoOsu(osu).Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("setup difficulty cache", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = Array.Empty<Mod>();
|
||||||
|
|
||||||
|
Child = difficultyCache = new TestBeatmapDifficultyCache();
|
||||||
|
|
||||||
|
starDifficultyBindable = difficultyCache.GetBindableDifficulty(importedSet.Beatmaps.First());
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value?.Stars == BASE_STARS);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStarDifficultyChangesOnModSettings()
|
||||||
|
{
|
||||||
|
OsuModDoubleTime dt = null;
|
||||||
|
|
||||||
|
AddStep("set computation function", () => difficultyCache.ComputeDifficulty = lookup =>
|
||||||
|
{
|
||||||
|
var modRateAdjust = (ModRateAdjust)lookup.OrderedMods.SingleOrDefault(mod => mod is ModRateAdjust);
|
||||||
|
return new StarDifficulty(BASE_STARS + modRateAdjust?.SpeedChange.Value ?? 0, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
|
||||||
|
AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.5);
|
||||||
|
|
||||||
|
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 1.25);
|
||||||
|
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.25);
|
||||||
|
|
||||||
|
AddStep("change selected mod to NC", () => SelectedMods.Value = new[] { new OsuModNightcore { SpeedChange = { Value = 1.75 } } });
|
||||||
|
AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value?.Stars == BASE_STARS + 1.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStarDifficultyAdjustHashCodeConflict()
|
||||||
|
{
|
||||||
|
OsuModDifficultyAdjust difficultyAdjust = null;
|
||||||
|
|
||||||
|
AddStep("set computation function", () => difficultyCache.ComputeDifficulty = lookup =>
|
||||||
|
{
|
||||||
|
var modDifficultyAdjust = (ModDifficultyAdjust)lookup.OrderedMods.SingleOrDefault(mod => mod is ModDifficultyAdjust);
|
||||||
|
return new StarDifficulty(BASE_STARS * (modDifficultyAdjust?.OverallDifficulty.Value ?? 1), 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("change selected mod to DA", () => SelectedMods.Value = new[] { difficultyAdjust = new OsuModDifficultyAdjust() });
|
||||||
|
AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value?.Stars == BASE_STARS);
|
||||||
|
|
||||||
|
AddStep("change DA difficulty to 0.5", () => difficultyAdjust.OverallDifficulty.Value = 0.5f);
|
||||||
|
AddUntilStep($"star difficulty -> {BASE_STARS * 0.5f}", () => starDifficultyBindable.Value?.Stars == BASE_STARS / 2);
|
||||||
|
|
||||||
|
// hash code of 0 (the value) conflicts with the hash code of null (the initial/default value).
|
||||||
|
// it's important that the mod reference and its underlying bindable references stay the same to demonstrate this failure.
|
||||||
|
AddStep("change DA difficulty to 0", () => difficultyAdjust.OverallDifficulty.Value = 0);
|
||||||
|
AddUntilStep("star difficulty -> 0", () => starDifficultyBindable.Value?.Stars == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyEqualsWithDifferentModInstances()
|
||||||
|
{
|
||||||
|
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||||
|
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||||
|
|
||||||
|
Assert.That(key1, Is.EqualTo(key2));
|
||||||
|
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyEqualsWithDifferentModOrder()
|
||||||
|
{
|
||||||
|
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHardRock(), new OsuModHidden() });
|
||||||
|
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModHidden(), new OsuModHardRock() });
|
||||||
|
|
||||||
|
Assert.That(key1, Is.EqualTo(key2));
|
||||||
|
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyDoesntEqualWithDifferentModSettings()
|
||||||
|
{
|
||||||
|
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
|
||||||
|
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
|
||||||
|
|
||||||
|
Assert.That(key1, Is.Not.EqualTo(key2));
|
||||||
|
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeyEqualWithMatchingModSettings()
|
||||||
|
{
|
||||||
|
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
|
||||||
|
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = 1234 }, new RulesetInfo { ID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
|
||||||
|
|
||||||
|
Assert.That(key1, Is.EqualTo(key2));
|
||||||
|
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestCase(1.3, DifficultyRating.Easy)]
|
||||||
|
[TestCase(1.993, DifficultyRating.Easy)]
|
||||||
|
[TestCase(1.998, DifficultyRating.Normal)]
|
||||||
|
[TestCase(2.4, DifficultyRating.Normal)]
|
||||||
|
[TestCase(2.693, DifficultyRating.Normal)]
|
||||||
|
[TestCase(2.698, DifficultyRating.Hard)]
|
||||||
|
[TestCase(3.5, DifficultyRating.Hard)]
|
||||||
|
[TestCase(3.993, DifficultyRating.Hard)]
|
||||||
|
[TestCase(3.997, DifficultyRating.Insane)]
|
||||||
|
[TestCase(5.0, DifficultyRating.Insane)]
|
||||||
|
[TestCase(5.292, DifficultyRating.Insane)]
|
||||||
|
[TestCase(5.297, DifficultyRating.Expert)]
|
||||||
|
[TestCase(6.2, DifficultyRating.Expert)]
|
||||||
|
[TestCase(6.493, DifficultyRating.Expert)]
|
||||||
|
[TestCase(6.498, DifficultyRating.ExpertPlus)]
|
||||||
|
[TestCase(8.3, DifficultyRating.ExpertPlus)]
|
||||||
|
public void TestDifficultyRatingMapping(double starRating, DifficultyRating expectedBracket)
|
||||||
|
{
|
||||||
|
var actualBracket = BeatmapDifficultyCache.GetDifficultyRating(starRating);
|
||||||
|
|
||||||
|
Assert.AreEqual(expectedBracket, actualBracket);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestBeatmapDifficultyCache : BeatmapDifficultyCache
|
||||||
|
{
|
||||||
|
public Func<DifficultyCacheLookup, StarDifficulty> ComputeDifficulty { get; set; }
|
||||||
|
|
||||||
|
protected override Task<StarDifficulty> ComputeValueAsync(DifficultyCacheLookup lookup, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
return Task.FromResult(ComputeDifficulty?.Invoke(lookup) ?? new StarDifficulty(BASE_STARS, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -19,6 +20,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
{
|
{
|
||||||
private ChannelManager channelManager;
|
private ChannelManager channelManager;
|
||||||
private int currentMessageId;
|
private int currentMessageId;
|
||||||
|
private List<Message> sentMessages;
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
@ -34,6 +36,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
AddStep("register request handling", () =>
|
AddStep("register request handling", () =>
|
||||||
{
|
{
|
||||||
currentMessageId = 0;
|
currentMessageId = 0;
|
||||||
|
sentMessages = new List<Message>();
|
||||||
|
|
||||||
((DummyAPIAccess)API).HandleRequest = req =>
|
((DummyAPIAccess)API).HandleRequest = req =>
|
||||||
{
|
{
|
||||||
@ -44,16 +47,11 @@ namespace osu.Game.Tests.Chat
|
|||||||
return true;
|
return true;
|
||||||
|
|
||||||
case PostMessageRequest postMessage:
|
case PostMessageRequest postMessage:
|
||||||
postMessage.TriggerSuccess(new Message(++currentMessageId)
|
handlePostMessageRequest(postMessage);
|
||||||
{
|
return true;
|
||||||
IsAction = postMessage.Message.IsAction,
|
|
||||||
ChannelId = postMessage.Message.ChannelId,
|
|
||||||
Content = postMessage.Message.Content,
|
|
||||||
Links = postMessage.Message.Links,
|
|
||||||
Timestamp = postMessage.Message.Timestamp,
|
|
||||||
Sender = postMessage.Message.Sender
|
|
||||||
});
|
|
||||||
|
|
||||||
|
case MarkChannelAsReadRequest markRead:
|
||||||
|
handleMarkChannelAsReadRequest(markRead);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,12 +81,65 @@ namespace osu.Game.Tests.Chat
|
|||||||
AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to"));
|
AddAssert("/np command received by channel 2", () => channel2.Messages.Last().Content.Contains("is listening to"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMarkAsReadIgnoringLocalMessages()
|
||||||
|
{
|
||||||
|
Channel channel = null;
|
||||||
|
|
||||||
|
AddStep("join channel and select it", () =>
|
||||||
|
{
|
||||||
|
channelManager.JoinChannel(channel = createChannel(1, ChannelType.Public));
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("post message", () => channelManager.PostMessage("Something interesting"));
|
||||||
|
|
||||||
|
AddStep("post /help command", () => channelManager.PostCommand("help", channel));
|
||||||
|
AddStep("post /me command with no action", () => channelManager.PostCommand("me", channel));
|
||||||
|
AddStep("post /join command with no channel", () => channelManager.PostCommand("join", channel));
|
||||||
|
AddStep("post /join command with non-existent channel", () => channelManager.PostCommand("join i-dont-exist", channel));
|
||||||
|
AddStep("post non-existent command", () => channelManager.PostCommand("non-existent-cmd arg", channel));
|
||||||
|
|
||||||
|
AddStep("mark channel as read", () => channelManager.MarkChannelAsRead(channel));
|
||||||
|
AddAssert("channel's last read ID is set to the latest message", () => channel.LastReadId == sentMessages.Last().Id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePostMessageRequest(PostMessageRequest request)
|
||||||
|
{
|
||||||
|
var message = new Message(++currentMessageId)
|
||||||
|
{
|
||||||
|
IsAction = request.Message.IsAction,
|
||||||
|
ChannelId = request.Message.ChannelId,
|
||||||
|
Content = request.Message.Content,
|
||||||
|
Links = request.Message.Links,
|
||||||
|
Timestamp = request.Message.Timestamp,
|
||||||
|
Sender = request.Message.Sender
|
||||||
|
};
|
||||||
|
|
||||||
|
sentMessages.Add(message);
|
||||||
|
request.TriggerSuccess(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMarkChannelAsReadRequest(MarkChannelAsReadRequest request)
|
||||||
|
{
|
||||||
|
// only accept messages that were sent through the API
|
||||||
|
if (sentMessages.Contains(request.Message))
|
||||||
|
{
|
||||||
|
request.TriggerSuccess();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
request.TriggerFailure(new APIException("unknown message!", null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Channel createChannel(int id, ChannelType type) => new Channel(new User())
|
private Channel createChannel(int id, ChannelType type) => new Channel(new User())
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
Name = $"Channel {id}",
|
Name = $"Channel {id}",
|
||||||
Topic = $"Topic of channel {id} with type {type}",
|
Topic = $"Topic of channel {id} with type {type}",
|
||||||
Type = type,
|
Type = type,
|
||||||
|
LastMessageId = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
private class ChannelManagerContainer : CompositeDrawable
|
private class ChannelManagerContainer : CompositeDrawable
|
||||||
|
@ -7,6 +7,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Collections.IO
|
namespace osu.Game.Tests.Collections.IO
|
||||||
@ -127,7 +128,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
[Test]
|
[Test]
|
||||||
public async Task TestSaveAndReload()
|
public async Task TestSaveAndReload()
|
||||||
{
|
{
|
||||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload", bypassCleanup: true))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -148,7 +149,7 @@ namespace osu.Game.Tests.Collections.IO
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (HeadlessGameHost host = new HeadlessGameHost("TestSaveAndReload"))
|
using (HeadlessGameHost host = new TestRunHeadlessGameHost("TestSaveAndReload"))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Platform;
|
|||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Input;
|
using osu.Game.Input;
|
||||||
using osu.Game.Input.Bindings;
|
using osu.Game.Input.Bindings;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using Realms;
|
using Realms;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Database
|
namespace osu.Game.Tests.Database
|
||||||
@ -42,7 +43,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||||
|
|
||||||
keyBindingStore.Register(testContainer);
|
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||||
|
|
||||||
Assert.That(queryCount(), Is.EqualTo(3));
|
Assert.That(queryCount(), Is.EqualTo(3));
|
||||||
|
|
||||||
@ -66,7 +67,7 @@ namespace osu.Game.Tests.Database
|
|||||||
{
|
{
|
||||||
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
KeyBindingContainer testContainer = new TestKeyBindingContainer();
|
||||||
|
|
||||||
keyBindingStore.Register(testContainer);
|
keyBindingStore.Register(testContainer, Enumerable.Empty<RulesetInfo>());
|
||||||
|
|
||||||
using (var primaryUsage = realmContextFactory.GetForRead())
|
using (var primaryUsage = realmContextFactory.GetForRead())
|
||||||
{
|
{
|
||||||
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
|||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Edit.Checks;
|
using osu.Game.Rulesets.Edit.Checks;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -30,7 +31,7 @@ namespace osu.Game.Tests.Editing.Checks
|
|||||||
{
|
{
|
||||||
check = new CheckMutedObjects();
|
check = new CheckMutedObjects();
|
||||||
|
|
||||||
cpi = new ControlPointInfo();
|
cpi = new LegacyControlPointInfo();
|
||||||
cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular });
|
cpi.Add(0, new SampleControlPoint { SampleVolume = volume_regular });
|
||||||
cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low });
|
cpi.Add(1000, new SampleControlPoint { SampleVolume = volume_low });
|
||||||
cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted });
|
cpi.Add(2000, new SampleControlPoint { SampleVolume = volume_muted });
|
||||||
|
@ -2,11 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Gameplay
|
namespace osu.Game.Tests.Gameplay
|
||||||
@ -121,6 +123,18 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddAssert("Drawable lifetime is restored", () => dho.LifetimeStart == 666 && dho.LifetimeEnd == 999);
|
AddAssert("Drawable lifetime is restored", () => dho.LifetimeStart == 666 && dho.LifetimeEnd == 999);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestStateChangeBeforeLoadComplete()
|
||||||
|
{
|
||||||
|
TestDrawableHitObject dho = null;
|
||||||
|
AddStep("Add DHO and apply result", () =>
|
||||||
|
{
|
||||||
|
Child = dho = new TestDrawableHitObject(new HitObject { StartTime = Time.Current });
|
||||||
|
dho.MissForcefully();
|
||||||
|
});
|
||||||
|
AddAssert("DHO state is correct", () => dho.State.Value == ArmedState.Miss);
|
||||||
|
}
|
||||||
|
|
||||||
private class TestDrawableHitObject : DrawableHitObject
|
private class TestDrawableHitObject : DrawableHitObject
|
||||||
{
|
{
|
||||||
public const double INITIAL_LIFETIME_OFFSET = 100;
|
public const double INITIAL_LIFETIME_OFFSET = 100;
|
||||||
@ -141,6 +155,19 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
if (SetLifetimeStartOnApply)
|
if (SetLifetimeStartOnApply)
|
||||||
LifetimeStart = LIFETIME_ON_APPLY;
|
LifetimeStart = LIFETIME_ON_APPLY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void MissForcefully() => ApplyResult(r => r.Type = HitResult.Miss);
|
||||||
|
|
||||||
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
{
|
||||||
|
if (state != ArmedState.Miss)
|
||||||
|
{
|
||||||
|
base.UpdateHitStateTransforms(state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.FadeOut(1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
private class TestLifetimeEntry : HitObjectLifetimeEntry
|
||||||
|
@ -204,7 +204,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
this.resources = resources;
|
this.resources = resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override ISkin GetSkin() => new TestSkin("test-sample", resources);
|
protected internal override ISkin GetSkin() => new TestSkin("test-sample", resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestDrawableStoryboardSample : DrawableStoryboardSample
|
private class TestDrawableStoryboardSample : DrawableStoryboardSample
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user