mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 09:27:29 +08:00
Merge branch 'master' into keep-shared-settings-ruleset-change
This commit is contained in:
commit
27f81288ef
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.418.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>
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,6 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
private void load()
|
||||
{
|
||||
// todo: enable distance spacing once catch supports applying it to its existing distance snap grid implementation.
|
||||
RightSideToolboxContainer.Alpha = 0;
|
||||
DistanceSpacingMultiplier.Disabled = true;
|
||||
|
||||
LayerBelowRuleset.Add(new PlayfieldBorder
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public DifficultyBindable CircleSize { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 1,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.CircleSize,
|
||||
@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
public DifficultyBindable ApproachRate { get; } = new DifficultyBindable
|
||||
{
|
||||
Precision = 0.1f,
|
||||
MinValue = 1,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
ExtendedMaxValue = 11,
|
||||
ReadCurrentFromDifficulty = diff => diff.ApproachRate,
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
public class CatchModNightcore : ModNightcore<CatchHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -136,6 +136,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
Origin = Anchor.TopCentre;
|
||||
|
||||
Size = new Vector2(BASE_SIZE);
|
||||
|
||||
if (difficulty != null)
|
||||
Scale = calculateScale(difficulty);
|
||||
|
||||
@ -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) ||
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.5;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Mania.Mods
|
||||
{
|
||||
public class ManiaModNightcore : ModNightcore<ManiaHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => 1;
|
||||
}
|
||||
}
|
||||
|
@ -236,6 +236,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
};
|
||||
|
||||
// Position and resize the body to lie half-way under the head and the tail notes.
|
||||
// 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;
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
{
|
||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||
|
||||
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||
protected internal DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||
|
||||
public DrawableHoldNoteTail()
|
||||
: this(null)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,8 +167,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
{
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.BottomCentre;
|
||||
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y) * -1);
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Anchor = Anchor.BottomCentre; // needs to be flipped due to scale flip in Update.
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
if (bodySprite != null)
|
||||
{
|
||||
bodySprite.Origin = Anchor.TopCentre;
|
||||
bodySprite.Scale = new Vector2(bodySprite.Scale.X, Math.Abs(bodySprite.Scale.Y));
|
||||
bodySprite.Anchor = Anchor.TopCentre;
|
||||
}
|
||||
|
||||
if (light != null)
|
||||
@ -211,11 +211,15 @@ 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:
|
||||
@ -228,7 +232,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy
|
||||
|
||||
bodySprite.FillMode = FillMode.Stretch;
|
||||
// i dunno this looks about right??
|
||||
bodySprite.Scale = new Vector2(1, 32800 / sprite.DrawHeight);
|
||||
bodySprite.Scale = new Vector2(1, scaleDirection * 32800 / sprite.DrawHeight);
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
private TestActionKeyCounter leftKeyCounter = null!;
|
||||
private DefaultKeyCounter leftKeyCounter = null!;
|
||||
|
||||
private TestActionKeyCounter rightKeyCounter = null!;
|
||||
private DefaultKeyCounter rightKeyCounter = null!;
|
||||
|
||||
private OsuInputManager osuInputManager = null!;
|
||||
|
||||
@ -59,14 +59,14 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
|
||||
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
Depth = float.MinValue,
|
||||
X = -100,
|
||||
},
|
||||
rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
|
||||
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@ -150,6 +150,42 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
assertKeyCounter(1, 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPositionalTrackingAfterLongDistanceTravelled()
|
||||
{
|
||||
// When a single touch has already travelled enough distance on screen, it should remain as the positional
|
||||
// tracking touch until released (unless a direct touch occurs).
|
||||
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
assertKeyCounter(1, 0);
|
||||
checkPressed(OsuAction.LeftButton);
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// cover some distance
|
||||
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||
beginTouch(TouchSource.Touch1, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch1, new Vector2(9999));
|
||||
beginTouch(TouchSource.Touch1);
|
||||
|
||||
beginTouch(TouchSource.Touch2);
|
||||
|
||||
assertKeyCounter(1, 1);
|
||||
checkNotPressed(OsuAction.LeftButton);
|
||||
checkPressed(OsuAction.RightButton);
|
||||
// in this case, touch 2 should not become the positional tracking touch.
|
||||
checkPosition(TouchSource.Touch1);
|
||||
|
||||
// even if the second touch moves on the screen, the original tracking touch is retained.
|
||||
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||
beginTouch(TouchSource.Touch2, new Vector2(0));
|
||||
beginTouch(TouchSource.Touch2, new Vector2(9999));
|
||||
|
||||
checkPosition(TouchSource.Touch1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPositionalInputUpdatesOnlyFromMostRecentTouch()
|
||||
{
|
||||
@ -562,8 +598,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private void assertKeyCounter(int left, int right)
|
||||
{
|
||||
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
|
||||
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
|
||||
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses.Value, () => Is.EqualTo(left));
|
||||
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses.Value, () => Is.EqualTo(right));
|
||||
}
|
||||
|
||||
private void releaseAllTouches()
|
||||
@ -579,11 +615,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
|
||||
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
|
||||
|
||||
public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler<OsuAction>
|
||||
public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler<OsuAction>
|
||||
{
|
||||
public OsuAction Action { get; }
|
||||
|
||||
public TestActionKeyCounter(OsuAction action)
|
||||
public TestActionKeyCounterTrigger(OsuAction action)
|
||||
: base(action.ToString())
|
||||
{
|
||||
Action = action;
|
||||
@ -593,8 +629,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
if (e.Action == Action)
|
||||
{
|
||||
IsLit = true;
|
||||
Increment();
|
||||
Activate();
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -602,7 +637,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||
{
|
||||
if (e.Action == Action) IsLit = false;
|
||||
if (e.Action == Action)
|
||||
Deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,7 +56,7 @@ 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
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModNightcore : ModNightcore<OsuHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModDaycore : ModDaycore
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModDoubleTime : ModDoubleTime
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModHalfTime : ModHalfTime
|
||||
{
|
||||
public override double ScoreMultiplier => 0.3;
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
public class TaikoModNightcore : ModNightcore<TaikoHitObject>
|
||||
{
|
||||
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1;
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
@ -161,6 +160,51 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeVideoWithLowercaseExtension()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
|
||||
using (var resStream = TestResources.OpenResource("video-with-lowercase-extension.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var beatmap = decoder.Decode(stream);
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeVideoWithUppercaseExtension()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
|
||||
using (var resStream = TestResources.OpenResource("video-with-uppercase-extension.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var beatmap = decoder.Decode(stream);
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeImageSpecifiedAsVideo()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder { ApplyOffsets = false };
|
||||
|
||||
using (var resStream = TestResources.OpenResource("image-specified-as-video.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var beatmap = decoder.Decode(stream);
|
||||
var metadata = beatmap.Metadata;
|
||||
|
||||
Assert.AreEqual("BG.jpg", metadata.BackgroundFile);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapTimingPoints()
|
||||
{
|
||||
@ -181,16 +225,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 +269,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 +317,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()
|
||||
{
|
||||
@ -298,6 +364,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
{
|
||||
var comboColors = decoder.Decode(stream).ComboColours;
|
||||
|
||||
Debug.Assert(comboColors != null);
|
||||
|
||||
Color4[] expectedColors =
|
||||
{
|
||||
new Color4(142, 199, 255, 255),
|
||||
@ -308,7 +376,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
new Color4(255, 177, 140, 255),
|
||||
new Color4(100, 100, 100, 255), // alpha is specified as 100, but should be ignored.
|
||||
};
|
||||
Assert.AreEqual(expectedColors.Length, comboColors?.Count);
|
||||
Assert.AreEqual(expectedColors.Length, comboColors.Count);
|
||||
for (int i = 0; i < expectedColors.Length; i++)
|
||||
Assert.AreEqual(expectedColors[i], comboColors[i]);
|
||||
}
|
||||
@ -393,14 +461,14 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.IsNotNull(curveData);
|
||||
Assert.AreEqual(new Vector2(192, 168), positionData.Position);
|
||||
Assert.AreEqual(new Vector2(192, 168), positionData!.Position);
|
||||
Assert.AreEqual(956, hitObjects[0].StartTime);
|
||||
Assert.IsTrue(hitObjects[0].Samples.Any(s => s.Name == HitSampleInfo.HIT_NORMAL));
|
||||
|
||||
positionData = hitObjects[1] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(304, 56), positionData.Position);
|
||||
Assert.AreEqual(new Vector2(304, 56), positionData!.Position);
|
||||
Assert.AreEqual(1285, hitObjects[1].StartTime);
|
||||
Assert.IsTrue(hitObjects[1].Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP));
|
||||
}
|
||||
@ -556,8 +624,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestFallbackDecoderForCorruptedHeader()
|
||||
{
|
||||
Decoder<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> decoder = null!;
|
||||
Beatmap beatmap = null!;
|
||||
|
||||
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
@ -574,8 +642,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestFallbackDecoderForMissingHeader()
|
||||
{
|
||||
Decoder<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> decoder = null!;
|
||||
Beatmap beatmap = null!;
|
||||
|
||||
using (var resStream = TestResources.OpenResource("missing-header.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
@ -592,8 +660,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestDecodeFileWithEmptyLinesAtStart()
|
||||
{
|
||||
Decoder<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> decoder = null!;
|
||||
Beatmap beatmap = null!;
|
||||
|
||||
using (var resStream = TestResources.OpenResource("empty-lines-at-start.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
@ -610,8 +678,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestDecodeFileWithEmptyLinesAndNoHeader()
|
||||
{
|
||||
Decoder<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> decoder = null!;
|
||||
Beatmap beatmap = null!;
|
||||
|
||||
using (var resStream = TestResources.OpenResource("empty-line-instead-of-header.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
@ -628,8 +696,8 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestDecodeFileWithContentImmediatelyAfterHeader()
|
||||
{
|
||||
Decoder<Beatmap> decoder = null;
|
||||
Beatmap beatmap = null;
|
||||
Decoder<Beatmap> decoder = null!;
|
||||
Beatmap beatmap = null!;
|
||||
|
||||
using (var resStream = TestResources.OpenResource("no-empty-line-after-header.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
@ -656,7 +724,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
[Test]
|
||||
public void TestAllowFallbackDecoderOverwrite()
|
||||
{
|
||||
Decoder<Beatmap> decoder = null;
|
||||
Decoder<Beatmap> decoder = null!;
|
||||
|
||||
using (var resStream = TestResources.OpenResource("corrupted-header.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
|
@ -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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osuTK;
|
||||
@ -30,35 +28,35 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.IsTrue(storyboard.HasDrawable);
|
||||
Assert.AreEqual(6, storyboard.Layers.Count());
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.FirstOrDefault(l => l.Depth == 3);
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.IsNotNull(background);
|
||||
Assert.AreEqual(16, background.Elements.Count);
|
||||
Assert.IsTrue(background.VisibleWhenFailing);
|
||||
Assert.IsTrue(background.VisibleWhenPassing);
|
||||
Assert.AreEqual("Background", background.Name);
|
||||
|
||||
StoryboardLayer fail = storyboard.Layers.FirstOrDefault(l => l.Depth == 2);
|
||||
StoryboardLayer fail = storyboard.Layers.Single(l => l.Depth == 2);
|
||||
Assert.IsNotNull(fail);
|
||||
Assert.AreEqual(0, fail.Elements.Count);
|
||||
Assert.IsTrue(fail.VisibleWhenFailing);
|
||||
Assert.IsFalse(fail.VisibleWhenPassing);
|
||||
Assert.AreEqual("Fail", fail.Name);
|
||||
|
||||
StoryboardLayer pass = storyboard.Layers.FirstOrDefault(l => l.Depth == 1);
|
||||
StoryboardLayer pass = storyboard.Layers.Single(l => l.Depth == 1);
|
||||
Assert.IsNotNull(pass);
|
||||
Assert.AreEqual(0, pass.Elements.Count);
|
||||
Assert.IsFalse(pass.VisibleWhenFailing);
|
||||
Assert.IsTrue(pass.VisibleWhenPassing);
|
||||
Assert.AreEqual("Pass", pass.Name);
|
||||
|
||||
StoryboardLayer foreground = storyboard.Layers.FirstOrDefault(l => l.Depth == 0);
|
||||
StoryboardLayer foreground = storyboard.Layers.Single(l => l.Depth == 0);
|
||||
Assert.IsNotNull(foreground);
|
||||
Assert.AreEqual(151, foreground.Elements.Count);
|
||||
Assert.IsTrue(foreground.VisibleWhenFailing);
|
||||
Assert.IsTrue(foreground.VisibleWhenPassing);
|
||||
Assert.AreEqual("Foreground", foreground.Name);
|
||||
|
||||
StoryboardLayer overlay = storyboard.Layers.FirstOrDefault(l => l.Depth == int.MinValue);
|
||||
StoryboardLayer overlay = storyboard.Layers.Single(l => l.Depth == int.MinValue);
|
||||
Assert.IsNotNull(overlay);
|
||||
Assert.IsEmpty(overlay.Elements);
|
||||
Assert.IsTrue(overlay.VisibleWhenFailing);
|
||||
@ -76,7 +74,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
|
||||
var sprite = background.Elements.ElementAt(0) as StoryboardSprite;
|
||||
Assert.NotNull(sprite);
|
||||
Assert.IsTrue(sprite.HasCommands);
|
||||
Assert.IsTrue(sprite!.HasCommands);
|
||||
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
|
||||
Assert.IsTrue(sprite.IsDrawable);
|
||||
Assert.AreEqual(Anchor.Centre, sprite.Origin);
|
||||
@ -171,6 +169,55 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeVideoWithLowercaseExtension()
|
||||
{
|
||||
var decoder = new LegacyStoryboardDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("video-with-lowercase-extension.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||
|
||||
Assert.AreEqual("Video.avi", ((StoryboardVideo)video.Elements[0]).Path);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeVideoWithUppercaseExtension()
|
||||
{
|
||||
var decoder = new LegacyStoryboardDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("video-with-uppercase-extension.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||
Assert.That(video.Elements.Count, Is.EqualTo(1));
|
||||
|
||||
Assert.AreEqual("Video.AVI", ((StoryboardVideo)video.Elements[0]).Path);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeImageSpecifiedAsVideo()
|
||||
{
|
||||
var decoder = new LegacyStoryboardDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("image-specified-as-video.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer video = storyboard.Layers.Single(l => l.Name == "Video");
|
||||
Assert.That(video.Elements.Count, Is.Zero);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeOutOfRangeLoopAnimationType()
|
||||
{
|
||||
|
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,
|
||||
|
4
osu.Game.Tests/Resources/image-specified-as-video.osb
Normal file
4
osu.Game.Tests/Resources/image-specified-as-video.osb
Normal file
@ -0,0 +1,4 @@
|
||||
osu file format v14
|
||||
|
||||
[Events]
|
||||
Video,0,"BG.jpg",0,0
|
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
|
@ -0,0 +1,5 @@
|
||||
osu file format v14
|
||||
|
||||
[Events]
|
||||
0,0,"BG.jpg",0,0
|
||||
Video,0,"Video.avi",0,0
|
@ -0,0 +1,5 @@
|
||||
osu file format v14
|
||||
|
||||
[Events]
|
||||
0,0,"BG.jpg",0,0
|
||||
Video,0,"Video.AVI",0,0
|
@ -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[] GetRawData(string fileName) => parentManager.GetRawData(fileName);
|
||||
|
||||
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>
|
||||
|
@ -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>().GetRawData(@"sh_TestVertex.vs");
|
||||
Dependencies.Get<ShaderManager>().GetRawData(@"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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -7,16 +7,20 @@ 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
|
||||
@ -36,13 +40,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
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;
|
||||
@ -51,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
// 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
|
||||
{
|
||||
@ -71,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;
|
||||
}
|
||||
|
||||
@ -129,14 +148,36 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
waitForAliveObjectIndex(3);
|
||||
checkValidObjectIndex(3);
|
||||
|
||||
AddStep("Seek into future", () => Beatmap.Value.Track.Seek(beatmap.HitObjects.Last().GetEndTime() + 10000));
|
||||
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(3);
|
||||
checkValidObjectIndex(4);
|
||||
}
|
||||
|
||||
private void seekBeforeIndex(int index) =>
|
||||
AddStep($"seek to just before object {index}", () => Beatmap.Value.Track.Seek(beatmap.HitObjects[index].StartTime - 100));
|
||||
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)
|
||||
{
|
||||
|
@ -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<KeyCounter>>().Single();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -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(new KeyCounterKeyboard(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,8 @@ 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;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@ -17,43 +19,63 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public TestSceneKeyCounter()
|
||||
{
|
||||
KeyCounterKeyboard testCounter;
|
||||
|
||||
KeyCounterDisplay kc = new KeyCounterDisplay
|
||||
KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new KeyCounter[]
|
||||
{
|
||||
testCounter = new KeyCounterKeyboard(Key.X),
|
||||
new KeyCounterKeyboard(Key.X),
|
||||
new KeyCounterMouse(MouseButton.Left),
|
||||
new KeyCounterMouse(MouseButton.Right),
|
||||
},
|
||||
Position = new Vector2(0, 72.7f)
|
||||
};
|
||||
|
||||
KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Position = new Vector2(0, -72.7f)
|
||||
};
|
||||
|
||||
defaultDisplay.AddRange(new InputTrigger[]
|
||||
{
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||
});
|
||||
|
||||
argonDisplay.AddRange(new InputTrigger[]
|
||||
{
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||
});
|
||||
|
||||
var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First();
|
||||
|
||||
AddStep("Add random", () =>
|
||||
{
|
||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||
kc.Add(new KeyCounterKeyboard(key));
|
||||
defaultDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||
argonDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||
});
|
||||
|
||||
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
|
||||
Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key;
|
||||
|
||||
void addPressKeyStep()
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
||||
AddStep("Disable counting", () =>
|
||||
{
|
||||
AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
||||
}
|
||||
argonDisplay.IsCounting.Value = false;
|
||||
defaultDisplay.IsCounting.Value = false;
|
||||
});
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
||||
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
|
||||
AddStep("Disable counting", () => testCounter.IsCounting = false);
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
|
||||
Add(defaultDisplay);
|
||||
Add(argonDisplay);
|
||||
|
||||
Add(kc);
|
||||
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Resolved]
|
||||
private SessionStatics sessionStatics { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; }
|
||||
|
||||
[Cached(typeof(INotificationOverlay))]
|
||||
private readonly NotificationOverlay notificationOverlay;
|
||||
|
||||
@ -317,6 +320,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
saveVolumes();
|
||||
setFullVolume();
|
||||
|
||||
AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true));
|
||||
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
||||
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||
|
||||
@ -333,12 +337,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
restoreVolumes();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEpilepsyWarningWithDisabledStoryboard()
|
||||
{
|
||||
saveVolumes();
|
||||
setFullVolume();
|
||||
|
||||
AddStep("disable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, false));
|
||||
AddStep("change epilepsy warning", () => epilepsyWarning = true);
|
||||
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||
|
||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||
|
||||
AddUntilStep("epilepsy warning absent", () => getWarning() == null);
|
||||
|
||||
restoreVolumes();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEpilepsyWarningEarlyExit()
|
||||
{
|
||||
saveVolumes();
|
||||
setFullVolume();
|
||||
|
||||
AddStep("enable storyboards", () => config.SetValue(OsuSetting.ShowStoryboard, true));
|
||||
AddStep("set epilepsy warning", () => epilepsyWarning = true);
|
||||
AddStep("load dummy beatmap", () => resetPlayer(false));
|
||||
|
||||
@ -449,7 +471,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("click notification", () => notification.TriggerClick());
|
||||
}
|
||||
|
||||
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault();
|
||||
private EpilepsyWarning getWarning() => loader.ChildrenOfType<EpilepsyWarning>().SingleOrDefault(w => w.IsAlive);
|
||||
|
||||
private partial class TestPlayerLoader : PlayerLoader
|
||||
{
|
||||
|
@ -164,6 +164,36 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRevertNestedObjects()
|
||||
{
|
||||
ManualClock clock = null;
|
||||
|
||||
var beatmap = new Beatmap();
|
||||
beatmap.HitObjects.Add(new TestHitObjectWithNested { Duration = 40 });
|
||||
|
||||
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
||||
|
||||
AddStep("skip to middle of object", () => clock.CurrentTime = (beatmap.HitObjects[0].StartTime + beatmap.HitObjects[0].GetEndTime()) / 2);
|
||||
AddAssert("2 objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(2));
|
||||
|
||||
AddStep("skip to before end of object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() - 1);
|
||||
AddAssert("3 objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
|
||||
|
||||
DrawableHitObject drawableHitObject = null;
|
||||
HashSet<HitObject> revertedHitObjects = new HashSet<HitObject>();
|
||||
|
||||
AddStep("retrieve drawable hit object", () => drawableHitObject = playfield.ChildrenOfType<DrawableTestHitObjectWithNested>().Single());
|
||||
AddStep("set up revert tracking", () =>
|
||||
{
|
||||
revertedHitObjects.Clear();
|
||||
drawableHitObject.OnRevertResult += (ho, _) => revertedHitObjects.Add(ho.HitObject);
|
||||
});
|
||||
AddStep("skip back to object start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime);
|
||||
AddAssert("3 reverts fired", () => revertedHitObjects, () => Has.Count.EqualTo(3));
|
||||
AddAssert("no objects judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestApplyHitResultOnKilled()
|
||||
{
|
||||
@ -258,6 +288,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
RegisterPool<TestHitObject, DrawableTestHitObject>(poolSize);
|
||||
RegisterPool<TestKilledHitObject, DrawableTestKilledHitObject>(poolSize);
|
||||
RegisterPool<TestHitObjectWithNested, DrawableTestHitObjectWithNested>(poolSize);
|
||||
RegisterPool<NestedHitObject, DrawableNestedHitObject>(poolSize);
|
||||
}
|
||||
|
||||
protected override HitObjectLifetimeEntry CreateLifetimeEntry(HitObject hitObject) => new TestHitObjectLifetimeEntry(hitObject);
|
||||
@ -388,6 +420,120 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
}
|
||||
|
||||
private class TestHitObjectWithNested : TestHitObject
|
||||
{
|
||||
protected override void CreateNestedHitObjects(CancellationToken cancellationToken)
|
||||
{
|
||||
base.CreateNestedHitObjects(cancellationToken);
|
||||
|
||||
for (int i = 0; i < 3; ++i)
|
||||
AddNested(new NestedHitObject { StartTime = (float)Duration * (i + 1) / 4 });
|
||||
}
|
||||
}
|
||||
|
||||
private class NestedHitObject : ConvertHitObject
|
||||
{
|
||||
}
|
||||
|
||||
private partial class DrawableTestHitObjectWithNested : DrawableHitObject<TestHitObjectWithNested>
|
||||
{
|
||||
private Container nestedContainer;
|
||||
|
||||
public DrawableTestHitObjectWithNested()
|
||||
: base(null)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Red
|
||||
},
|
||||
nestedContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
Size = new Vector2(200, 50);
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||
{
|
||||
base.AddNestedHitObject(hitObject);
|
||||
nestedContainer.Add(hitObject);
|
||||
}
|
||||
|
||||
protected override void ClearNestedHitObjects()
|
||||
{
|
||||
base.ClearNestedHitObjects();
|
||||
nestedContainer.Clear(false);
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
if (timeOffset >= 0)
|
||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class DrawableNestedHitObject : DrawableHitObject<NestedHitObject>
|
||||
{
|
||||
public DrawableNestedHitObject()
|
||||
: this(null)
|
||||
{
|
||||
}
|
||||
|
||||
public DrawableNestedHitObject(NestedHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
Size = new Vector2(15);
|
||||
Colour = Colour4.White;
|
||||
RelativePositionAxes = Axes.Both;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnApply()
|
||||
{
|
||||
base.OnApply();
|
||||
|
||||
X = (float)((HitObject.StartTime - ParentHitObject!.HitObject.StartTime) / (ParentHitObject.HitObject.GetEndTime() - ParentHitObject.HitObject.StartTime));
|
||||
Y = 0.5f;
|
||||
|
||||
LifetimeStart = ParentHitObject.LifetimeStart;
|
||||
LifetimeEnd = ParentHitObject.LifetimeEnd;
|
||||
}
|
||||
|
||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||
{
|
||||
base.CheckForResult(userTriggered, timeOffset);
|
||||
if (timeOffset >= 0)
|
||||
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
protected override void AddCheckSteps()
|
||||
{
|
||||
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 0));
|
||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0));
|
||||
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK.Input;
|
||||
@ -45,6 +46,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("Time still stopped", () => lastTime == Player.GameplayClockContainer.CurrentTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDoesNotFailOnExit()
|
||||
{
|
||||
loadPlayerWithBeatmap();
|
||||
|
||||
AddUntilStep("wait for first hit", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
AddAssert("ensure rank is not fail", () => Player.ScoreProcessor.Rank.Value, () => Is.Not.EqualTo(ScoreRank.F));
|
||||
AddStep("exit player", () => Player.Exit());
|
||||
AddUntilStep("wait for exit", () => Player.Parent == null);
|
||||
AddAssert("ensure rank is not fail", () => Player.ScoreProcessor.Rank.Value, () => Is.Not.EqualTo(ScoreRank.F));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPauseViaSpaceWithSkip()
|
||||
{
|
||||
|
@ -1,9 +1,11 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
@ -12,41 +14,224 @@ using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneSkinEditor : PlayerTestScene
|
||||
{
|
||||
private SkinEditor? skinEditor;
|
||||
private SkinEditor skinEditor = null!;
|
||||
|
||||
protected override bool Autoplay => true;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||
|
||||
[Cached]
|
||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||
|
||||
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddUntilStep("wait for hud load", () => Player.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
||||
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
|
||||
|
||||
AddStep("reload skin editor", () =>
|
||||
{
|
||||
skinEditor?.Expire();
|
||||
if (skinEditor.IsNotNull())
|
||||
skinEditor.Expire();
|
||||
Player.ScaleTo(0.4f);
|
||||
LoadComponentAsync(skinEditor = new SkinEditor(Player), Add);
|
||||
});
|
||||
AddUntilStep("wait for loaded", () => skinEditor!.IsLoaded);
|
||||
AddUntilStep("wait for loaded", () => skinEditor.IsLoaded);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDragSelection()
|
||||
{
|
||||
BigBlackBox box1 = null!;
|
||||
BigBlackBox box2 = null!;
|
||||
BigBlackBox box3 = null!;
|
||||
|
||||
AddStep("Add big black boxes", () =>
|
||||
{
|
||||
var target = Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
target.Add(box1 = new BigBlackBox
|
||||
{
|
||||
Position = new Vector2(-90),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
target.Add(box2 = new BigBlackBox
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
target.Add(box3 = new BigBlackBox
|
||||
{
|
||||
Position = new Vector2(90),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
});
|
||||
|
||||
// This step is specifically added to reproduce an edge case which was found during cyclic selection development.
|
||||
// If everything is working as expected it should not affect the subsequent drag selections.
|
||||
AddRepeatStep("Select top left", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft + new Vector2(box1.ScreenSpaceDrawQuad.Width / 8));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}, 2);
|
||||
|
||||
AddStep("Begin drag top left", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.TopLeft - new Vector2(box1.ScreenSpaceDrawQuad.Width / 4));
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("Drag to bottom right", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.TopRight + new Vector2(-box3.ScreenSpaceDrawQuad.Width / 8, box3.ScreenSpaceDrawQuad.Height / 4));
|
||||
});
|
||||
|
||||
AddStep("Release button", () =>
|
||||
{
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("First two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box1, box2 }));
|
||||
|
||||
AddStep("Begin drag bottom right", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.BottomRight + new Vector2(box3.ScreenSpaceDrawQuad.Width / 4));
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("Drag to top left", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre - new Vector2(box2.ScreenSpaceDrawQuad.Width / 4));
|
||||
});
|
||||
|
||||
AddStep("Release button", () =>
|
||||
{
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("Last two boxes selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||
|
||||
// Test cyclic selection doesn't trigger in this state.
|
||||
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("Last two boxes still selected", () => skinEditor.SelectedComponents, () => Is.EqualTo(new[] { box2, box3 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCyclicSelection()
|
||||
{
|
||||
SkinBlueprint[] blueprints = null!;
|
||||
|
||||
AddStep("Add big black boxes", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("Three black boxes added", () => targetContainer.Components.OfType<BigBlackBox>().Count(), () => Is.EqualTo(3));
|
||||
|
||||
AddStep("Store black box blueprints", () =>
|
||||
{
|
||||
blueprints = skinEditor.ChildrenOfType<SkinBlueprint>().Where(b => b.Item is BigBlackBox).ToArray();
|
||||
});
|
||||
|
||||
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||
|
||||
AddStep("move cursor to black box", () =>
|
||||
{
|
||||
// Slightly offset from centre to avoid random failures (see https://github.com/ppy/osu-framework/issues/5669).
|
||||
InputManager.MoveMouseTo(((Drawable)blueprints[0].Item).ScreenSpaceDrawQuad.Centre + new Vector2(1));
|
||||
});
|
||||
|
||||
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("Selection is black box 2", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[1].Item));
|
||||
|
||||
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("Selection is black box 3", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[2].Item));
|
||||
|
||||
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("Selection is black box 1", () => skinEditor.SelectedComponents.Single(), () => Is.EqualTo(blueprints[0].Item));
|
||||
|
||||
AddStep("select all boxes", () =>
|
||||
{
|
||||
skinEditor.SelectedComponents.Clear();
|
||||
skinEditor.SelectedComponents.AddRange(targetContainer.Components.OfType<BigBlackBox>().Skip(1));
|
||||
});
|
||||
|
||||
AddAssert("all boxes selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("click on black box stack", () => InputManager.Click(MouseButton.Left));
|
||||
AddAssert("all boxes still selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestBringToFront(bool alterSelectionOrder)
|
||||
{
|
||||
AddAssert("Ensure over three components available", () => targetContainer.Components.Count, () => Is.GreaterThan(3));
|
||||
|
||||
IEnumerable<ISerialisableDrawable> originalOrder = null!;
|
||||
|
||||
AddStep("Save order of components before operation", () => originalOrder = targetContainer.Components.Take(3).ToArray());
|
||||
|
||||
if (alterSelectionOrder)
|
||||
AddStep("Select first three components in reverse order", () => skinEditor.SelectedComponents.AddRange(originalOrder.Reverse()));
|
||||
else
|
||||
AddStep("Select first three components", () => skinEditor.SelectedComponents.AddRange(originalOrder));
|
||||
|
||||
AddAssert("Components are not front-most", () => targetContainer.Components.TakeLast(3).ToArray(), () => Is.Not.EqualTo(skinEditor.SelectedComponents));
|
||||
|
||||
AddStep("Bring to front", () => skinEditor.BringSelectionToFront());
|
||||
AddAssert("Ensure components are now front-most in original order", () => targetContainer.Components.TakeLast(3).ToArray(), () => Is.EqualTo(originalOrder));
|
||||
AddStep("Bring to front again", () => skinEditor.BringSelectionToFront());
|
||||
AddAssert("Ensure components are still front-most in original order", () => targetContainer.Components.TakeLast(3).ToArray(), () => Is.EqualTo(originalOrder));
|
||||
}
|
||||
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestSendToBack(bool alterSelectionOrder)
|
||||
{
|
||||
AddAssert("Ensure over three components available", () => targetContainer.Components.Count, () => Is.GreaterThan(3));
|
||||
|
||||
IEnumerable<ISerialisableDrawable> originalOrder = null!;
|
||||
|
||||
AddStep("Save order of components before operation", () => originalOrder = targetContainer.Components.TakeLast(3).ToArray());
|
||||
|
||||
if (alterSelectionOrder)
|
||||
AddStep("Select last three components in reverse order", () => skinEditor.SelectedComponents.AddRange(originalOrder.Reverse()));
|
||||
else
|
||||
AddStep("Select last three components", () => skinEditor.SelectedComponents.AddRange(originalOrder));
|
||||
|
||||
AddAssert("Components are not back-most", () => targetContainer.Components.Take(3).ToArray(), () => Is.Not.EqualTo(skinEditor.SelectedComponents));
|
||||
|
||||
AddStep("Send to back", () => skinEditor.SendSelectionToBack());
|
||||
AddAssert("Ensure components are now back-most in original order", () => targetContainer.Components.Take(3).ToArray(), () => Is.EqualTo(originalOrder));
|
||||
AddStep("Send to back again", () => skinEditor.SendSelectionToBack());
|
||||
AddAssert("Ensure components are still back-most in original order", () => targetContainer.Components.Take(3).ToArray(), () => Is.EqualTo(originalOrder));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestToggleEditor()
|
||||
{
|
||||
AddToggleStep("toggle editor visibility", _ => skinEditor!.ToggleVisibility());
|
||||
AddToggleStep("toggle editor visibility", _ => skinEditor.ToggleVisibility());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -59,7 +244,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First(b => b.Item is BarHitErrorMeter);
|
||||
|
||||
hitErrorMeter = (BarHitErrorMeter)blueprint.Item;
|
||||
skinEditor!.SelectedComponents.Clear();
|
||||
skinEditor.SelectedComponents.Clear();
|
||||
skinEditor.SelectedComponents.Add(blueprint.Item);
|
||||
});
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user