mirror of
https://github.com/ppy/osu.git
synced 2024-11-07 10:57:51 +08:00
Merge branch 'master' into gameplay/argon-key-counter_le-retour
This commit is contained in:
commit
809b817e8e
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@ -13,17 +13,17 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# FIXME: Tools won't run in .NET 6.0 unless you install 3.1.x LTS side by side.
|
||||
# https://itnext.io/how-to-support-multiple-net-sdks-in-github-actions-workflows-b988daa884e
|
||||
- name: Install .NET 3.1.x LTS
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "3.1.x"
|
||||
|
||||
- name: Install .NET 6.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "6.0.x"
|
||||
|
||||
@ -77,10 +77,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "6.0.x"
|
||||
|
||||
@ -94,7 +94,7 @@ jobs:
|
||||
# Attempt to upload results even if test fails.
|
||||
# https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#always
|
||||
- name: Upload Test Results
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}}
|
||||
@ -106,10 +106,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "6.0.x"
|
||||
|
||||
@ -125,10 +125,10 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install .NET 6.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "6.0.x"
|
||||
|
||||
|
18
.github/workflows/diffcalc.yml
vendored
18
.github/workflows/diffcalc.yml
vendored
@ -48,8 +48,8 @@ jobs:
|
||||
CONTINUE="no"
|
||||
fi
|
||||
|
||||
echo "::set-output name=continue::${CONTINUE}"
|
||||
echo "::set-output name=matrix::${MATRIX_JSON}"
|
||||
echo "continue=${CONTINUE}" >> $GITHUB_OUTPUT
|
||||
echo "matrix=${MATRIX_JSON}" >> $GITHUB_OUTPUT
|
||||
diffcalc:
|
||||
name: Run
|
||||
runs-on: self-hosted
|
||||
@ -80,34 +80,34 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
echo "::set-output name=branchname::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')"
|
||||
echo "::set-output name=repo::$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')"
|
||||
echo "branchname=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.ref' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||
echo "repo=$(curl -H "Authorization: token ${GITHUB_TOKEN}" ${{ github.event.issue.pull_request.url }} | jq '.head.repo.full_name' | sed 's/\"//g')" >> $GITHUB_OUTPUT
|
||||
|
||||
# Checkout osu
|
||||
- name: Checkout osu (master)
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: 'master/osu'
|
||||
- name: Checkout osu (pr)
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: 'pr/osu'
|
||||
repository: ${{ steps.upstreambranch.outputs.repo }}
|
||||
ref: ${{ steps.upstreambranch.outputs.branchname }}
|
||||
|
||||
- name: Checkout osu-difficulty-calculator (master)
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ppy/osu-difficulty-calculator
|
||||
path: 'master/osu-difficulty-calculator'
|
||||
- name: Checkout osu-difficulty-calculator (pr)
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: ppy/osu-difficulty-calculator
|
||||
path: 'pr/osu-difficulty-calculator'
|
||||
|
||||
- name: Install .NET 5.0.x
|
||||
uses: actions/setup-dotnet@v1
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: "5.0.x"
|
||||
|
||||
|
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
<!-- Contains required properties for osu!framework projects. -->
|
||||
<Project>
|
||||
<PropertyGroup Label="C#">
|
||||
<LangVersion>9.0</LangVersion>
|
||||
<LangVersion>10.0</LangVersion>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
@ -105,7 +105,7 @@ When it comes to contributing to the project, the two main things you can do to
|
||||
|
||||
If you wish to help with localisation efforts, head over to [crowdin](https://crowdin.com/project/osu-web).
|
||||
|
||||
For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project.
|
||||
We love to reward quality contributions. If you have made a large contribution, or are a regular contributor, you are welcome to [submit an expense via opencollective](https://opencollective.com/ppy/expenses/new). If you have any questions, feel free to [reach out to peppy](mailto:pe@ppy.sh) before doing so.
|
||||
|
||||
## Licence
|
||||
|
||||
|
@ -3,15 +3,53 @@
|
||||
#
|
||||
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
|
||||
|
||||
$CSPROJ="osu.Game/osu.Game.csproj"
|
||||
$GAME_CSPROJ="osu.Game/osu.Game.csproj"
|
||||
$ANDROID_PROPS="osu.Android.props"
|
||||
$IOS_PROPS="osu.iOS.props"
|
||||
$SLN="osu.sln"
|
||||
|
||||
dotnet remove $CSPROJ package ppy.osu.Framework;
|
||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj;
|
||||
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
||||
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework;
|
||||
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android;
|
||||
dotnet remove $IOS_PROPS reference ppy.osu.Framework.iOS;
|
||||
|
||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj `
|
||||
../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj `
|
||||
../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj `
|
||||
../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj;
|
||||
|
||||
dotnet add $GAME_CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj;
|
||||
dotnet add $ANDROID_PROPS reference ../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj;
|
||||
dotnet add $IOS_PROPS reference ../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj;
|
||||
|
||||
# workaround for dotnet add not inserting $(MSBuildThisFileDirectory) on props files
|
||||
(Get-Content "osu.Android.props") -replace "`"..\\osu-framework", "`"`$(MSBuildThisFileDirectory)..\osu-framework" | Set-Content "osu.Android.props"
|
||||
(Get-Content "osu.iOS.props") -replace "`"..\\osu-framework", "`"`$(MSBuildThisFileDirectory)..\osu-framework" | Set-Content "osu.iOS.props"
|
||||
|
||||
# needed because iOS framework nupkg includes a set of properties to work around certain issues during building,
|
||||
# and those get ignored when referencing framework via project, threfore we have to manually include it via props reference.
|
||||
(Get-Content "osu.iOS.props") |
|
||||
Foreach-Object {
|
||||
if ($_ -match "</Project>")
|
||||
{
|
||||
" <Import Project=`"`$(MSBuildThisFileDirectory)../osu-framework/osu.Framework.iOS.props`"/>"
|
||||
}
|
||||
|
||||
$_
|
||||
} | Set-Content "osu.iOS.props"
|
||||
|
||||
$TMP=New-TemporaryFile
|
||||
|
||||
$SLNF=Get-Content "osu.Desktop.slnf" | ConvertFrom-Json
|
||||
$TMP=New-TemporaryFile
|
||||
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj")
|
||||
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
|
||||
Move-Item -Path $TMP -Destination "osu.Desktop.slnf" -Force
|
||||
|
||||
$SLNF=Get-Content "osu.Android.slnf" | ConvertFrom-Json
|
||||
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj")
|
||||
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
|
||||
Move-Item -Path $TMP -Destination "osu.Android.slnf" -Force
|
||||
|
||||
$SLNF=Get-Content "osu.iOS.slnf" | ConvertFrom-Json
|
||||
$SLNF.solution.projects += ("../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj")
|
||||
ConvertTo-Json $SLNF | Out-File $TMP -Encoding UTF8
|
||||
Move-Item -Path $TMP -Destination "osu.iOS.slnf" -Force
|
||||
|
@ -5,14 +5,41 @@
|
||||
#
|
||||
# https://github.com/ppy/osu-framework/wiki/Testing-local-framework-checkout-with-other-projects
|
||||
|
||||
CSPROJ="osu.Game/osu.Game.csproj"
|
||||
GAME_CSPROJ="osu.Game/osu.Game.csproj"
|
||||
ANDROID_PROPS="osu.Android.props"
|
||||
IOS_PROPS="osu.iOS.props"
|
||||
SLN="osu.sln"
|
||||
|
||||
dotnet remove $CSPROJ package ppy.osu.Framework
|
||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj ../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj
|
||||
dotnet add $CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
||||
dotnet remove $GAME_CSPROJ reference ppy.osu.Framework
|
||||
dotnet remove $ANDROID_PROPS reference ppy.osu.Framework.Android
|
||||
dotnet remove $IOS_PROPS reference ppy.osu.Framework.iOS
|
||||
|
||||
dotnet sln $SLN add ../osu-framework/osu.Framework/osu.Framework.csproj \
|
||||
../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj \
|
||||
../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj \
|
||||
../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj
|
||||
|
||||
dotnet add $GAME_CSPROJ reference ../osu-framework/osu.Framework/osu.Framework.csproj
|
||||
dotnet add $ANDROID_PROPS reference ../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj
|
||||
dotnet add $IOS_PROPS reference ../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj
|
||||
|
||||
# workaround for dotnet add not inserting $(MSBuildThisFileDirectory) on props files
|
||||
sed -i.bak 's:"..\\osu-framework:"$(MSBuildThisFileDirectory)..\\osu-framework:g' ./osu.Android.props && rm osu.Android.props.bak
|
||||
sed -i.bak 's:"..\\osu-framework:"$(MSBuildThisFileDirectory)..\\osu-framework:g' ./osu.iOS.props && rm osu.iOS.props.bak
|
||||
|
||||
# needed because iOS framework nupkg includes a set of properties to work around certain issues during building,
|
||||
# and those get ignored when referencing framework via project, threfore we have to manually include it via props reference.
|
||||
sed -i.bak '/<\/Project>/i\
|
||||
<Import Project=\"$(MSBuildThisFileDirectory)../osu-framework/osu.Framework.iOS.props\"/>\
|
||||
' ./osu.iOS.props && rm osu.iOS.props.bak
|
||||
|
||||
SLNF="osu.Desktop.slnf"
|
||||
tmp=$(mktemp)
|
||||
|
||||
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj"]' osu.Desktop.slnf > $tmp
|
||||
mv -f $tmp $SLNF
|
||||
mv -f $tmp osu.Desktop.slnf
|
||||
|
||||
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.Android/osu.Framework.Android.csproj"]' osu.Android.slnf > $tmp
|
||||
mv -f $tmp osu.Android.slnf
|
||||
|
||||
jq '.solution.projects += ["../osu-framework/osu.Framework/osu.Framework.csproj", "../osu-framework/osu.Framework.NativeLibs/osu.Framework.NativeLibs.csproj", "../osu-framework/osu.Framework.iOS/osu.Framework.iOS.csproj"]' osu.iOS.slnf > $tmp
|
||||
mv -f $tmp osu.iOS.slnf
|
||||
|
@ -8,9 +8,13 @@
|
||||
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
|
||||
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
<AndroidManifestMerger>manifestmerger.jar</AndroidManifestMerger>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.131.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2023.403.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidManifestOverlay Include="$(MSBuildThisFileDirectory)osu.Android\Properties\AndroidManifestOverlay.xml" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
15
osu.Android/Properties/AndroidManifestOverlay.xml
Normal file
15
osu.Android/Properties/AndroidManifestOverlay.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="mailto" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
@ -169,7 +169,7 @@ namespace osu.Desktop
|
||||
case UserActivity.InGame game:
|
||||
return game.BeatmapInfo;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
case UserActivity.EditingBeatmap edit:
|
||||
return edit.BeatmapInfo;
|
||||
}
|
||||
|
||||
@ -183,10 +183,10 @@ namespace osu.Desktop
|
||||
case UserActivity.InGame game:
|
||||
return game.BeatmapInfo.ToString() ?? string.Empty;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
case UserActivity.EditingBeatmap edit:
|
||||
return edit.BeatmapInfo.ToString() ?? string.Empty;
|
||||
|
||||
case UserActivity.Watching watching:
|
||||
case UserActivity.WatchingReplay watching:
|
||||
return watching.BeatmapInfo.ToString();
|
||||
|
||||
case UserActivity.InLobby lobby:
|
||||
|
@ -5,7 +5,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
@ -139,7 +138,17 @@ namespace osu.Desktop
|
||||
|
||||
desktopWindow.CursorState |= CursorState.Hidden;
|
||||
desktopWindow.Title = Name;
|
||||
desktopWindow.DragDrop += f => fileDrop(new[] { f });
|
||||
desktopWindow.DragDrop += f =>
|
||||
{
|
||||
// on macOS, URL associations are handled via SDL_DROPFILE events.
|
||||
if (f.StartsWith(OSU_PROTOCOL, StringComparison.Ordinal))
|
||||
{
|
||||
HandleLink(f);
|
||||
return;
|
||||
}
|
||||
|
||||
fileDrop(new[] { f });
|
||||
};
|
||||
}
|
||||
|
||||
protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo();
|
||||
@ -151,10 +160,6 @@ namespace osu.Desktop
|
||||
{
|
||||
lock (importableFiles)
|
||||
{
|
||||
string firstExtension = Path.GetExtension(filePaths.First());
|
||||
|
||||
if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return;
|
||||
|
||||
importableFiles.AddRange(filePaths);
|
||||
|
||||
Logger.Log($"Adding {filePaths.Length} files for import");
|
||||
|
123
osu.Game.Benchmarks/BenchmarkCarouselFilter.cs
Normal file
123
osu.Game.Benchmarks/BenchmarkCarouselFilter.cs
Normal file
@ -0,0 +1,123 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
|
||||
namespace osu.Game.Benchmarks
|
||||
{
|
||||
public class BenchmarkCarouselFilter : BenchmarkTest
|
||||
{
|
||||
private BeatmapInfo getExampleBeatmap() => new BeatmapInfo
|
||||
{
|
||||
Ruleset = new RulesetInfo
|
||||
{
|
||||
ShortName = "osu",
|
||||
OnlineID = 0
|
||||
},
|
||||
StarRating = 4.0d,
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
ApproachRate = 5.0f,
|
||||
DrainRate = 3.0f,
|
||||
CircleSize = 2.0f,
|
||||
},
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Artist = "The Artist",
|
||||
ArtistUnicode = "check unicode too",
|
||||
Title = "Title goes here",
|
||||
TitleUnicode = "Title goes here",
|
||||
Author = { Username = "The Author" },
|
||||
Source = "unit tests",
|
||||
Tags = "look for tags too",
|
||||
},
|
||||
DifficultyName = "version as well",
|
||||
Length = 2500,
|
||||
BPM = 160,
|
||||
BeatDivisor = 12,
|
||||
Status = BeatmapOnlineStatus.Loved
|
||||
};
|
||||
|
||||
private CarouselBeatmap carouselBeatmap = null!;
|
||||
private FilterCriteria criteria1 = null!;
|
||||
private FilterCriteria criteria2 = null!;
|
||||
private FilterCriteria criteria3 = null!;
|
||||
private FilterCriteria criteria4 = null!;
|
||||
private FilterCriteria criteria5 = null!;
|
||||
private FilterCriteria criteria6 = null!;
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
var beatmap = getExampleBeatmap();
|
||||
beatmap.OnlineID = 20201010;
|
||||
beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 };
|
||||
carouselBeatmap = new CarouselBeatmap(beatmap);
|
||||
criteria1 = new FilterCriteria();
|
||||
criteria2 = new FilterCriteria
|
||||
{
|
||||
Ruleset = new RulesetInfo { ShortName = "catch" }
|
||||
};
|
||||
criteria3 = new FilterCriteria
|
||||
{
|
||||
Ruleset = new RulesetInfo { OnlineID = 6 },
|
||||
AllowConvertedBeatmaps = true,
|
||||
BPM = new FilterCriteria.OptionalRange<double>
|
||||
{
|
||||
IsUpperInclusive = false,
|
||||
Max = 160d
|
||||
}
|
||||
};
|
||||
criteria4 = new FilterCriteria
|
||||
{
|
||||
Ruleset = new RulesetInfo { OnlineID = 6 },
|
||||
AllowConvertedBeatmaps = true,
|
||||
SearchText = "an artist"
|
||||
};
|
||||
criteria5 = new FilterCriteria
|
||||
{
|
||||
Creator = new FilterCriteria.OptionalTextFilter { SearchTerm = "the author AND then something else" }
|
||||
};
|
||||
criteria6 = new FilterCriteria { SearchText = "20201010" };
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CarouselBeatmapFilter()
|
||||
{
|
||||
carouselBeatmap.Filter(criteria1);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CriteriaMatchingSpecificRuleset()
|
||||
{
|
||||
carouselBeatmap.Filter(criteria2);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CriteriaMatchingRangeMax()
|
||||
{
|
||||
carouselBeatmap.Filter(criteria3);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CriteriaMatchingTerms()
|
||||
{
|
||||
carouselBeatmap.Filter(criteria4);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CriteriaMatchingCreator()
|
||||
{
|
||||
carouselBeatmap.Filter(criteria5);
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void CriteriaMatchingBeatmapIDs()
|
||||
{
|
||||
carouselBeatmap.Filter(criteria6);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameAppDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using UIKit;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||
{
|
||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
if (withModifiedSkin)
|
||||
{
|
||||
AddStep("change component scale", () => Player.ChildrenOfType<LegacyScoreCounter>().First().Scale = new Vector2(2f));
|
||||
AddStep("update target", () => Player.ChildrenOfType<SkinnableTargetContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
||||
AddStep("update target", () => Player.ChildrenOfType<SkinComponentsContainer>().ForEach(LegacySkin.UpdateDrawableTarget));
|
||||
AddStep("exit player", () => Player.Exit());
|
||||
CreateTest();
|
||||
}
|
||||
|
@ -60,26 +60,24 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[Test]
|
||||
public void TestCatcherHyperStateReverted()
|
||||
{
|
||||
DrawableCatchHitObject drawableObject1 = null;
|
||||
DrawableCatchHitObject drawableObject2 = null;
|
||||
JudgementResult result1 = null;
|
||||
JudgementResult result2 = null;
|
||||
AddStep("catch hyper fruit", () =>
|
||||
{
|
||||
attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
|
||||
result1 = attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
|
||||
});
|
||||
AddStep("catch normal fruit", () =>
|
||||
{
|
||||
attemptCatch(new Fruit(), out drawableObject2, out result2);
|
||||
result2 = attemptCatch(new Fruit());
|
||||
});
|
||||
AddStep("revert second result", () =>
|
||||
{
|
||||
catcher.OnRevertResult(drawableObject2, result2);
|
||||
catcher.OnRevertResult(result2);
|
||||
});
|
||||
checkHyperDash(true);
|
||||
AddStep("revert first result", () =>
|
||||
{
|
||||
catcher.OnRevertResult(drawableObject1, result1);
|
||||
catcher.OnRevertResult(result1);
|
||||
});
|
||||
checkHyperDash(false);
|
||||
}
|
||||
@ -87,16 +85,15 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
[Test]
|
||||
public void TestCatcherAnimationStateReverted()
|
||||
{
|
||||
DrawableCatchHitObject drawableObject = null;
|
||||
JudgementResult result = null;
|
||||
AddStep("catch kiai fruit", () =>
|
||||
{
|
||||
attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
|
||||
result = attemptCatch(new TestKiaiFruit());
|
||||
});
|
||||
checkState(CatcherAnimationState.Kiai);
|
||||
AddStep("revert result", () =>
|
||||
{
|
||||
catcher.OnRevertResult(drawableObject, result);
|
||||
catcher.OnRevertResult(result);
|
||||
});
|
||||
checkState(CatcherAnimationState.Idle);
|
||||
}
|
||||
@ -268,23 +265,19 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
|
||||
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
|
||||
|
||||
private void attemptCatch(CatchHitObject hitObject)
|
||||
{
|
||||
attemptCatch(() => hitObject, 1);
|
||||
}
|
||||
|
||||
private void attemptCatch(Func<CatchHitObject> hitObject, int count)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
attemptCatch(hitObject(), out _, out _);
|
||||
attemptCatch(hitObject());
|
||||
}
|
||||
|
||||
private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
|
||||
private JudgementResult attemptCatch(CatchHitObject hitObject)
|
||||
{
|
||||
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
drawableObject = createDrawableObject(hitObject);
|
||||
result = createResult(hitObject);
|
||||
var drawableObject = createDrawableObject(hitObject);
|
||||
var result = createResult(hitObject);
|
||||
applyResult(drawableObject, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 1,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 1,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Graphics;
|
||||
|
||||
@ -27,12 +28,12 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
if (lookup is GlobalSkinComponentLookup targetComponent)
|
||||
if (lookup is SkinComponentsContainerLookup containerLookup)
|
||||
{
|
||||
switch (targetComponent.Lookup)
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case GlobalSkinComponentLookup.LookupType.MainHUDComponents:
|
||||
var components = base.GetDrawableComponent(lookup) as SkinnableTargetComponentsContainer;
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
var components = base.GetDrawableComponent(lookup) as Container;
|
||||
|
||||
if (providesComboCounter && components != null)
|
||||
{
|
||||
|
@ -63,12 +63,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value);
|
||||
}
|
||||
|
||||
public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result)
|
||||
public void OnRevertResult(JudgementResult result)
|
||||
{
|
||||
if (!result.Type.AffectsCombo() || !result.HasResult)
|
||||
return;
|
||||
|
||||
updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value);
|
||||
updateCombo(result.ComboAtJudgement, null);
|
||||
}
|
||||
|
||||
private void updateCombo(int newCombo, Color4? hitObjectColour)
|
||||
|
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||
|
||||
private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||
=> CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result);
|
||||
private void onRevertResult(JudgementResult result)
|
||||
=> CatcherArea.OnRevertResult(result);
|
||||
}
|
||||
}
|
||||
|
@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Origin = Anchor.TopCentre;
|
||||
|
||||
Size = new Vector2(BASE_SIZE);
|
||||
|
||||
if (difficulty != null)
|
||||
Scale = calculateScale(difficulty);
|
||||
|
||||
@ -254,7 +255,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
||||
public void OnRevertResult(JudgementResult result)
|
||||
{
|
||||
var catchResult = (CatchJudgementResult)result;
|
||||
|
||||
@ -268,8 +269,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
SetHyperDashState();
|
||||
}
|
||||
|
||||
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
|
||||
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
|
||||
caughtObjectContainer.RemoveAll(d => d.HitObject == result.HitObject, false);
|
||||
droppedObjectTarget.RemoveAll(d => d.HitObject == result.HitObject, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -333,8 +334,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
base.Update();
|
||||
|
||||
var scaleFromDirection = new Vector2((int)VisualDirection, 1);
|
||||
|
||||
body.Scale = scaleFromDirection;
|
||||
caughtObjectContainer.Scale = hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
||||
// Inverse of catcher scale is applied here, as catcher gets scaled by circle size and so do the incoming fruit.
|
||||
caughtObjectContainer.Scale = (1 / Scale.X) * (flipCatcherPlate ? scaleFromDirection : Vector2.One);
|
||||
hitExplosionContainer.Scale = flipCatcherPlate ? scaleFromDirection : Vector2.One;
|
||||
|
||||
// Correct overshooting.
|
||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||
|
@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
comboDisplay.OnNewResult(hitObject, result);
|
||||
}
|
||||
|
||||
public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result)
|
||||
public void OnRevertResult(JudgementResult result)
|
||||
{
|
||||
comboDisplay.OnRevertResult(hitObject, result);
|
||||
Catcher.OnRevertResult(hitObject, result);
|
||||
comboDisplay.OnRevertResult(result);
|
||||
Catcher.OnRevertResult(result);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
@ -1,17 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameAppDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using UIKit;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||
{
|
||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,67 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
public partial class TestSceneObjectPlacement : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
[Test]
|
||||
public void TestPlacementBeforeTrackStart()
|
||||
{
|
||||
AddStep("Seek to 0", () => EditorClock.Seek(0));
|
||||
AddStep("Select note", () => InputManager.Key(Key.Number2));
|
||||
AddStep("Hover negative span", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
|
||||
});
|
||||
AddStep("Click", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSeekOnNotePlacement()
|
||||
{
|
||||
double? initialTime = null;
|
||||
|
||||
AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
|
||||
AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, true));
|
||||
placeObject();
|
||||
AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking);
|
||||
AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoSeekOnNotePlacement()
|
||||
{
|
||||
double? initialTime = null;
|
||||
|
||||
AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
|
||||
AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, false));
|
||||
placeObject();
|
||||
AddAssert("not seeking", () => !EditorClock.IsSeeking);
|
||||
AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime));
|
||||
}
|
||||
|
||||
private void placeObject()
|
||||
{
|
||||
AddStep("select note placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to centre of last column", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Last().ScreenSpaceDrawQuad.Centre));
|
||||
AddStep("place note", () => InputManager.Click(MouseButton.Left));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||
{
|
||||
public partial class TestScenePlacementBeforeTrackStart : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestPlacement()
|
||||
{
|
||||
AddStep("Seek to 0", () => EditorClock.Seek(0));
|
||||
AddStep("Select note", () => InputManager.Key(Key.Number2));
|
||||
AddStep("Hover negative span", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
|
||||
});
|
||||
AddStep("Click", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,9 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
@ -10,5 +13,19 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
public partial class TestSceneManiaPlayer : PlayerTestScene
|
||||
{
|
||||
protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset();
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("change direction to down", () => changeDirectionTo(ManiaScrollingDirection.Down));
|
||||
AddStep("change direction to up", () => changeDirectionTo(ManiaScrollingDirection.Up));
|
||||
}
|
||||
|
||||
private void changeDirectionTo(ManiaScrollingDirection direction)
|
||||
{
|
||||
var rulesetConfig = (ManiaRulesetConfigManager)RulesetConfigs.GetConfigFor(new ManiaRuleset()).AsNonNull();
|
||||
rulesetConfig.SetValue(ManiaRulesetSetting.ScrollDirection, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -69,8 +69,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
private double? releaseTime;
|
||||
|
||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||
|
||||
public DrawableHoldNote()
|
||||
: this(null)
|
||||
{
|
||||
@ -238,6 +236,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
};
|
||||
|
||||
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||
// The rationale for this is account for heads/tails with corner radius.
|
||||
bodyPiece.Y = (Direction.Value == ScrollingDirection.Up ? 1 : -1) * Head.Height / 2;
|
||||
bodyPiece.Height = DrawHeight - Head.Height / 2 + Tail.Height / 2;
|
||||
|
||||
|
@ -15,16 +15,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
/// </summary>
|
||||
public partial class DrawableHoldNoteTail : DrawableNote
|
||||
{
|
||||
/// <summary>
|
||||
/// Lenience of release hit windows. This is to make cases where the hold note release
|
||||
/// is timed alongside presses of other hit objects less awkward.
|
||||
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
|
||||
/// </summary>
|
||||
private const double release_window_lenience = 1.5;
|
||||
|
||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||
|
||||
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||
protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||
|
||||
public DrawableHoldNoteTail()
|
||||
: this(null)
|
||||
@ -40,14 +33,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
|
||||
public void UpdateResult() => base.UpdateResult(true);
|
||||
|
||||
public override double MaximumJudgementOffset => base.MaximumJudgementOffset * release_window_lenience;
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
Debug.Assert(HitObject.HitWindows != null);
|
||||
|
||||
// Factor in the release lenience
|
||||
timeOffset /= release_window_lenience;
|
||||
timeOffset /= TailNote.RELEASE_WINDOW_LENIENCE;
|
||||
|
||||
if (!userTriggered)
|
||||
{
|
||||
|
@ -81,6 +81,8 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
/// </summary>
|
||||
public TailNote Tail { get; private set; }
|
||||
|
||||
public override double MaximumJudgementOffset => Tail.MaximumJudgementOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The time between ticks of this hold.
|
||||
/// </summary>
|
||||
|
@ -10,6 +10,15 @@ namespace osu.Game.Rulesets.Mania.Objects
|
||||
{
|
||||
public class TailNote : Note
|
||||
{
|
||||
/// <summary>
|
||||
/// Lenience of release hit windows. This is to make cases where the hold note release
|
||||
/// is timed alongside presses of other hit objects less awkward.
|
||||
/// Todo: This shouldn't exist for non-LegacyBeatmapDecoder beatmaps
|
||||
/// </summary>
|
||||
public const double RELEASE_WINDOW_LENIENCE = 1.5;
|
||||
|
||||
public override Judgement CreateJudgement() => new ManiaJudgement();
|
||||
|
||||
public override double MaximumJudgementOffset => base.MaximumJudgementOffset * RELEASE_WINDOW_LENIENCE;
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +43,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
largeFaint = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||
Masking = true,
|
||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||
Blending = BlendingParameters.Additive,
|
||||
@ -80,11 +79,15 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
if (direction.NewValue == ScrollingDirection.Up)
|
||||
{
|
||||
Anchor = Anchor.TopCentre;
|
||||
largeFaint.Anchor = Anchor.TopCentre;
|
||||
largeFaint.Origin = Anchor.TopCentre;
|
||||
Y = ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
Anchor = Anchor.BottomCentre;
|
||||
largeFaint.Anchor = Anchor.BottomCentre;
|
||||
largeFaint.Origin = Anchor.BottomCentre;
|
||||
Y = -ArgonNotePiece.NOTE_HEIGHT / 2;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||
Height = ArgonNotePiece.NOTE_HEIGHT * ArgonNotePiece.NOTE_ACCENT_RATIO;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||
|
@ -20,10 +20,9 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
public partial class ArgonHoldBodyPiece : CompositeDrawable, IHoldNoteBody
|
||||
{
|
||||
protected readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||
protected readonly IBindable<bool> IsHitting = new Bindable<bool>();
|
||||
|
||||
private Drawable background = null!;
|
||||
private Box foreground = null!;
|
||||
private ArgonHoldNoteHittingLayer hittingLayer = null!;
|
||||
|
||||
public ArgonHoldBodyPiece()
|
||||
{
|
||||
@ -32,7 +31,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
// Without this, the width of the body will be slightly larger than the head/tail.
|
||||
Masking = true;
|
||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||
Blending = BlendingParameters.Additive;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
@ -41,12 +39,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
InternalChildren = new[]
|
||||
{
|
||||
background = new Box { RelativeSizeAxes = Axes.Both },
|
||||
foreground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
},
|
||||
hittingLayer = new ArgonHoldNoteHittingLayer()
|
||||
};
|
||||
|
||||
if (drawableObject != null)
|
||||
@ -54,44 +47,19 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
var holdNote = (DrawableHoldNote)drawableObject;
|
||||
|
||||
AccentColour.BindTo(holdNote.AccentColour);
|
||||
IsHitting.BindTo(holdNote.IsHitting);
|
||||
hittingLayer.AccentColour.BindTo(holdNote.AccentColour);
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNote.IsHitting);
|
||||
}
|
||||
|
||||
AccentColour.BindValueChanged(colour =>
|
||||
{
|
||||
background.Colour = colour.NewValue.Darken(1.2f);
|
||||
foreground.Colour = colour.NewValue.Opacity(0.2f);
|
||||
background.Colour = colour.NewValue.Darken(0.6f);
|
||||
}, true);
|
||||
|
||||
IsHitting.BindValueChanged(hitting =>
|
||||
{
|
||||
const float animation_length = 50;
|
||||
|
||||
foreground.ClearTransforms();
|
||||
|
||||
if (hitting.NewValue)
|
||||
{
|
||||
// wait for the next sync point
|
||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||
|
||||
using (foreground.BeginDelayedSequence(synchronisedOffset))
|
||||
{
|
||||
foreground.FadeTo(1, animation_length).Then()
|
||||
.FadeTo(0.5f, animation_length)
|
||||
.Loop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreground.FadeOut(animation_length);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Recycle()
|
||||
{
|
||||
foreground.ClearTransforms();
|
||||
foreground.Alpha = 0;
|
||||
hittingLayer.Recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
internal partial class ArgonHoldNoteHeadPiece : ArgonNotePiece
|
||||
{
|
||||
protected override Drawable CreateIcon() => new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = 2,
|
||||
Size = new Vector2(20, 5),
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osuTK.Graphics;
|
||||
using Box = osu.Framework.Graphics.Shapes.Box;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
public partial class ArgonHoldNoteHittingLayer : Box
|
||||
{
|
||||
public readonly Bindable<Color4> AccentColour = new Bindable<Color4>();
|
||||
public readonly Bindable<bool> IsHitting = new Bindable<bool>();
|
||||
|
||||
public ArgonHoldNoteHittingLayer()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Blending = BlendingParameters.Additive;
|
||||
Alpha = 0;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AccentColour.BindValueChanged(colour =>
|
||||
{
|
||||
Colour = colour.NewValue.Lighten(0.2f).Opacity(0.3f);
|
||||
}, true);
|
||||
|
||||
IsHitting.BindValueChanged(hitting =>
|
||||
{
|
||||
const float animation_length = 80;
|
||||
|
||||
ClearTransforms();
|
||||
|
||||
if (hitting.NewValue)
|
||||
{
|
||||
// wait for the next sync point
|
||||
double synchronisedOffset = animation_length * 2 - Time.Current % (animation_length * 2);
|
||||
|
||||
using (BeginDelayedSequence(synchronisedOffset))
|
||||
{
|
||||
this.FadeTo(1, animation_length, Easing.OutSine).Then()
|
||||
.FadeTo(0.5f, animation_length, Easing.InSine)
|
||||
.Loop();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.FadeOut(animation_length);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void Recycle()
|
||||
{
|
||||
ClearTransforms();
|
||||
Alpha = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -5,8 +5,10 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
@ -16,47 +18,68 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
internal partial class ArgonHoldNoteTailPiece : CompositeDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private DrawableHitObject? drawableObject { get; set; }
|
||||
|
||||
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
private readonly Box shadeBackground;
|
||||
private readonly Box shadeForeground;
|
||||
private readonly Box foreground;
|
||||
private readonly ArgonHoldNoteHittingLayer hittingLayer;
|
||||
private readonly Box foregroundAdditive;
|
||||
|
||||
public ArgonHoldNoteTailPiece()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = ArgonNotePiece.NOTE_HEIGHT;
|
||||
|
||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS;
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
shadeBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = ArgonNotePiece.NOTE_HEIGHT,
|
||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
shadeForeground = new Box
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black),
|
||||
// Avoid ugly single pixel overlap.
|
||||
Height = 0.9f,
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Height = ArgonNotePiece.NOTE_ACCENT_RATIO,
|
||||
CornerRadius = ArgonNotePiece.CORNER_RADIUS,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
foreground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
hittingLayer = new ArgonHoldNoteHittingLayer(),
|
||||
foregroundAdditive = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Blending = BlendingParameters.Additive,
|
||||
Height = 0.5f,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||
private void load(IScrollingInfo scrollingInfo)
|
||||
{
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
direction.BindValueChanged(onDirectionChanged, true);
|
||||
@ -65,9 +88,24 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
{
|
||||
accentColour.BindTo(drawableObject.AccentColour);
|
||||
accentColour.BindValueChanged(onAccentChanged, true);
|
||||
|
||||
drawableObject.HitObjectApplied += hitObjectApplied;
|
||||
}
|
||||
}
|
||||
|
||||
private void hitObjectApplied(DrawableHitObject drawableHitObject)
|
||||
{
|
||||
var holdNoteTail = (DrawableHoldNoteTail)drawableHitObject;
|
||||
|
||||
hittingLayer.Recycle();
|
||||
|
||||
hittingLayer.AccentColour.UnbindBindings();
|
||||
hittingLayer.AccentColour.BindTo(holdNoteTail.HoldNote.AccentColour);
|
||||
|
||||
hittingLayer.IsHitting.UnbindBindings();
|
||||
((IBindable<bool>)hittingLayer.IsHitting).BindTo(holdNoteTail.HoldNote.IsHitting);
|
||||
}
|
||||
|
||||
private void onDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||
{
|
||||
Scale = new Vector2(1, direction.NewValue == ScrollingDirection.Up ? -1 : 1);
|
||||
@ -75,8 +113,20 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
private void onAccentChanged(ValueChangedEvent<Color4> accent)
|
||||
{
|
||||
shadeBackground.Colour = accent.NewValue.Darken(1.7f);
|
||||
shadeForeground.Colour = accent.NewValue.Darken(1.1f);
|
||||
foreground.Colour = accent.NewValue.Darken(0.6f); // matches body
|
||||
|
||||
foregroundAdditive.Colour = ColourInfo.GradientVertical(
|
||||
accent.NewValue.Opacity(0.4f),
|
||||
accent.NewValue.Opacity(0)
|
||||
);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableObject != null)
|
||||
drawableObject.HitObjectApplied -= hitObjectApplied;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
private readonly IBindable<Color4> accentColour = new Bindable<Color4>();
|
||||
|
||||
private readonly Box colouredBox;
|
||||
private readonly Box shadow;
|
||||
|
||||
public ArgonNotePiece()
|
||||
{
|
||||
@ -36,11 +35,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
CornerRadius = CORNER_RADIUS;
|
||||
Masking = true;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChildren = new[]
|
||||
{
|
||||
shadow = new Box
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0), Colour4.Black)
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -65,18 +65,22 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = CORNER_RADIUS * 2,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = 4,
|
||||
Icon = FontAwesome.Solid.AngleDown,
|
||||
Size = new Vector2(20),
|
||||
Scale = new Vector2(1, 0.7f)
|
||||
}
|
||||
CreateIcon(),
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual Drawable CreateIcon() => new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Y = 4,
|
||||
// TODO: replace with a non-squashed version.
|
||||
// The 0.7f height scale should be removed.
|
||||
Icon = FontAwesome.Solid.AngleDown,
|
||||
Size = new Vector2(20),
|
||||
Scale = new Vector2(1, 0.7f)
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(IScrollingInfo scrollingInfo, DrawableHitObject? drawableObject)
|
||||
{
|
||||
@ -105,8 +109,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
accent.NewValue.Lighten(0.1f),
|
||||
accent.NewValue
|
||||
);
|
||||
|
||||
shadow.Colour = accent.NewValue.Darken(0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
return new ArgonHoldNoteTailPiece();
|
||||
|
||||
case ManiaSkinComponents.HoldNoteHead:
|
||||
return new ArgonHoldNoteHeadPiece();
|
||||
|
||||
case ManiaSkinComponents.Note:
|
||||
return new ArgonNotePiece();
|
||||
|
||||
@ -69,12 +71,23 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
return base.GetDrawableComponent(lookup);
|
||||
}
|
||||
|
||||
private static readonly Color4 colour_special_column = new Color4(169, 106, 255, 255);
|
||||
|
||||
private const int total_colours = 6;
|
||||
|
||||
private static readonly Color4 colour_yellow = new Color4(255, 197, 40, 255);
|
||||
private static readonly Color4 colour_orange = new Color4(252, 109, 1, 255);
|
||||
private static readonly Color4 colour_pink = new Color4(213, 35, 90, 255);
|
||||
private static readonly Color4 colour_purple = new Color4(203, 60, 236, 255);
|
||||
private static readonly Color4 colour_cyan = new Color4(72, 198, 255, 255);
|
||||
private static readonly Color4 colour_green = new Color4(100, 192, 92, 255);
|
||||
|
||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
if (lookup is ManiaSkinConfigurationLookup maniaLookup)
|
||||
{
|
||||
int column = maniaLookup.ColumnIndex ?? 0;
|
||||
var stage = beatmap.GetStageForColumnIndex(column);
|
||||
int columnIndex = maniaLookup.ColumnIndex ?? 0;
|
||||
var stage = beatmap.GetStageForColumnIndex(columnIndex);
|
||||
|
||||
switch (maniaLookup.Lookup)
|
||||
{
|
||||
@ -87,53 +100,12 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.ColumnWidth:
|
||||
return SkinUtils.As<TValue>(new Bindable<float>(
|
||||
stage.IsSpecialColumn(column) ? 120 : 60
|
||||
stage.IsSpecialColumn(columnIndex) ? 120 : 60
|
||||
));
|
||||
|
||||
case LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour:
|
||||
|
||||
Color4 colour;
|
||||
|
||||
const int total_colours = 7;
|
||||
|
||||
if (stage.IsSpecialColumn(column))
|
||||
colour = new Color4(159, 101, 255, 255);
|
||||
else
|
||||
{
|
||||
switch (column % total_colours)
|
||||
{
|
||||
case 0:
|
||||
colour = new Color4(240, 216, 0, 255);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
colour = new Color4(240, 101, 0, 255);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
colour = new Color4(240, 0, 130, 255);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
colour = new Color4(192, 0, 240, 255);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
colour = new Color4(0, 96, 240, 255);
|
||||
break;
|
||||
|
||||
case 5:
|
||||
colour = new Color4(0, 226, 240, 255);
|
||||
break;
|
||||
|
||||
case 6:
|
||||
colour = new Color4(0, 240, 96, 255);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
var colour = getColourForLayout(columnIndex, stage);
|
||||
|
||||
return SkinUtils.As<TValue>(new Bindable<Color4>(colour));
|
||||
}
|
||||
@ -141,5 +113,203 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
||||
|
||||
return base.GetConfig<TLookup, TValue>(lookup);
|
||||
}
|
||||
|
||||
private Color4 getColourForLayout(int columnIndex, StageDefinition stage)
|
||||
{
|
||||
// Account for cases like dual-stage (assume that all stages have the same column count for now).
|
||||
columnIndex %= stage.Columns;
|
||||
|
||||
// For now, these are defined per column count as per https://user-images.githubusercontent.com/50823728/218038463-b450f46c-ef21-4551-b133-f866be59970c.png
|
||||
// See https://github.com/ppy/osu/discussions/21996 for discussion.
|
||||
switch (stage.Columns)
|
||||
{
|
||||
case 1:
|
||||
return colour_yellow;
|
||||
|
||||
case 2:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_green;
|
||||
|
||||
case 1: return colour_cyan;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 3:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_pink;
|
||||
|
||||
case 1: return colour_orange;
|
||||
|
||||
case 2: return colour_yellow;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 4:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_yellow;
|
||||
|
||||
case 1: return colour_orange;
|
||||
|
||||
case 2: return colour_pink;
|
||||
|
||||
case 3: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 5:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_pink;
|
||||
|
||||
case 1: return colour_orange;
|
||||
|
||||
case 2: return colour_yellow;
|
||||
|
||||
case 3: return colour_green;
|
||||
|
||||
case 4: return colour_cyan;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 6:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_pink;
|
||||
|
||||
case 1: return colour_orange;
|
||||
|
||||
case 2: return colour_yellow;
|
||||
|
||||
case 3: return colour_cyan;
|
||||
|
||||
case 4: return colour_purple;
|
||||
|
||||
case 5: return colour_pink;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 7:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_pink;
|
||||
|
||||
case 1: return colour_cyan;
|
||||
|
||||
case 2: return colour_pink;
|
||||
|
||||
case 3: return colour_special_column;
|
||||
|
||||
case 4: return colour_green;
|
||||
|
||||
case 5: return colour_cyan;
|
||||
|
||||
case 6: return colour_green;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 8:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_purple;
|
||||
|
||||
case 1: return colour_pink;
|
||||
|
||||
case 2: return colour_orange;
|
||||
|
||||
case 3: return colour_yellow;
|
||||
|
||||
case 4: return colour_yellow;
|
||||
|
||||
case 5: return colour_orange;
|
||||
|
||||
case 6: return colour_pink;
|
||||
|
||||
case 7: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 9:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_purple;
|
||||
|
||||
case 1: return colour_pink;
|
||||
|
||||
case 2: return colour_orange;
|
||||
|
||||
case 3: return colour_yellow;
|
||||
|
||||
case 4: return colour_special_column;
|
||||
|
||||
case 5: return colour_yellow;
|
||||
|
||||
case 6: return colour_orange;
|
||||
|
||||
case 7: return colour_pink;
|
||||
|
||||
case 8: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
case 10:
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0: return colour_purple;
|
||||
|
||||
case 1: return colour_pink;
|
||||
|
||||
case 2: return colour_orange;
|
||||
|
||||
case 3: return colour_yellow;
|
||||
|
||||
case 4: return colour_cyan;
|
||||
|
||||
case 5: return colour_green;
|
||||
|
||||
case 6: return colour_yellow;
|
||||
|
||||
case 7: return colour_orange;
|
||||
|
||||
case 8: return colour_pink;
|
||||
|
||||
case 9: return colour_purple;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
// fallback for unhandled scenarios
|
||||
|
||||
if (stage.IsSpecialColumn(columnIndex))
|
||||
return colour_special_column;
|
||||
|
||||
switch (columnIndex % total_colours)
|
||||
{
|
||||
case 0: return colour_yellow;
|
||||
|
||||
case 1: return colour_orange;
|
||||
|
||||
case 2: return colour_pink;
|
||||
|
||||
case 3: return colour_purple;
|
||||
|
||||
case 4: return colour_cyan;
|
||||
|
||||
case 5: return colour_green;
|
||||
|
||||
default: throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
@ -34,6 +37,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
private Drawable? lightContainer;
|
||||
|
||||
private Drawable? light;
|
||||
private LegacyNoteBodyStyle? bodyStyle;
|
||||
|
||||
public LegacyBodyPiece()
|
||||
{
|
||||
@ -54,9 +58,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
float lightScale = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteLightScale)?.Value
|
||||
?? 1;
|
||||
|
||||
float minimumColumnWidth = GetColumnSkinConfig<float>(skin, LegacyManiaSkinConfigurationLookups.MinimumColumnWidth)?.Value
|
||||
?? 1;
|
||||
|
||||
// Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length.
|
||||
// This animation is discarded and re-queried with the appropriate frame length afterwards.
|
||||
var tmp = skin.GetAnimation(lightImage, true, false);
|
||||
@ -83,7 +84,14 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
};
|
||||
}
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||
bodyStyle = skin.GetConfig<ManiaSkinConfigurationLookup, LegacyNoteBodyStyle>(new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.NoteBodyStyle))?.Value;
|
||||
|
||||
var wrapMode = bodyStyle == LegacyNoteBodyStyle.Stretch ? WrapMode.ClampToEdge : WrapMode.Repeat;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
|
||||
bodySprite = skin.GetAnimation(imageName, wrapMode, wrapMode, true, true).With(d =>
|
||||
{
|
||||
if (d == null)
|
||||
return;
|
||||
@ -94,16 +102,11 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
d.Anchor = Anchor.TopCentre;
|
||||
d.RelativeSizeAxes = Axes.Both;
|
||||
d.Size = Vector2.One;
|
||||
d.FillMode = FillMode.Stretch;
|
||||
d.Height = minimumColumnWidth / d.DrawWidth * 1.6f; // constant matching stable.
|
||||
// Todo: Wrap?
|
||||
});
|
||||
|
||||
if (bodySprite != null)
|
||||
InternalChild = bodySprite;
|
||||
|
||||
direction.BindTo(scrollingInfo.Direction);
|
||||
isHitting.BindTo(holdNote.IsHitting);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -164,8 +167,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.BottomCentre;
|
||||
bodySprite.Scale = new Vector2(1, -1);
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Anchor = Anchor.BottomCentre; // needs to be flipped due to scale flip in Update.
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -176,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Scale = Vector2.One;
|
||||
bodySprite.Anchor = Anchor.TopCentre;
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -207,6 +210,33 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
base.Update();
|
||||
missFadeTime.Value ??= holdNote.HoldBrokenTime;
|
||||
|
||||
int scaleDirection = (direction.Value == ScrollingDirection.Down ? 1 : -1);
|
||||
|
||||
// here we go...
|
||||
switch (bodyStyle)
|
||||
{
|
||||
case LegacyNoteBodyStyle.Stretch:
|
||||
// this is how lazer works by default. nothing required.
|
||||
if (bodySprite != null)
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection);
|
||||
break;
|
||||
|
||||
default:
|
||||
// this is where things get fucked up.
|
||||
// honestly there's three modes to handle here but they seem really pointless?
|
||||
// let's wait to see if anyone actually uses them in skins.
|
||||
if (bodySprite != null)
|
||||
{
|
||||
var sprite = bodySprite as Sprite ?? bodySprite.ChildrenOfType<Sprite>().Single();
|
||||
|
||||
bodySprite.FillMode = FillMode.Stretch;
|
||||
// i dunno this looks about right??
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -1,17 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameAppDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using UIKit;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||
{
|
||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
31
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs
Normal file
31
osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutopilot.cs
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModAutopilot : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestInstantResume()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModAutopilot(),
|
||||
PassCondition = () => true,
|
||||
Autoplay = false,
|
||||
});
|
||||
|
||||
AddUntilStep("wait for gameplay start", () => Player.LocalUserPlaying.Value);
|
||||
AddStep("press pause", () => InputManager.PressKey(Key.Escape));
|
||||
AddUntilStep("wait until paused", () => Player.GameplayClockContainer.IsPaused.Value);
|
||||
AddStep("release pause", () => InputManager.ReleaseKey(Key.Escape));
|
||||
AddStep("press resume", () => InputManager.PressKey(Key.Escape));
|
||||
AddUntilStep("wait for resume", () => !Player.IsResuming);
|
||||
AddAssert("resumed", () => !Player.GameplayClockContainer.IsPaused.Value);
|
||||
}
|
||||
}
|
||||
}
|
156
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs
Normal file
156
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public partial class TestSceneHitCircleLateFade : OsuTestScene
|
||||
{
|
||||
private float? alphaAtMiss;
|
||||
|
||||
[Test]
|
||||
public void TestHitCircleClassicMod()
|
||||
{
|
||||
AddStep("Create hit circle", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModClassic() };
|
||||
createCircle();
|
||||
});
|
||||
|
||||
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitCircleClassicAndFullHiddenMods()
|
||||
{
|
||||
AddStep("Create hit circle", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModClassic() };
|
||||
createCircle();
|
||||
});
|
||||
|
||||
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitCircleClassicAndApproachCircleOnlyHiddenMods()
|
||||
{
|
||||
AddStep("Create hit circle", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, new OsuModClassic() };
|
||||
createCircle();
|
||||
});
|
||||
|
||||
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitCircleNoMod()
|
||||
{
|
||||
AddStep("Create hit circle", () =>
|
||||
{
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
createCircle();
|
||||
});
|
||||
|
||||
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||
AddAssert("Opaque when missed", () => alphaAtMiss == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderClassicMod()
|
||||
{
|
||||
AddStep("Create slider", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModClassic() };
|
||||
createSlider();
|
||||
});
|
||||
|
||||
AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
|
||||
AddAssert("Head circle transparent when missed", () => alphaAtMiss == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSliderNoMod()
|
||||
{
|
||||
AddStep("Create slider", () =>
|
||||
{
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
createSlider();
|
||||
});
|
||||
|
||||
AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
|
||||
AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1);
|
||||
}
|
||||
|
||||
private void createCircle()
|
||||
{
|
||||
alphaAtMiss = null;
|
||||
|
||||
DrawableHitCircle drawableHitCircle = new DrawableHitCircle(new HitCircle
|
||||
{
|
||||
StartTime = Time.Current + 500,
|
||||
Position = new Vector2(250)
|
||||
});
|
||||
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||
mod.ApplyToDrawableHitObject(drawableHitCircle);
|
||||
|
||||
drawableHitCircle.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
drawableHitCircle.OnNewResult += (_, _) =>
|
||||
{
|
||||
alphaAtMiss = drawableHitCircle.Alpha;
|
||||
};
|
||||
|
||||
Child = drawableHitCircle;
|
||||
}
|
||||
|
||||
private void createSlider()
|
||||
{
|
||||
alphaAtMiss = null;
|
||||
|
||||
DrawableSlider drawableSlider = new DrawableSlider(new Slider
|
||||
{
|
||||
StartTime = Time.Current + 500,
|
||||
Position = new Vector2(250),
|
||||
Path = new SliderPath(PathType.Linear, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(0, 100),
|
||||
})
|
||||
});
|
||||
|
||||
drawableSlider.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
drawableSlider.OnLoadComplete += _ =>
|
||||
{
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||
mod.ApplyToDrawableHitObject(drawableSlider.HeadCircle);
|
||||
|
||||
drawableSlider.HeadCircle.OnNewResult += (_, _) =>
|
||||
{
|
||||
alphaAtMiss = drawableSlider.HeadCircle.Alpha;
|
||||
};
|
||||
};
|
||||
Child = drawableSlider;
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -150,6 +150,42 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
assertKeyCounter(1, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPositionalTrackingAfterLongDistanceTravelled()
|
||||
{
|
||||
// When a single touch has already travelled enough distance on screen, it should remain as the positional
|
||||
// tracking touch until released (unless a direct touch occurs).
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// cover some distance
|
||||
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
// in this case, touch 2 should not become the positional tracking touch.
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// even if the second touch moves on the screen, the original tracking touch is retained.
|
||||
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||
|
||||
checkPosition(TouchSource.Touch1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
||||
{
|
||||
@ -562,8 +598,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void assertKeyCounter(int left, int right)
|
||||
{
|
||||
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
|
||||
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
|
||||
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses.Value, () => Is.EqualTo(left));
|
||||
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses.Value, () => Is.EqualTo(right));
|
||||
}
|
||||
|
||||
private void releaseAllTouches()
|
||||
@ -579,7 +615,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
|
||||
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
|
||||
|
||||
public partial class TestActionKeyCounterTrigger : KeyCounter.InputTrigger, IKeyBindingHandler<OsuAction>
|
||||
public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
public OsuAction Action { get; }
|
||||
|
||||
@ -593,7 +629,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
if (e.Action == Action)
|
||||
{
|
||||
Light();
|
||||
Activate();
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -602,7 +638,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
if (e.Action == Action)
|
||||
Unlight();
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints
|
||||
protected override bool AlwaysShowWhenSelected => true;
|
||||
|
||||
protected override bool ShouldBeAlive => base.ShouldBeAlive
|
||||
|| (ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
||||
|| (DrawableObject is not DrawableSpinner && ShowHitMarkers.Value && editorClock.CurrentTime >= Item.StartTime && editorClock.CurrentTime - Item.GetEndTime() < HitCircleOverlapMarker.FADE_OUT_EXTENSION);
|
||||
|
||||
protected OsuSelectionBlueprint(T hitObject)
|
||||
: base(hitObject)
|
||||
|
@ -11,6 +11,7 @@ using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
private const double flash_duration = 1000;
|
||||
|
||||
private DrawableRuleset<OsuHitObject> ruleset = null!;
|
||||
private DrawableOsuRuleset ruleset = null!;
|
||||
|
||||
protected OsuAction? LastAcceptedAction { get; private set; }
|
||||
|
||||
@ -42,8 +43,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = drawableRuleset;
|
||||
drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
ruleset = (DrawableOsuRuleset)drawableRuleset;
|
||||
ruleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
|
||||
var periods = new List<Period>();
|
||||
|
||||
|
@ -11,6 +11,7 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
@ -55,11 +56,13 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// Grab the input manager to disable the user's cursor, and for future use
|
||||
inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
|
||||
inputManager = ((DrawableOsuRuleset)drawableRuleset).KeyBindingInputManager;
|
||||
inputManager.AllowUserCursorMovement = false;
|
||||
|
||||
// Generate the replay frames the cursor should follow
|
||||
replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap, drawableRuleset.Mods).Generate().Frames.Cast<OsuReplayFrame>().ToList();
|
||||
|
||||
drawableRuleset.UseResumeOverlay = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
@ -11,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
@ -31,6 +33,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
[SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")]
|
||||
public Bindable<bool> AlwaysPlayTailSample { get; } = new BindableBool(true);
|
||||
|
||||
[SettingSource("Fade out hit circles earlier", "Make hit circles fade out into a miss, rather than after it.")]
|
||||
public Bindable<bool> FadeHitCircleEarly { get; } = new Bindable<bool>(true);
|
||||
|
||||
private bool usingHiddenFading;
|
||||
|
||||
public void ApplyToHitObject(HitObject hitObject)
|
||||
{
|
||||
switch (hitObject)
|
||||
@ -51,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
if (ClassicNoteLock.Value)
|
||||
osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy();
|
||||
|
||||
usingHiddenFading = drawableRuleset.Mods.OfType<OsuModHidden>().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false;
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject obj)
|
||||
@ -59,12 +68,32 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
case DrawableSliderHead head:
|
||||
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
|
||||
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
||||
applyEarlyFading(head);
|
||||
break;
|
||||
|
||||
case DrawableSliderTail tail:
|
||||
tail.SamplePlaysOnlyOnHit = !AlwaysPlayTailSample.Value;
|
||||
break;
|
||||
|
||||
case DrawableHitCircle circle:
|
||||
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
||||
applyEarlyFading(circle);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyEarlyFading(DrawableHitCircle circle)
|
||||
{
|
||||
circle.ApplyCustomUpdateState += (o, _) =>
|
||||
{
|
||||
using (o.BeginAbsoluteSequence(o.StateUpdateTime))
|
||||
{
|
||||
double okWindow = o.HitObject.HitWindows.WindowFor(HitResult.Ok);
|
||||
double lateMissFadeTime = o.HitObject.HitWindows.WindowFor(HitResult.Meh) - okWindow;
|
||||
o.Delay(okWindow).FadeOut(lateMissFadeTime);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
|
||||
{
|
||||
// grab the input manager for future use.
|
||||
osuInputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
|
||||
osuInputManager = ((DrawableOsuRuleset)drawableRuleset).KeyBindingInputManager;
|
||||
}
|
||||
|
||||
public void ApplyToPlayer(Player player)
|
||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public partial class DrawableOsuJudgement : DrawableJudgement
|
||||
{
|
||||
protected SkinnableLighting Lighting { get; private set; }
|
||||
internal SkinnableLighting Lighting { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
@ -25,8 +25,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
public override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
||||
|
||||
/// <summary>
|
||||
/// Apply a judgement result.
|
||||
/// </summary>
|
||||
|
@ -10,7 +10,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
public partial class SkinnableLighting : SkinnableSprite
|
||||
internal partial class SkinnableLighting : SkinnableSprite
|
||||
{
|
||||
private DrawableHitObject targetObject;
|
||||
private JudgementResult targetResult;
|
||||
|
@ -71,8 +71,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
|
||||
|
||||
AddNested(i < SpinsRequired
|
||||
? new SpinnerTick { StartTime = startTime }
|
||||
: new SpinnerBonusTick { StartTime = startTime });
|
||||
? new SpinnerTick { StartTime = startTime, SpinnerDuration = Duration }
|
||||
: new SpinnerBonusTick { StartTime = startTime, SpinnerDuration = Duration });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,10 +11,17 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
{
|
||||
public class SpinnerTick : OsuHitObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Duration of the <see cref="Spinner"/> containing this spinner tick.
|
||||
/// </summary>
|
||||
public double SpinnerDuration { get; set; }
|
||||
|
||||
public override Judgement CreateJudgement() => new OsuSpinnerTickJudgement();
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
public override double MaximumJudgementOffset => SpinnerDuration;
|
||||
|
||||
public class OsuSpinnerTickJudgement : OsuJudgement
|
||||
{
|
||||
public override HitResult MaxResult => HitResult.SmallBonus;
|
||||
|
@ -252,13 +252,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
|
||||
renderer.SetBlend(BlendingParameters.Additive);
|
||||
renderer.PushLocalMatrix(DrawInfo.Matrix);
|
||||
|
||||
TextureShader.Bind();
|
||||
BindTextureShader(renderer);
|
||||
|
||||
texture.Bind();
|
||||
|
||||
for (int i = 0; i < points.Count; i++)
|
||||
drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
|
||||
|
||||
TextureShader.Unbind();
|
||||
UnbindTextureShader(renderer);
|
||||
renderer.PopLocalMatrix();
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Framework.Graphics.Rendering.Vertices;
|
||||
using osu.Framework.Graphics.Shaders;
|
||||
using osu.Framework.Graphics.Shaders.Types;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -255,15 +256,23 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
Source.parts.CopyTo(parts, 0);
|
||||
}
|
||||
|
||||
private IUniformBuffer<CursorTrailParameters> cursorTrailParameters;
|
||||
|
||||
public override void Draw(IRenderer renderer)
|
||||
{
|
||||
base.Draw(renderer);
|
||||
|
||||
vertexBatch ??= renderer.CreateQuadBatch<TexturedTrailVertex>(max_sprites, 1);
|
||||
|
||||
cursorTrailParameters ??= renderer.CreateUniformBuffer<CursorTrailParameters>();
|
||||
cursorTrailParameters.Data = cursorTrailParameters.Data with
|
||||
{
|
||||
FadeClock = time,
|
||||
FadeExponent = fadeExponent
|
||||
};
|
||||
|
||||
shader.Bind();
|
||||
shader.GetUniform<float>("g_FadeClock").UpdateValue(ref time);
|
||||
shader.GetUniform<float>("g_FadeExponent").UpdateValue(ref fadeExponent);
|
||||
shader.BindUniformBlock("m_CursorTrailParameters", cursorTrailParameters);
|
||||
|
||||
texture.Bind();
|
||||
|
||||
@ -323,6 +332,15 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
vertexBatch?.Dispose();
|
||||
cursorTrailParameters?.Dispose();
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
private record struct CursorTrailParameters
|
||||
{
|
||||
public UniformFloat FadeClock;
|
||||
public UniformFloat FadeExponent;
|
||||
private readonly UniformPadding8 pad1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
protected new OsuRulesetConfigManager Config => (OsuRulesetConfigManager)base.Config;
|
||||
|
||||
public new OsuInputManager KeyBindingInputManager => (OsuInputManager)base.KeyBindingInputManager;
|
||||
|
||||
public new OsuPlayfield Playfield => (OsuPlayfield)base.Playfield;
|
||||
|
||||
public DrawableOsuRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList<Mod> mods = null)
|
||||
|
@ -22,6 +22,13 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
/// </summary>
|
||||
private readonly List<TrackedTouch> trackedTouches = new List<TrackedTouch>();
|
||||
|
||||
/// <summary>
|
||||
/// The distance (in local pixels) that a touch must move before being considered a permanent tracking touch.
|
||||
/// After this distance is covered, any extra touches on the screen will be considered as button inputs, unless
|
||||
/// a new touch directly interacts with a hit circle.
|
||||
/// </summary>
|
||||
private const float distance_before_position_tracking_lock_in = 100;
|
||||
|
||||
private TrackedTouch? positionTrackingTouch;
|
||||
|
||||
private readonly OsuInputManager osuInputManager;
|
||||
@ -97,26 +104,32 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
return;
|
||||
}
|
||||
|
||||
// ..or if the current position tracking touch was not a direct touch (this one is debatable and may be change in the future, but it's the simplest way to handle)
|
||||
if (!positionTrackingTouch.DirectTouch)
|
||||
// ..or if the current position tracking touch was not a direct touch (and didn't travel across the screen too far).
|
||||
if (!positionTrackingTouch.DirectTouch && positionTrackingTouch.DistanceTravelled < distance_before_position_tracking_lock_in)
|
||||
{
|
||||
positionTrackingTouch = newTouch;
|
||||
return;
|
||||
}
|
||||
|
||||
// In the case the new touch was not used for position tracking, we should also check the previous position tracking touch.
|
||||
// If it was a direct touch and still has its action pressed, that action should be released.
|
||||
// If it still has its action pressed, that action should be released.
|
||||
//
|
||||
// This is done to allow tracking with the initial touch while still having both Left/Right actions available for alternating with two more touches.
|
||||
if (positionTrackingTouch.DirectTouch && positionTrackingTouch.Action is OsuAction directTouchAction)
|
||||
if (positionTrackingTouch.Action is OsuAction touchAction)
|
||||
{
|
||||
osuInputManager.KeyBindingContainer.TriggerReleased(directTouchAction);
|
||||
osuInputManager.KeyBindingContainer.TriggerReleased(touchAction);
|
||||
positionTrackingTouch.Action = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTouchMovement(TouchEvent touchEvent)
|
||||
{
|
||||
if (touchEvent is TouchMoveEvent moveEvent)
|
||||
{
|
||||
var trackedTouch = trackedTouches.Single(t => t.Source == touchEvent.Touch.Source);
|
||||
trackedTouch.DistanceTravelled += moveEvent.Delta.Length;
|
||||
}
|
||||
|
||||
// Movement should only be tracked for the most recent touch.
|
||||
if (touchEvent.Touch.Source != positionTrackingTouch?.Source)
|
||||
return;
|
||||
@ -148,8 +161,16 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
public OsuAction? Action;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the touch was on a hit circle receptor.
|
||||
/// </summary>
|
||||
public readonly bool DirectTouch;
|
||||
|
||||
/// <summary>
|
||||
/// The total distance on screen travelled by this touch (in local pixels).
|
||||
/// </summary>
|
||||
public float DistanceTravelled;
|
||||
|
||||
public TrackedTouch(TouchSource source, OsuAction? action, bool directTouch)
|
||||
{
|
||||
Source = source;
|
||||
|
@ -4,6 +4,7 @@
|
||||
<OutputType>Library</OutputType>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Description>click the circles. to the beat.</Description>
|
||||
<LangVersion>10</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Label="Nuget">
|
||||
|
@ -1,17 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Foundation;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameAppDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using UIKit;
|
||||
using osu.Framework.iOS;
|
||||
using osu.Game.Tests;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||
{
|
||||
@ -11,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
212
osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs
Normal file
212
osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModSingleTap.cs
Normal file
@ -0,0 +1,212 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Taiko.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneTaikoModSingleTap : TaikoModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestInputAlternate() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModSingleTap(),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 300,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 500,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 700,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(120),
|
||||
new TaikoReplayFrame(300, TaikoAction.LeftRim),
|
||||
new TaikoReplayFrame(320),
|
||||
new TaikoReplayFrame(500, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(520),
|
||||
new TaikoReplayFrame(700, TaikoAction.LeftRim),
|
||||
new TaikoReplayFrame(720),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputSameKey() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModSingleTap(),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 300,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 500,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 700,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(120),
|
||||
new TaikoReplayFrame(300, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(320),
|
||||
new TaikoReplayFrame(500, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(520),
|
||||
new TaikoReplayFrame(700, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(720),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 4
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputIntro() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModSingleTap(),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(0, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(20),
|
||||
new TaikoReplayFrame(100, TaikoAction.LeftRim),
|
||||
new TaikoReplayFrame(120),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputStrong() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModSingleTap(),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 300,
|
||||
Type = HitType.Rim,
|
||||
IsStrong = true
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 500,
|
||||
Type = HitType.Rim,
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(120),
|
||||
new TaikoReplayFrame(300, TaikoAction.LeftRim),
|
||||
new TaikoReplayFrame(320),
|
||||
new TaikoReplayFrame(500, TaikoAction.LeftRim),
|
||||
new TaikoReplayFrame(520),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestInputBreaks() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModSingleTap(),
|
||||
Autoplay = false,
|
||||
Beatmap = new Beatmap
|
||||
{
|
||||
Breaks = new List<BreakPeriod>
|
||||
{
|
||||
new BreakPeriod(100, 1600),
|
||||
},
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = 100,
|
||||
Type = HitType.Rim
|
||||
},
|
||||
new Hit
|
||||
{
|
||||
StartTime = 2000,
|
||||
Type = HitType.Rim,
|
||||
},
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(100, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(120),
|
||||
// Press different key after break but before hit object.
|
||||
new TaikoReplayFrame(1900, TaikoAction.LeftRim),
|
||||
new TaikoReplayFrame(1820),
|
||||
// Press original key at second hitobject and ensure it has been hit.
|
||||
new TaikoReplayFrame(2000, TaikoAction.RightRim),
|
||||
new TaikoReplayFrame(2020),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 2
|
||||
});
|
||||
}
|
||||
}
|
@ -73,11 +73,10 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
beatmap.ControlPointInfo.Add(start_time, new TimingControlPoint
|
||||
{
|
||||
BeatLength = beat_length,
|
||||
TimeSignature = new TimeSignature(time_signature_numerator)
|
||||
TimeSignature = new TimeSignature(time_signature_numerator),
|
||||
OmitFirstBarLine = true
|
||||
});
|
||||
|
||||
beatmap.ControlPointInfo.Add(start_time, new EffectControlPoint { OmitFirstBarLine = true });
|
||||
|
||||
var barlines = new BarLineGenerator<BarLine>(beatmap).BarLines;
|
||||
|
||||
AddAssert("first barline ommited", () => barlines.All(b => b.StartTime != start_time));
|
||||
|
@ -72,7 +72,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
converted.ControlPointInfo.Add(hitObject.StartTime, new EffectControlPoint
|
||||
{
|
||||
KiaiMode = currentEffectPoint.KiaiMode,
|
||||
OmitFirstBarLine = currentEffectPoint.OmitFirstBarLine,
|
||||
ScrollSpeed = lastScrollSpeed = nextScrollSpeed,
|
||||
});
|
||||
}
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@ -35,20 +33,11 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
switch (e.Button)
|
||||
{
|
||||
case MouseButton.Left:
|
||||
HitObject.Type = HitType.Centre;
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
if (e.Button != MouseButton.Left)
|
||||
return false;
|
||||
|
||||
case MouseButton.Right:
|
||||
HitObject.Type = HitType.Rim;
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
EndPlacement(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void UpdateTimeAndPosition(SnapResult result)
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
@ -12,5 +14,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
@ -13,5 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
=> new ModReplayData(new TaikoAutoGenerator(beatmap).Generate(), new ModCreatedUser { Username = "mekkadosu!" });
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
||||
}
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
|
||||
{
|
||||
if (drawable is DrawableHit)
|
||||
drawable.SnapJudgementLocation = true;
|
||||
if (drawable is DrawableTaikoHitObject hit)
|
||||
hit.SnapJudgementLocation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
|
||||
@ -8,6 +10,8 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModRelax : ModRelax
|
||||
{
|
||||
public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katu's.";
|
||||
public override LocalisableString Description => @"No ninja-like spinners, demanding drumrolls or unexpected katus.";
|
||||
|
||||
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(TaikoModSingleTap) }).ToArray();
|
||||
}
|
||||
}
|
||||
|
127
osu.Game.Rulesets.Taiko/Mods/TaikoModSingleTap.cs
Normal file
127
osu.Game.Rulesets.Taiko/Mods/TaikoModSingleTap.cs
Normal file
@ -0,0 +1,127 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Utils;
|
||||
using osu.Game.Rulesets.Taiko.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public partial class TaikoModSingleTap : Mod, IApplicableToDrawableRuleset<TaikoHitObject>, IUpdatableByPlayfield
|
||||
{
|
||||
public override string Name => @"Single Tap";
|
||||
public override string Acronym => @"SG";
|
||||
public override LocalisableString Description => @"One key for dons, one key for kats.";
|
||||
|
||||
public override double ScoreMultiplier => 1.0;
|
||||
public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(TaikoModCinema) };
|
||||
public override ModType Type => ModType.Conversion;
|
||||
|
||||
private DrawableTaikoRuleset ruleset = null!;
|
||||
|
||||
private TaikoPlayfield playfield { get; set; } = null!;
|
||||
|
||||
private TaikoAction? lastAcceptedCentreAction { get; set; }
|
||||
private TaikoAction? lastAcceptedRimAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A tracker for periods where single tap should not be enforced (i.e. non-gameplay periods).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is different from <see cref="Player.IsBreakTime"/> in that the periods here end strictly at the first object after the break, rather than the break's end time.
|
||||
/// </remarks>
|
||||
private PeriodTracker nonGameplayPeriods = null!;
|
||||
|
||||
private IFrameStableClock gameplayClock = null!;
|
||||
|
||||
public void ApplyToDrawableRuleset(DrawableRuleset<TaikoHitObject> drawableRuleset)
|
||||
{
|
||||
ruleset = (DrawableTaikoRuleset)drawableRuleset;
|
||||
ruleset.KeyBindingInputManager.Add(new InputInterceptor(this));
|
||||
playfield = (TaikoPlayfield)ruleset.Playfield;
|
||||
|
||||
var periods = new List<Period>();
|
||||
|
||||
if (drawableRuleset.Objects.Any())
|
||||
{
|
||||
periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1));
|
||||
|
||||
foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks)
|
||||
periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1));
|
||||
|
||||
static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh);
|
||||
}
|
||||
|
||||
nonGameplayPeriods = new PeriodTracker(periods);
|
||||
|
||||
gameplayClock = drawableRuleset.FrameStableClock;
|
||||
}
|
||||
|
||||
public void Update(Playfield playfield)
|
||||
{
|
||||
if (!nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) return;
|
||||
|
||||
lastAcceptedCentreAction = null;
|
||||
lastAcceptedRimAction = null;
|
||||
}
|
||||
|
||||
private bool checkCorrectAction(TaikoAction action)
|
||||
{
|
||||
if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime))
|
||||
return true;
|
||||
|
||||
// If next hit object is strong, allow usage of all actions. Strong drumrolls are ignored in this check.
|
||||
if (playfield.HitObjectContainer.AliveObjects.FirstOrDefault(h => h.Result?.HasResult != true)?.HitObject is TaikoStrongableHitObject hitObject
|
||||
&& hitObject.IsStrong
|
||||
&& hitObject is not DrumRoll)
|
||||
return true;
|
||||
|
||||
if ((action == TaikoAction.LeftCentre || action == TaikoAction.RightCentre)
|
||||
&& (lastAcceptedCentreAction == null || lastAcceptedCentreAction == action))
|
||||
{
|
||||
lastAcceptedCentreAction = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((action == TaikoAction.LeftRim || action == TaikoAction.RightRim)
|
||||
&& (lastAcceptedRimAction == null || lastAcceptedRimAction == action))
|
||||
{
|
||||
lastAcceptedRimAction = action;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private partial class InputInterceptor : Component, IKeyBindingHandler<TaikoAction>
|
||||
{
|
||||
private readonly TaikoModSingleTap mod;
|
||||
|
||||
public InputInterceptor(TaikoModSingleTap mod)
|
||||
{
|
||||
this.mod = mod;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<TaikoAction> e)
|
||||
// if the pressed action is incorrect, block it from reaching gameplay.
|
||||
=> !mod.checkCorrectAction(e.Action);
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<TaikoAction> e)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -37,8 +37,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
protected override SkinnableDrawable CreateMainPiece() => new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.DrumRollTick), _ => new TickPiece());
|
||||
|
||||
public override double MaximumJudgementOffset => HitObject.HitWindow;
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
@ -25,6 +25,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
private readonly Container nonProxiedContent;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the location of the hit should be snapped to the hit target before animating.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is how osu-stable worked, but notably is not how TnT works.
|
||||
/// Not snapping results in less visual feedback on hit accuracy.
|
||||
/// </remarks>
|
||||
public bool SnapJudgementLocation { get; set; }
|
||||
|
||||
protected DrawableTaikoHitObject([CanBeNull] TaikoHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
|
@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
|
||||
|
||||
protected override HitWindows CreateHitWindows() => HitWindows.Empty;
|
||||
|
||||
public override double MaximumJudgementOffset => HitWindow;
|
||||
|
||||
protected override StrongNestedHitObject CreateStrongNestedHit(double startTime) => new StrongNestedHit { StartTime = startTime };
|
||||
|
||||
public class StrongNestedHit : StrongNestedHitObject
|
||||
|
@ -158,6 +158,7 @@ namespace osu.Game.Rulesets.Taiko
|
||||
new TaikoModDifficultyAdjust(),
|
||||
new TaikoModClassic(),
|
||||
new TaikoModSwap(),
|
||||
new TaikoModSingleTap(),
|
||||
};
|
||||
|
||||
case ModType.Automation:
|
||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true);
|
||||
|
||||
public new TaikoInputManager KeyBindingInputManager => (TaikoInputManager)base.KeyBindingInputManager;
|
||||
|
||||
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
||||
|
||||
protected override bool UserScrollSpeedAdjustment => false;
|
||||
|
@ -1,16 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using Foundation;
|
||||
using osu.Framework.iOS;
|
||||
|
||||
namespace osu.Game.Tests.iOS
|
||||
{
|
||||
[Register("AppDelegate")]
|
||||
public class AppDelegate : GameAppDelegate
|
||||
{
|
||||
protected override Framework.Game CreateGame() => new OsuTestBrowser();
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using UIKit;
|
||||
using osu.Framework.iOS;
|
||||
|
||||
namespace osu.Game.Tests.iOS
|
||||
{
|
||||
@ -11,7 +9,7 @@ namespace osu.Game.Tests.iOS
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
UIApplication.Main(args, null, typeof(AppDelegate));
|
||||
GameApplication.Main(new OsuTestBrowser());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -181,16 +181,19 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.AreEqual(956, timingPoint.Time);
|
||||
Assert.AreEqual(329.67032967033, timingPoint.BeatLength);
|
||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||
|
||||
timingPoint = controlPoints.TimingPointAt(48428);
|
||||
Assert.AreEqual(956, timingPoint.Time);
|
||||
Assert.AreEqual(329.67032967033d, timingPoint.BeatLength);
|
||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||
|
||||
timingPoint = controlPoints.TimingPointAt(119637);
|
||||
Assert.AreEqual(119637, timingPoint.Time);
|
||||
Assert.AreEqual(659.340659340659, timingPoint.BeatLength);
|
||||
Assert.AreEqual(TimeSignature.SimpleQuadruple, timingPoint.TimeSignature);
|
||||
Assert.IsFalse(timingPoint.OmitFirstBarLine);
|
||||
|
||||
var difficultyPoint = controlPoints.DifficultyPointAt(0);
|
||||
Assert.AreEqual(0, difficultyPoint.Time);
|
||||
@ -222,17 +225,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
var effectPoint = controlPoints.EffectPointAt(0);
|
||||
Assert.AreEqual(0, effectPoint.Time);
|
||||
Assert.IsFalse(effectPoint.KiaiMode);
|
||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||
|
||||
effectPoint = controlPoints.EffectPointAt(53703);
|
||||
Assert.AreEqual(53703, effectPoint.Time);
|
||||
Assert.IsTrue(effectPoint.KiaiMode);
|
||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||
|
||||
effectPoint = controlPoints.EffectPointAt(116637);
|
||||
Assert.AreEqual(95901, effectPoint.Time);
|
||||
Assert.IsFalse(effectPoint.KiaiMode);
|
||||
Assert.IsFalse(effectPoint.OmitFirstBarLine);
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,6 +273,28 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeOmitBarLineEffect()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
|
||||
using (var resStream = TestResources.OpenResource("omit-barline-control-points.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var controlPoints = (LegacyControlPointInfo)decoder.Decode(stream).ControlPointInfo;
|
||||
|
||||
Assert.That(controlPoints.TimingPoints.Count, Is.EqualTo(6));
|
||||
Assert.That(controlPoints.EffectPoints.Count, Is.EqualTo(0));
|
||||
|
||||
Assert.That(controlPoints.TimingPointAt(500).OmitFirstBarLine, Is.False);
|
||||
Assert.That(controlPoints.TimingPointAt(1500).OmitFirstBarLine, Is.True);
|
||||
Assert.That(controlPoints.TimingPointAt(2500).OmitFirstBarLine, Is.False);
|
||||
Assert.That(controlPoints.TimingPointAt(3500).OmitFirstBarLine, Is.False);
|
||||
Assert.That(controlPoints.TimingPointAt(4500).OmitFirstBarLine, Is.False);
|
||||
Assert.That(controlPoints.TimingPointAt(5500).OmitFirstBarLine, Is.True);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTimingPointResetsSpeedMultiplier()
|
||||
{
|
||||
|
125
osu.Game.Tests/Database/LegacyExporterTest.cs
Normal file
125
osu.Game.Tests/Database/LegacyExporterTest.cs
Normal file
@ -0,0 +1,125 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Tests.Database
|
||||
{
|
||||
[TestFixture]
|
||||
public class LegacyExporterTest
|
||||
{
|
||||
private TestLegacyExporter legacyExporter = null!;
|
||||
private TemporaryNativeStorage storage = null!;
|
||||
|
||||
private const string short_filename = "normal file name";
|
||||
|
||||
private const string long_filename =
|
||||
"some file with super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name super long name";
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
storage = new TemporaryNativeStorage("export-storage");
|
||||
legacyExporter = new TestLegacyExporter(storage);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExportFileWithNormalNameTest()
|
||||
{
|
||||
var item = new TestPathInfo(short_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
|
||||
exportItemAndAssert(item, short_filename);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExportFileWithNormalNameMultipleTimesTest()
|
||||
{
|
||||
var item = new TestPathInfo(short_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.LessThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
|
||||
//Export multiple times
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
string expectedFileName = i == 0 ? short_filename : $"{short_filename} ({i})";
|
||||
exportItemAndAssert(item, expectedFileName);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExportFileWithSuperLongNameTest()
|
||||
{
|
||||
int expectedLength = TestLegacyExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||
string expectedName = long_filename.Remove(expectedLength);
|
||||
|
||||
var item = new TestPathInfo(long_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
exportItemAndAssert(item, expectedName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ExportFileWithSuperLongNameMultipleTimesTest()
|
||||
{
|
||||
int expectedLength = TestLegacyExporter.MAX_FILENAME_LENGTH - (legacyExporter.GetExtension().Length);
|
||||
string expectedName = long_filename.Remove(expectedLength);
|
||||
|
||||
var item = new TestPathInfo(long_filename);
|
||||
|
||||
Assert.That(item.Filename.Length, Is.GreaterThan(TestLegacyExporter.MAX_FILENAME_LENGTH));
|
||||
|
||||
//Export multiple times
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
string expectedFilename = i == 0 ? expectedName : $"{expectedName} ({i})";
|
||||
exportItemAndAssert(item, expectedFilename);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportItemAndAssert(IHasNamedFiles item, string expectedName)
|
||||
{
|
||||
Assert.DoesNotThrow(() => legacyExporter.Export(item));
|
||||
Assert.That(storage.Exists($"exports/{expectedName}{legacyExporter.GetExtension()}"), Is.True);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
if (storage.IsNotNull())
|
||||
storage.Dispose();
|
||||
}
|
||||
|
||||
private class TestPathInfo : IHasNamedFiles
|
||||
{
|
||||
public string Filename { get; }
|
||||
|
||||
public IEnumerable<INamedFileUsage> Files { get; } = new List<INamedFileUsage>();
|
||||
|
||||
public TestPathInfo(string filename)
|
||||
{
|
||||
Filename = filename;
|
||||
}
|
||||
|
||||
public override string ToString() => Filename;
|
||||
}
|
||||
|
||||
private class TestLegacyExporter : LegacyExporter<IHasNamedFiles>
|
||||
{
|
||||
public TestLegacyExporter(Storage storage)
|
||||
: base(storage)
|
||||
{
|
||||
}
|
||||
|
||||
public string GetExtension() => FileExtension;
|
||||
|
||||
protected override string FileExtension => ".test";
|
||||
}
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ using osu.Game.Overlays.Settings;
|
||||
namespace osu.Game.Tests.Mods
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class SettingsSourceAttributeTest
|
||||
public partial class SettingSourceAttributeTest
|
||||
{
|
||||
[Test]
|
||||
public void TestOrdering()
|
@ -43,6 +43,18 @@ namespace osu.Game.Tests.NonVisual
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||
|
||||
cpi.Add(1200, new TimingControlPoint { OmitFirstBarLine = true }); // is not redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(3));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(3));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(3));
|
||||
|
||||
cpi.Add(1500, new TimingControlPoint { OmitFirstBarLine = true }); // is not redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(4));
|
||||
Assert.That(cpi.TimingPoints.Count, Is.EqualTo(4));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(4));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -95,12 +107,12 @@ namespace osu.Game.Tests.NonVisual
|
||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(0));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0));
|
||||
|
||||
cpi.Add(1000, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // is not redundant
|
||||
cpi.Add(1400, new EffectControlPoint { KiaiMode = true, OmitFirstBarLine = true }); // same settings, but is not redundant
|
||||
cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant
|
||||
cpi.Add(1400, new EffectControlPoint { KiaiMode = true }); // is redundant
|
||||
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(2));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2));
|
||||
Assert.That(cpi.Groups.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1));
|
||||
Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -7,6 +7,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -93,6 +94,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
|
||||
}
|
||||
|
||||
public override IAdjustableAudioComponent Audio { get; }
|
||||
public override Playfield Playfield { get; }
|
||||
public override Container Overlays { get; }
|
||||
public override Container FrameStableComponents { get; }
|
||||
|
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20230305.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-argon-20230305.osk
Normal file
Binary file not shown.
@ -176,6 +176,7 @@ namespace osu.Game.Tests.Resources
|
||||
CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
||||
},
|
||||
BeatmapInfo = beatmap,
|
||||
BeatmapHash = beatmap.Hash,
|
||||
Ruleset = beatmap.Ruleset,
|
||||
Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() },
|
||||
TotalScore = 2845370,
|
||||
|
27
osu.Game.Tests/Resources/omit-barline-control-points.osu
Normal file
27
osu.Game.Tests/Resources/omit-barline-control-points.osu
Normal file
@ -0,0 +1,27 @@
|
||||
osu file format v14
|
||||
|
||||
[TimingPoints]
|
||||
|
||||
// Uninherited: none, inherited: none
|
||||
0,500,4,2,0,100,1,0
|
||||
0,-50,4,3,0,100,0,0
|
||||
|
||||
// Uninherited: omit, inherited: none
|
||||
1000,500,4,2,0,100,1,8
|
||||
1000,-50,4,3,0,100,0,0
|
||||
|
||||
// Uninherited: none, inherited: omit (should be ignored, inheriting cannot omit)
|
||||
2000,500,4,2,0,100,1,0
|
||||
2000,-50,4,3,0,100,0,8
|
||||
|
||||
// Inherited: none, uninherited: none
|
||||
3000,-50,4,3,0,100,0,0
|
||||
3000,500,4,2,0,100,1,0
|
||||
|
||||
// Inherited: omit, uninherited: none (should be ignored, inheriting cannot omit)
|
||||
4000,-50,4,3,0,100,0,8
|
||||
4000,500,4,2,0,100,1,0
|
||||
|
||||
// Inherited: none, uninherited: omit
|
||||
5000,-50,4,3,0,100,0,0
|
||||
5000,500,4,2,0,100,1,8
|
@ -80,7 +80,7 @@ namespace osu.Game.Tests.Rulesets
|
||||
|
||||
dependencies.CacheAs<TextureStore>(ParentTextureStore = new TestTextureStore(parent.Get<GameHost>().Renderer));
|
||||
dependencies.CacheAs<ISampleStore>(ParentSampleStore = new TestSampleStore());
|
||||
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer));
|
||||
dependencies.CacheAs<ShaderManager>(ParentShaderManager = new TestShaderManager(parent.Get<GameHost>().Renderer, parent.Get<ShaderManager>()));
|
||||
|
||||
return new DrawableRulesetDependencies(new OsuRuleset(), dependencies);
|
||||
}
|
||||
@ -156,12 +156,15 @@ namespace osu.Game.Tests.Rulesets
|
||||
|
||||
private class TestShaderManager : ShaderManager
|
||||
{
|
||||
public TestShaderManager(IRenderer renderer)
|
||||
private readonly ShaderManager parentManager;
|
||||
|
||||
public TestShaderManager(IRenderer renderer, ShaderManager parentManager)
|
||||
: base(renderer, new ResourceStore<byte[]>())
|
||||
{
|
||||
this.parentManager = parentManager;
|
||||
}
|
||||
|
||||
public override byte[] LoadRaw(string name) => null;
|
||||
public override byte[] LoadRaw(string name) => parentManager.LoadRaw(name);
|
||||
|
||||
public bool IsDisposed { get; private set; }
|
||||
|
||||
|
@ -133,6 +133,25 @@ namespace osu.Game.Tests.Skins.IO
|
||||
assertImportedOnce(import1, import2);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestImportExportedNonAsciiSkinFilename() => runSkinTest(async osu =>
|
||||
{
|
||||
MemoryStream exportStream = new MemoryStream();
|
||||
|
||||
var import1 = await loadSkinIntoOsu(osu, new ImportTask(createOskWithIni("name 『1』", "author 1"), "custom.osk"));
|
||||
assertCorrectMetadata(import1, "name 『1』 [custom]", "author 1", osu);
|
||||
|
||||
import1.PerformRead(s =>
|
||||
{
|
||||
new LegacySkinExporter(osu.Dependencies.Get<Storage>()).ExportModelTo(s, exportStream);
|
||||
});
|
||||
|
||||
string exportFilename = import1.GetDisplayString().GetValidFilename();
|
||||
|
||||
var import2 = await loadSkinIntoOsu(osu, new ImportTask(exportStream, $"{exportFilename}.osk"));
|
||||
assertCorrectMetadata(import2, "name 『1』 [custom]", "author 1", osu);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public Task TestSameMetadataNameSameFolderName([Values] bool batchImport) => runSkinTest(async osu =>
|
||||
{
|
||||
|
@ -48,7 +48,9 @@ namespace osu.Game.Tests.Skins
|
||||
// Covers BPM counter.
|
||||
"Archives/modified-default-20221205.osk",
|
||||
// Covers judgement counter.
|
||||
"Archives/modified-default-20230117.osk"
|
||||
"Archives/modified-default-20230117.osk",
|
||||
// Covers player avatar and flag.
|
||||
"Archives/modified-argon-20230305.osk",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@ -66,15 +68,15 @@ namespace osu.Game.Tests.Skins
|
||||
{
|
||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||
|
||||
foreach (var target in skin.DrawableComponentInfo)
|
||||
foreach (var target in skin.LayoutInfos)
|
||||
{
|
||||
foreach (var info in target.Value)
|
||||
foreach (var info in target.Value.AllDrawables)
|
||||
instantiatedTypes.Add(info.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var editableTypes = SkinnableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true);
|
||||
var editableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISerialisableDrawable)?.IsEditable == true);
|
||||
|
||||
Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes));
|
||||
}
|
||||
@ -87,8 +89,8 @@ namespace osu.Game.Tests.Skins
|
||||
{
|
||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||
|
||||
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
|
||||
Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(9));
|
||||
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9));
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,11 +102,11 @@ namespace osu.Game.Tests.Skins
|
||||
{
|
||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||
|
||||
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
|
||||
Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(6));
|
||||
Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.SongSelect], Has.Length.EqualTo(1));
|
||||
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6));
|
||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1));
|
||||
|
||||
var skinnableInfo = skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.SongSelect].First();
|
||||
var skinnableInfo = skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.First();
|
||||
|
||||
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
|
||||
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
|
||||
@ -115,10 +117,10 @@ namespace osu.Game.Tests.Skins
|
||||
using (var storage = new ZipArchiveReader(stream))
|
||||
{
|
||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||
Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents], Has.Length.EqualTo(8));
|
||||
Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
|
||||
Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
|
||||
Assert.That(skin.DrawableComponentInfo[GlobalSkinComponentLookup.LookupType.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
|
||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8));
|
||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
|
||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
|
||||
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
@ -51,9 +49,11 @@ namespace osu.Game.Tests.Testing
|
||||
[Test]
|
||||
public void TestRetrieveShader()
|
||||
{
|
||||
AddAssert("ruleset shaders retrieved", () =>
|
||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs") != null &&
|
||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs") != null);
|
||||
AddStep("ruleset shaders retrieved without error", () =>
|
||||
{
|
||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestVertex.vs");
|
||||
Dependencies.Get<ShaderManager>().LoadRaw(@"sh_TestFragment.fs");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -76,12 +76,12 @@ namespace osu.Game.Tests.Testing
|
||||
}
|
||||
|
||||
public override IResourceStore<byte[]> CreateResourceStore() => new NamespacedResourceStore<byte[]>(TestResources.GetStore(), @"Resources");
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager();
|
||||
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new TestRulesetConfigManager();
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => Array.Empty<Mod>();
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => null;
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null;
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null;
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod>? mods = null) => null!;
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null!;
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) => null!;
|
||||
}
|
||||
|
||||
private class TestRulesetConfigManager : IRulesetConfigManager
|
||||
|
@ -9,6 +9,7 @@ using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Rendering;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
@ -97,15 +98,29 @@ namespace osu.Game.Tests.Visual.Background
|
||||
texelSize = Source.texelSize;
|
||||
}
|
||||
|
||||
public override void Draw(IRenderer renderer)
|
||||
{
|
||||
TextureShader.GetUniform<float>("thickness").UpdateValue(ref thickness);
|
||||
TextureShader.GetUniform<float>("texelSize").UpdateValue(ref texelSize);
|
||||
private IUniformBuffer<TriangleBorderData>? borderDataBuffer;
|
||||
|
||||
base.Draw(renderer);
|
||||
protected override void BindUniformResources(IShader shader, IRenderer renderer)
|
||||
{
|
||||
base.BindUniformResources(shader, renderer);
|
||||
|
||||
borderDataBuffer ??= renderer.CreateUniformBuffer<TriangleBorderData>();
|
||||
borderDataBuffer.Data = borderDataBuffer.Data with
|
||||
{
|
||||
Thickness = thickness,
|
||||
TexelSize = texelSize
|
||||
};
|
||||
|
||||
shader.BindUniformBlock("m_BorderData", borderDataBuffer);
|
||||
}
|
||||
|
||||
protected override bool CanDrawOpaqueInterior => false;
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
borderDataBuffer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2));
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2));
|
||||
|
||||
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
||||
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
|
||||
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value);
|
||||
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
||||
|
||||
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||
|
||||
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
||||
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
||||
|
@ -1,26 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
|
||||
@ -28,10 +25,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
|
||||
{
|
||||
private ISkin currentBeatmapSkin;
|
||||
private ISkin currentBeatmapSkin = null!;
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skinManager { get; set; }
|
||||
private SkinManager skinManager { get; set; } = null!;
|
||||
|
||||
protected override bool HasCustomSteps => true;
|
||||
|
||||
@ -39,8 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||
{
|
||||
CreateSkinTest(TrianglesSkin.CreateInfo(), () => new LegacyBeatmapSkin(new BeatmapInfo(), null));
|
||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
||||
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||
}
|
||||
|
||||
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func<ISkin> getBeatmapSkin)
|
||||
@ -55,17 +52,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
}
|
||||
|
||||
protected bool AssertComponentsFromExpectedSource(GlobalSkinComponentLookup.LookupType target, ISkin expectedSource)
|
||||
protected bool AssertComponentsFromExpectedSource(SkinComponentsContainerLookup.TargetArea target, ISkin expectedSource)
|
||||
{
|
||||
var actualComponentsContainer = Player.ChildrenOfType<SkinnableTargetContainer>().First(s => s.Target == target)
|
||||
.ChildrenOfType<SkinnableTargetComponentsContainer>().SingleOrDefault();
|
||||
var targetContainer = Player.ChildrenOfType<SkinComponentsContainer>().First(s => s.Lookup.Target == target);
|
||||
var actualComponentsContainer = targetContainer.ChildrenOfType<Container>().SingleOrDefault(c => c.Parent == targetContainer);
|
||||
|
||||
if (actualComponentsContainer == null)
|
||||
return false;
|
||||
|
||||
var actualInfo = actualComponentsContainer.CreateSkinnableInfo();
|
||||
var actualInfo = actualComponentsContainer.CreateSerialisedInfo();
|
||||
|
||||
var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new GlobalSkinComponentLookup(target));
|
||||
var expectedComponentsContainer = expectedSource.GetDrawableComponent(new SkinComponentsContainerLookup(target)) as Container;
|
||||
if (expectedComponentsContainer == null)
|
||||
return false;
|
||||
|
||||
@ -86,23 +83,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
Add(expectedComponentsAdjustmentContainer);
|
||||
expectedComponentsAdjustmentContainer.UpdateSubTree();
|
||||
var expectedInfo = expectedComponentsContainer.CreateSkinnableInfo();
|
||||
var expectedInfo = expectedComponentsContainer.CreateSerialisedInfo();
|
||||
Remove(expectedComponentsAdjustmentContainer, true);
|
||||
|
||||
return almostEqual(actualInfo, expectedInfo);
|
||||
}
|
||||
|
||||
private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) =>
|
||||
private static bool almostEqual(SerialisedDrawableInfo drawableInfo, SerialisedDrawableInfo? other) =>
|
||||
other != null
|
||||
&& info.Type == other.Type
|
||||
&& info.Anchor == other.Anchor
|
||||
&& info.Origin == other.Origin
|
||||
&& Precision.AlmostEquals(info.Position, other.Position, 1)
|
||||
&& Precision.AlmostEquals(info.Scale, other.Scale)
|
||||
&& Precision.AlmostEquals(info.Rotation, other.Rotation)
|
||||
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>(almostEqual));
|
||||
&& drawableInfo.Type == other.Type
|
||||
&& drawableInfo.Anchor == other.Anchor
|
||||
&& drawableInfo.Origin == other.Origin
|
||||
&& Precision.AlmostEquals(drawableInfo.Position, other.Position, 1)
|
||||
&& Precision.AlmostEquals(drawableInfo.Scale, other.Scale)
|
||||
&& Precision.AlmostEquals(drawableInfo.Rotation, other.Rotation)
|
||||
&& drawableInfo.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SerialisedDrawableInfo>(almostEqual));
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
|
||||
@ -111,7 +108,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private readonly ISkin beatmapSkin;
|
||||
|
||||
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
|
||||
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
|
||||
: base(beatmap, storyboard, referenceClock, audio)
|
||||
{
|
||||
this.beatmapSkin = beatmapSkin;
|
||||
|
@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
addSeekStep(3000);
|
||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Select(kc => kc.CountPresses).Sum() == 15);
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15);
|
||||
AddStep("clear results", () => Player.Results.Clear());
|
||||
addSeekStep(0);
|
||||
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
||||
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0));
|
||||
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
||||
}
|
||||
|
||||
|
@ -1,50 +1,67 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Storyboards;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneGameplaySampleTriggerSource : PlayerTestScene
|
||||
{
|
||||
private TestGameplaySampleTriggerSource sampleTriggerSource;
|
||||
private TestGameplaySampleTriggerSource sampleTriggerSource = null!;
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
private Beatmap beatmap;
|
||||
private Beatmap beatmap = null!;
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audio { get; set; } = null!;
|
||||
|
||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null)
|
||||
=> new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audio);
|
||||
|
||||
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
ControlPointInfo controlPointInfo = new LegacyControlPointInfo();
|
||||
|
||||
beatmap = new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty { CircleSize = 6, SliderMultiplier = 3 },
|
||||
Ruleset = ruleset
|
||||
}
|
||||
},
|
||||
ControlPointInfo = controlPointInfo
|
||||
};
|
||||
|
||||
const double start_offset = 8000;
|
||||
const double spacing = 2000;
|
||||
|
||||
// intentionally start objects a bit late so we can test the case of no alive objects.
|
||||
double t = start_offset;
|
||||
beatmap.HitObjects.AddRange(new[]
|
||||
|
||||
beatmap.HitObjects.AddRange(new HitObject[]
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
// intentionally start objects a bit late so we can test the case of no alive objects.
|
||||
StartTime = t += spacing,
|
||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
},
|
||||
@ -61,12 +78,24 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = t + spacing,
|
||||
StartTime = t += spacing,
|
||||
},
|
||||
new Slider
|
||||
{
|
||||
StartTime = t += spacing,
|
||||
Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, Vector2.UnitY * 200 }),
|
||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) },
|
||||
SampleControlPoint = new SampleControlPoint { SampleBank = "soft" },
|
||||
},
|
||||
});
|
||||
|
||||
// Add a change in volume halfway through final slider.
|
||||
controlPointInfo.Add(t, new SampleControlPoint
|
||||
{
|
||||
SampleBank = "normal",
|
||||
SampleVolume = 20,
|
||||
});
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
@ -80,42 +109,88 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestCorrectHitObject()
|
||||
{
|
||||
HitObjectLifetimeEntry nextObjectEntry = null;
|
||||
waitForAliveObjectIndex(null);
|
||||
checkValidObjectIndex(0);
|
||||
|
||||
AddAssert("no alive objects", () => getNextAliveObject() == null);
|
||||
seekBeforeIndex(0);
|
||||
waitForAliveObjectIndex(0);
|
||||
checkValidObjectIndex(0);
|
||||
|
||||
AddAssert("check initially correct object", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[0]);
|
||||
AddAssert("first object not hit", () => getNextAliveObject()?.Entry?.Result?.HasResult != true);
|
||||
|
||||
AddUntilStep("get next object", () =>
|
||||
AddStep("hit first object", () =>
|
||||
{
|
||||
var nextDrawableObject = getNextAliveObject();
|
||||
var next = getNextAliveObject();
|
||||
|
||||
if (nextDrawableObject != null)
|
||||
if (next != null)
|
||||
{
|
||||
nextObjectEntry = nextDrawableObject.Entry;
|
||||
InputManager.MoveMouseTo(nextDrawableObject.ScreenSpaceDrawQuad.Centre);
|
||||
return true;
|
||||
Debug.Assert(next.Entry?.Result?.HasResult != true);
|
||||
|
||||
InputManager.MoveMouseTo(next.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
AddUntilStep("hit first hitobject", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Left);
|
||||
return nextObjectEntry.Result?.HasResult == true;
|
||||
});
|
||||
AddAssert("first object hit", () => getNextAliveObject()?.Entry?.Result?.HasResult == true);
|
||||
|
||||
AddAssert("check correct object after hit", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[1]);
|
||||
checkValidObjectIndex(1);
|
||||
|
||||
AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[2]);
|
||||
AddUntilStep("check correct object after miss", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]);
|
||||
// Still object 1 as it's not hit yet.
|
||||
seekBeforeIndex(1);
|
||||
waitForAliveObjectIndex(1);
|
||||
checkValidObjectIndex(1);
|
||||
|
||||
AddUntilStep("no alive objects", () => getNextAliveObject() == null);
|
||||
AddAssert("check correct object after none alive", () => sampleTriggerSource.GetMostValidObject() == beatmap.HitObjects[3]);
|
||||
seekBeforeIndex(2);
|
||||
waitForAliveObjectIndex(2);
|
||||
checkValidObjectIndex(2);
|
||||
|
||||
seekBeforeIndex(3);
|
||||
waitForAliveObjectIndex(3);
|
||||
checkValidObjectIndex(3);
|
||||
|
||||
seekBeforeIndex(4);
|
||||
waitForAliveObjectIndex(4);
|
||||
|
||||
// Even before the object, we should prefer the first nested object's sample.
|
||||
// This is because the (parent) object will only play its sample at the final EndTime.
|
||||
AddAssert("check valid object is slider's first nested", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[4].NestedHitObjects.First()));
|
||||
|
||||
AddStep("seek to just before slider ends", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects[4].GetEndTime() - 100));
|
||||
waitForCatchUp();
|
||||
AddUntilStep("wait until valid object is slider's last nested", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[4].NestedHitObjects.Last()));
|
||||
|
||||
// After we get far enough away, the samples of the object itself should be used, not any nested object.
|
||||
AddStep("seek to further after slider", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects[4].GetEndTime() + 1000));
|
||||
waitForCatchUp();
|
||||
AddUntilStep("wait until valid object is slider itself", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[4]));
|
||||
|
||||
AddStep("Seek into future", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects.Last().GetEndTime() + 10000));
|
||||
waitForCatchUp();
|
||||
waitForAliveObjectIndex(null);
|
||||
checkValidObjectIndex(4);
|
||||
}
|
||||
|
||||
private DrawableHitObject getNextAliveObject() =>
|
||||
private void seekBeforeIndex(int index)
|
||||
{
|
||||
AddStep($"seek to just before object {index}", () => Player.GameplayClockContainer.Seek(beatmap.HitObjects[index].StartTime - 100));
|
||||
waitForCatchUp();
|
||||
}
|
||||
|
||||
private void waitForCatchUp() =>
|
||||
AddUntilStep("wait for frame stable clock to catch up", () => Precision.AlmostEquals(Player.GameplayClockContainer.CurrentTime, Player.DrawableRuleset.FrameStableClock.CurrentTime));
|
||||
|
||||
private void waitForAliveObjectIndex(int? index)
|
||||
{
|
||||
if (index == null)
|
||||
AddUntilStep("wait for no alive objects", getNextAliveObject, () => Is.Null);
|
||||
else
|
||||
AddUntilStep($"wait for next alive to be {index}", () => getNextAliveObject()?.HitObject, () => Is.EqualTo(beatmap.HitObjects[index.Value]));
|
||||
}
|
||||
|
||||
private void checkValidObjectIndex(int index) =>
|
||||
AddAssert($"check valid object is {index}", () => sampleTriggerSource.GetMostValidObject(), () => Is.EqualTo(beatmap.HitObjects[index]));
|
||||
|
||||
private DrawableHitObject? getNextAliveObject() =>
|
||||
Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.FirstOrDefault();
|
||||
|
||||
[Test]
|
||||
|
@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
|
||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -235,8 +235,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
createNew();
|
||||
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().Alpha == 0);
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().All(c => c.ComponentsLoaded));
|
||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
||||
|
||||
AddStep("bind on update", () =>
|
||||
{
|
||||
@ -254,10 +254,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
createNew();
|
||||
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().Alpha == 0);
|
||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
||||
|
||||
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().Reload());
|
||||
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinnableTargetContainer>().Single().ComponentsLoaded);
|
||||
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Reload());
|
||||
AddUntilStep("skinnable components loaded", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().ComponentsLoaded);
|
||||
}
|
||||
|
||||
private void createNew(Action<HUDOverlay>? action = null)
|
||||
@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(hudOverlay.KeyCounter.CreateKeyCounter(new KeyCounterKeyboardTrigger(Key.Space)));
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
|
||||
scoreProcessor.Combo.Value = 1;
|
||||
|
||||
|
@ -9,6 +9,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -281,6 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
remove => throw new InvalidOperationException($"{nameof(RevertResult)} operations not supported in test context");
|
||||
}
|
||||
|
||||
public override IAdjustableAudioComponent Audio { get; }
|
||||
public override Playfield Playfield { get; }
|
||||
public override Container Overlays { get; }
|
||||
public override Container FrameStableComponents { get; }
|
||||
|
@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -18,48 +19,46 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public TestSceneKeyCounter()
|
||||
{
|
||||
DefaultKeyCounter testCounter;
|
||||
KeyCounterDisplay kc;
|
||||
KeyCounterDisplay argonKc;
|
||||
|
||||
Children = new Drawable[]
|
||||
KeyCounterDisplay kc = new DefaultKeyCounterDisplay
|
||||
{
|
||||
kc = new DefaultKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Position = new Vector2(0, -50),
|
||||
Children = new[]
|
||||
{
|
||||
testCounter = new DefaultKeyCounter(new KeyCounterKeyboardTrigger(Key.X)),
|
||||
new DefaultKeyCounter(new KeyCounterKeyboardTrigger(Key.X)),
|
||||
new DefaultKeyCounter(new KeyCounterMouseTrigger(MouseButton.Left)),
|
||||
new DefaultKeyCounter(new KeyCounterMouseTrigger(MouseButton.Right)),
|
||||
},
|
||||
},
|
||||
argonKc = new ArgonKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Position = new Vector2(0, 50),
|
||||
Children = new[]
|
||||
{
|
||||
new ArgonKeyCounter(new KeyCounterKeyboardTrigger(Key.X)),
|
||||
new ArgonKeyCounter(new KeyCounterKeyboardTrigger(Key.X)),
|
||||
new ArgonKeyCounter(new KeyCounterMouseTrigger(MouseButton.Left)),
|
||||
new ArgonKeyCounter(new KeyCounterMouseTrigger(MouseButton.Right)),
|
||||
},
|
||||
}
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Position = new Vector2(0, 72.7f)
|
||||
};
|
||||
|
||||
KeyCounterDisplay argonKc = new ArgonKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Position = new Vector2(0, -72.7f)
|
||||
};
|
||||
|
||||
kc.AddRange(new InputTrigger[]
|
||||
{
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||
});
|
||||
|
||||
argonKc.AddRange(new InputTrigger[]
|
||||
{
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||
});
|
||||
|
||||
var testCounter = (DefaultKeyCounter)kc.Counters.First();
|
||||
|
||||
AddStep("Add random", () =>
|
||||
{
|
||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||
kc.Add(kc.CreateKeyCounter(new KeyCounterKeyboardTrigger(key)));
|
||||
argonKc.Add(argonKc.CreateKeyCounter(new KeyCounterKeyboardTrigger(key)));
|
||||
kc.Add(new KeyCounterKeyboardTrigger(key));
|
||||
argonKc.Add(new KeyCounterKeyboardTrigger(key));
|
||||
});
|
||||
|
||||
Key testKey = ((KeyCounterKeyboardTrigger)kc.Children.First().Trigger).Key;
|
||||
Key testKey = ((KeyCounterKeyboardTrigger)kc.Counters.First().Trigger).Key;
|
||||
|
||||
void addPressKeyStep()
|
||||
{
|
||||
@ -67,12 +66,15 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
|
||||
AddStep("Disable counting", () => testCounter.IsCounting = false);
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
||||
AddStep("Disable counting", () => testCounter.IsCounting.Value = false);
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
|
||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
||||
|
||||
Add(kc);
|
||||
Add(argonKc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs
Normal file
24
osu.Game.Tests/Visual/Gameplay/TestSceneLetterboxOverlay.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Screens.Play.Break;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneLetterboxOverlay : OsuTestScene
|
||||
{
|
||||
public TestSceneLetterboxOverlay()
|
||||
{
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new LetterboxOverlay()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,6 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
@ -37,6 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private TestDrawablePoolingRuleset drawableRuleset;
|
||||
|
||||
private TestPlayfield playfield => (TestPlayfield)drawableRuleset.Playfield;
|
||||
|
||||
[Test]
|
||||
public void TestReusedWithHitObjectsSpacedFarApart()
|
||||
{
|
||||
@ -133,29 +134,49 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("no DHOs shown", () => !this.ChildrenOfType<DrawableTestHitObject>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRevertResult()
|
||||
{
|
||||
ManualClock clock = null;
|
||||
Beatmap beatmap;
|
||||
|
||||
createTest(beatmap = new Beatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new TestHitObject { StartTime = 0 },
|
||||
new TestHitObject { StartTime = 500 },
|
||||
new TestHitObject { StartTime = 1000 },
|
||||
}
|
||||
}, 10, () => new FramedClock(clock = new ManualClock()));
|
||||
|
||||
AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100);
|
||||
AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("rewind to middle", () => clock.CurrentTime = beatmap.HitObjects[1].StartTime - 100);
|
||||
AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(1));
|
||||
|
||||
AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100);
|
||||
AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("disable frame stability", () => drawableRuleset.FrameStablePlayback = false);
|
||||
AddStep("instant seek to start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime - 100);
|
||||
AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyHitResultOnKilled()
|
||||
{
|
||||
ManualClock clock = null;
|
||||
bool anyJudged = false;
|
||||
|
||||
void onNewResult(JudgementResult _) => anyJudged = true;
|
||||
|
||||
var beatmap = new Beatmap();
|
||||
beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 });
|
||||
|
||||
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
||||
|
||||
AddStep("subscribe to new result", () =>
|
||||
{
|
||||
anyJudged = false;
|
||||
drawableRuleset.NewResult += onNewResult;
|
||||
});
|
||||
AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000);
|
||||
|
||||
AddAssert("object judged", () => anyJudged);
|
||||
|
||||
AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
|
||||
AddAssert("object judged", () => playfield.JudgedObjects.Count == 1);
|
||||
}
|
||||
|
||||
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null)
|
||||
@ -212,12 +233,24 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private partial class TestPlayfield : Playfield
|
||||
{
|
||||
public readonly HashSet<HitObject> JudgedObjects = new HashSet<HitObject>();
|
||||
|
||||
private readonly int poolSize;
|
||||
|
||||
public TestPlayfield(int poolSize)
|
||||
{
|
||||
this.poolSize = poolSize;
|
||||
AddInternal(HitObjectContainer);
|
||||
NewResult += (_, r) =>
|
||||
{
|
||||
Assert.That(JudgedObjects, Has.No.Member(r.HitObject));
|
||||
JudgedObjects.Add(r.HitObject);
|
||||
};
|
||||
RevertResult += r =>
|
||||
{
|
||||
Assert.That(JudgedObjects, Has.Member(r.HitObject));
|
||||
JudgedObjects.Remove(r.HitObject);
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user