mirror of
https://github.com/ppy/osu.git
synced 2026-05-20 19:40:38 +08:00
Merge branch 'master' into pp-dev
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
name: Pack and nuget
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
notify_pending_production_deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Submit pending deployment notification
|
||||
run: |
|
||||
export TITLE="Pending osu Production Deployment: $GITHUB_REF_NAME"
|
||||
export URL="https://github.com/ppy/osu/actions/runs/$GITHUB_RUN_ID"
|
||||
export DESCRIPTION="Awaiting approval for building NuGet packages for tag $GITHUB_REF_NAME:
|
||||
[View Workflow Run]($URL)"
|
||||
export ACTOR_ICON="https://avatars.githubusercontent.com/u/$GITHUB_ACTOR_ID"
|
||||
|
||||
BODY="$(jq --null-input '{
|
||||
"embeds": [
|
||||
{
|
||||
"title": env.TITLE,
|
||||
"color": 15098112,
|
||||
"description": env.DESCRIPTION,
|
||||
"url": env.URL,
|
||||
"author": {
|
||||
"name": env.GITHUB_ACTOR,
|
||||
"icon_url": env.ACTOR_ICON
|
||||
}
|
||||
}
|
||||
]
|
||||
}')"
|
||||
|
||||
curl \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$BODY" \
|
||||
"${{ secrets.DISCORD_INFRA_WEBHOOK_URL }}"
|
||||
|
||||
pack:
|
||||
name: Pack
|
||||
runs-on: ubuntu-latest
|
||||
environment: production
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set artifacts directory
|
||||
id: artifactsPath
|
||||
run: echo "::set-output name=nuget_artifacts::${{github.workspace}}/artifacts"
|
||||
|
||||
- name: Install .NET 8.0.x
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: "8.0.x"
|
||||
|
||||
- name: Pack
|
||||
run: |
|
||||
# Replace project references in templates with package reference, because they're included as source files.
|
||||
dotnet remove Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj reference osu.Game/osu.Game.csproj
|
||||
dotnet remove Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj reference osu.Game/osu.Game.csproj
|
||||
dotnet remove Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj reference osu.Game/osu.Game.csproj
|
||||
dotnet remove Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj reference osu.Game/osu.Game.csproj
|
||||
|
||||
dotnet add Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform/osu.Game.Rulesets.EmptyFreeform.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
dotnet add Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
dotnet add Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling/osu.Game.Rulesets.EmptyScrolling.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
dotnet add Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -n -v ${{ github.ref_name }}
|
||||
|
||||
# Pack
|
||||
dotnet pack -c Release osu.Game /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Osu /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Taiko /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Catch /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release osu.Game.Rulesets.Mania /p:Version=${{ github.ref_name }} /p:GenerateDocumentationFile=true /p:IncludeSymbols=true /p:SymbolPackageFormat=snupkg -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
dotnet pack -c Release Templates /p:Version=${{ github.ref_name }} -o ${{steps.artifactsPath.outputs.nuget_artifacts}}
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: osu
|
||||
path: |
|
||||
${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg
|
||||
${{steps.artifactsPath.outputs.nuget_artifacts}}/*.snupkg
|
||||
|
||||
- name: Publish packages to nuget.org
|
||||
run: dotnet nuget push ${{steps.artifactsPath.outputs.nuget_artifacts}}/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
|
||||
+4
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osuTK;
|
||||
|
||||
@@ -17,5 +18,8 @@ namespace osu.Game.Rulesets.EmptyFreeform.Replays
|
||||
if (button.HasValue)
|
||||
Actions.Add(button.Value);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is EmptyFreeformReplayFrame freeformFrame && Time == freeformFrame.Time && Position == freeformFrame.Position && Actions.SequenceEqual(freeformFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
+3
@@ -9,5 +9,8 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
public class PippidonReplayFrame : ReplayFrame
|
||||
{
|
||||
public Vector2 Position;
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is PippidonReplayFrame pippidonFrame && Time == pippidonFrame.Time && Position == pippidonFrame.Position;
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
||||
@@ -15,5 +16,8 @@ namespace osu.Game.Rulesets.EmptyScrolling.Replays
|
||||
if (button.HasValue)
|
||||
Actions.Add(button.Value);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is EmptyScrollingReplayFrame scrollingFrame && Time == scrollingFrame.Time && Actions.SequenceEqual(scrollingFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
|
||||
namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
@@ -15,5 +16,8 @@ namespace osu.Game.Rulesets.Pippidon.Replays
|
||||
if (button.HasValue)
|
||||
Actions.Add(button.Value);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is PippidonReplayFrame pippidonFrame && Time == pippidonFrame.Time && Actions.SequenceEqual(pippidonFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
clone_depth: 1
|
||||
version: '{branch}-{build}'
|
||||
image: Visual Studio 2022
|
||||
cache:
|
||||
- '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml'
|
||||
|
||||
dotnet_csproj:
|
||||
patch: true
|
||||
file: 'osu.Game\osu.Game.csproj' # Use wildcard when it's able to exclude Xamarin projects
|
||||
version: '0.0.{build}'
|
||||
|
||||
before_build:
|
||||
- cmd: dotnet --info # Useful when version mismatch between CI and local
|
||||
- cmd: dotnet workload install maui-android # Change to `dotnet workload restore` once there's no old projects
|
||||
- cmd: dotnet workload install maui-ios # Change to `dotnet workload restore` once there's no old projects
|
||||
- cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects
|
||||
|
||||
build:
|
||||
project: osu.sln
|
||||
parallel: true
|
||||
verbosity: minimal
|
||||
publish_nuget: true
|
||||
|
||||
after_build:
|
||||
- ps: .\InspectCode.ps1
|
||||
|
||||
test:
|
||||
assemblies:
|
||||
except:
|
||||
- '**\*Android*'
|
||||
- '**\*iOS*'
|
||||
- 'build\**\*'
|
||||
@@ -1,86 +0,0 @@
|
||||
clone_depth: 1
|
||||
version: '{build}'
|
||||
image: Visual Studio 2022
|
||||
test: off
|
||||
skip_non_tags: true
|
||||
configuration: Release
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- job_name: osu-game
|
||||
- job_name: osu-ruleset
|
||||
job_depends_on: osu-game
|
||||
- job_name: taiko-ruleset
|
||||
job_depends_on: osu-game
|
||||
- job_name: catch-ruleset
|
||||
job_depends_on: osu-game
|
||||
- job_name: mania-ruleset
|
||||
job_depends_on: osu-game
|
||||
- job_name: templates
|
||||
job_depends_on: osu-game
|
||||
|
||||
nuget:
|
||||
project_feed: true
|
||||
|
||||
for:
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: osu-game
|
||||
build_script:
|
||||
- cmd: dotnet pack osu.Game\osu.Game.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: osu-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: taiko-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: catch-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Catch\osu.Game.Rulesets.Catch.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: mania-ruleset
|
||||
build_script:
|
||||
- cmd: dotnet remove osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet add osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet pack osu.Game.Rulesets.Mania\osu.Game.Rulesets.Mania.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
-
|
||||
matrix:
|
||||
only:
|
||||
- job_name: templates
|
||||
build_script:
|
||||
- cmd: dotnet remove Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet remove Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet remove Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj reference osu.Game\osu.Game.csproj
|
||||
- cmd: dotnet remove Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj reference osu.Game\osu.Game.csproj
|
||||
|
||||
- cmd: dotnet add Templates\Rulesets\ruleset-empty\osu.Game.Rulesets.EmptyFreeform\osu.Game.Rulesets.EmptyFreeform.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet add Templates\Rulesets\ruleset-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet add Templates\Rulesets\ruleset-scrolling-empty\osu.Game.Rulesets.EmptyScrolling\osu.Game.Rulesets.EmptyScrolling.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
- cmd: dotnet add Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon\osu.Game.Rulesets.Pippidon.csproj package ppy.osu.Game -v %APPVEYOR_REPO_TAG_NAME%
|
||||
|
||||
- cmd: dotnet pack Templates\osu.Game.Templates.csproj /p:Version=%APPVEYOR_REPO_TAG_NAME%
|
||||
|
||||
artifacts:
|
||||
- path: '**\*.nupkg'
|
||||
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: nuget
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.512.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.704.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -184,7 +184,7 @@ namespace osu.Desktop
|
||||
|
||||
var app = VelopackApp.Build();
|
||||
|
||||
app.WithFirstRun(_ => isFirstRun = true);
|
||||
app.OnFirstRun(_ => isFirstRun = true);
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
configureWindows(app);
|
||||
@@ -195,9 +195,9 @@ namespace osu.Desktop
|
||||
[SupportedOSPlatform("windows")]
|
||||
private static void configureWindows(VelopackApp app)
|
||||
{
|
||||
app.WithFirstRun(_ => WindowsAssociationManager.InstallAssociations());
|
||||
app.WithAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations());
|
||||
app.WithBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations());
|
||||
app.OnFirstRun(_ => WindowsAssociationManager.InstallAssociations());
|
||||
app.OnAfterUpdateFastCallback(_ => WindowsAssociationManager.UpdateAssociations());
|
||||
app.OnBeforeUninstallFastCallback(_ => WindowsAssociationManager.UninstallAssociations());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Screens.Play;
|
||||
using Velopack;
|
||||
using Velopack.Sources;
|
||||
using UpdateManager = osu.Game.Updater.UpdateManager;
|
||||
|
||||
namespace osu.Desktop.Updater
|
||||
{
|
||||
public partial class VelopackUpdateManager : Game.Updater.UpdateManager
|
||||
public partial class VelopackUpdateManager : UpdateManager
|
||||
{
|
||||
[Resolved]
|
||||
private INotificationOverlay notificationOverlay { get; set; } = null!;
|
||||
@@ -27,143 +28,119 @@ namespace osu.Desktop.Updater
|
||||
[Resolved]
|
||||
private ILocalUserPlayInfo? localUserInfo { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager osuConfigManager { get; set; } = null!;
|
||||
|
||||
private bool isInGameplay => localUserInfo?.PlayingState.Value != LocalUserPlayingState.NotPlaying;
|
||||
|
||||
private readonly Bindable<ReleaseStream> releaseStream = new Bindable<ReleaseStream>();
|
||||
private UpdateManager? updateManager;
|
||||
private UpdateInfo? pendingUpdate;
|
||||
private ScheduledDelegate? scheduledBackgroundCheck;
|
||||
|
||||
protected override void LoadComplete()
|
||||
private void scheduleNextUpdateCheck()
|
||||
{
|
||||
// Used by the base implementation.
|
||||
osuConfigManager.BindWith(OsuSetting.ReleaseStream, releaseStream);
|
||||
releaseStream.BindValueChanged(_ => onReleaseStreamChanged(), true);
|
||||
|
||||
base.LoadComplete();
|
||||
scheduledBackgroundCheck?.Cancel();
|
||||
scheduledBackgroundCheck = Scheduler.AddDelayed(() =>
|
||||
{
|
||||
log("Running scheduled background update check...");
|
||||
CheckForUpdate();
|
||||
}, 60000 * 30);
|
||||
}
|
||||
|
||||
private void onReleaseStreamChanged()
|
||||
protected override async Task<bool> PerformUpdateCheck(CancellationToken cancellationToken)
|
||||
{
|
||||
updateManager = new UpdateManager(new GithubSource(@"https://github.com/ppy/osu", null, releaseStream.Value == ReleaseStream.Tachyon), new UpdateOptions
|
||||
scheduledBackgroundCheck?.Cancel();
|
||||
|
||||
if (isInGameplay)
|
||||
{
|
||||
AllowVersionDowngrade = true,
|
||||
log("Update check cancelled - user is in gameplay");
|
||||
scheduleNextUpdateCheck();
|
||||
return false;
|
||||
}
|
||||
|
||||
IUpdateSource updateSource = new GithubSource(@"https://github.com/ppy/osu", null, ReleaseStream.Value == Game.Configuration.ReleaseStream.Tachyon);
|
||||
Velopack.UpdateManager updateManager = new Velopack.UpdateManager(updateSource, new UpdateOptions
|
||||
{
|
||||
AllowVersionDowngrade = true
|
||||
});
|
||||
|
||||
Schedule(() => Task.Run(CheckForUpdateAsync));
|
||||
}
|
||||
UpdateInfo? update = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
|
||||
|
||||
protected override async Task<bool> PerformUpdateCheck() => await checkForUpdateAsync().ConfigureAwait(false);
|
||||
|
||||
private async Task<bool> checkForUpdateAsync()
|
||||
{
|
||||
// whether to check again in 30 minutes. generally only if there's an error or no update was found (yet).
|
||||
bool scheduleRecheck = false;
|
||||
|
||||
try
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Avoid any kind of update checking while gameplay is running.
|
||||
if (isInGameplay)
|
||||
{
|
||||
scheduleRecheck = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: we should probably be checking if there's a more recent update, rather than shortcutting here.
|
||||
// Velopack does support this scenario (see https://github.com/ppy/osu/pull/28743#discussion_r1743495975).
|
||||
if (pendingUpdate != null)
|
||||
{
|
||||
// If there is an update pending restart, show the notification to restart again.
|
||||
notificationOverlay.Post(new UpdateApplicationCompleteNotification
|
||||
{
|
||||
Activated = () =>
|
||||
{
|
||||
Task.Run(restartToApplyUpdate);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (updateManager == null)
|
||||
{
|
||||
scheduleRecheck = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
pendingUpdate = await updateManager.CheckForUpdatesAsync().ConfigureAwait(false);
|
||||
|
||||
// No update is available. We'll check again later.
|
||||
if (pendingUpdate == null)
|
||||
{
|
||||
scheduleRecheck = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// An update is found, let's notify the user and start downloading it.
|
||||
UpdateProgressNotification notification = new UpdateProgressNotification
|
||||
{
|
||||
CompletionClickAction = () =>
|
||||
{
|
||||
Task.Run(restartToApplyUpdate);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
runOutsideOfGameplay(() => notificationOverlay.Post(notification));
|
||||
notification.StartDownload();
|
||||
|
||||
try
|
||||
{
|
||||
await updateManager.DownloadUpdatesAsync(pendingUpdate, p => notification.Progress = p / 100f).ConfigureAwait(false);
|
||||
runOutsideOfGameplay(() => notification.State = ProgressNotificationState.Completed);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// In the case of an error, a separate notification will be displayed.
|
||||
scheduleRecheck = true;
|
||||
notification.FailDownload();
|
||||
Logger.Error(e, @"update failed!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// we'll ignore this and retry later. can be triggered by no internet connection or thread abortion.
|
||||
scheduleRecheck = true;
|
||||
Logger.Log($@"update check failed ({e.Message})");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (scheduleRecheck)
|
||||
{
|
||||
Scheduler.AddDelayed(() => Task.Run(async () => await checkForUpdateAsync().ConfigureAwait(false)), 60000 * 30);
|
||||
}
|
||||
log("Update check cancelled");
|
||||
scheduleNextUpdateCheck();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (update == null)
|
||||
{
|
||||
// No update is available.
|
||||
log("No update found");
|
||||
scheduleNextUpdateCheck();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Download update in the background while notifying awaiters of the update being available.
|
||||
log($"New update available: {update.TargetFullRelease.Version}");
|
||||
downloadUpdate(updateManager, update, cancellationToken);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void runOutsideOfGameplay(Action action)
|
||||
private void downloadUpdate(Velopack.UpdateManager updateManager, UpdateInfo update, CancellationToken cancellationToken) => Task.Run(async () =>
|
||||
{
|
||||
log($"Beginning download of update {update.TargetFullRelease.Version}...");
|
||||
|
||||
UpdateDownloadProgressNotification progressNotification = new UpdateDownloadProgressNotification(cancellationToken)
|
||||
{
|
||||
CompletionClickAction = () =>
|
||||
{
|
||||
restartToApplyUpdate(updateManager, update);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(progressNotification.CancellationToken, cancellationToken))
|
||||
{
|
||||
progressNotification.StartDownload();
|
||||
runOutsideOfGameplay(() => notificationOverlay.Post(progressNotification), cts.Token);
|
||||
|
||||
await updateManager.DownloadUpdatesAsync(update, p => progressNotification.Progress = p / 100f, cts.Token).ConfigureAwait(false);
|
||||
runOutsideOfGameplay(() => progressNotification.State = ProgressNotificationState.Completed, cts.Token);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
progressNotification.FailDownload();
|
||||
log(@"Update cancelled");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// In the case of an error, a separate notification will be displayed.
|
||||
progressNotification.FailDownload();
|
||||
Logger.Error(e, @"Update failed!");
|
||||
}
|
||||
|
||||
return true;
|
||||
}, cancellationToken);
|
||||
|
||||
private void runOutsideOfGameplay(Action action, CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
return;
|
||||
|
||||
if (isInGameplay)
|
||||
{
|
||||
Scheduler.AddDelayed(() => runOutsideOfGameplay(action), 1000);
|
||||
Scheduler.AddDelayed(() => runOutsideOfGameplay(action, cancellationToken), 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
action();
|
||||
}
|
||||
|
||||
private async Task restartToApplyUpdate()
|
||||
private void restartToApplyUpdate(Velopack.UpdateManager updateManager, UpdateInfo update) => Task.Run(async () =>
|
||||
{
|
||||
if (updateManager == null)
|
||||
return;
|
||||
|
||||
await updateManager.WaitExitThenApplyUpdatesAsync(pendingUpdate?.TargetFullRelease).ConfigureAwait(false);
|
||||
await updateManager.WaitExitThenApplyUpdatesAsync(update.TargetFullRelease).ConfigureAwait(false);
|
||||
Schedule(() => game.AttemptExit());
|
||||
}
|
||||
});
|
||||
|
||||
private static void log(string text) => Logger.Log($"VelopackUpdateManager: {text}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="System.IO.Packaging" Version="9.0.2" />
|
||||
<PackageReference Include="DiscordRichPresence" Version="1.2.1.24" />
|
||||
<PackageReference Include="Velopack" Version="0.0.1053" />
|
||||
<PackageReference Include="Velopack" Version="0.0.1298" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<EmbeddedResource Include="lazer.ico" />
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Catch.Objects;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
|
||||
@@ -23,21 +24,21 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Fruits",
|
||||
Name = BeatmapStatisticStrings.Fruits,
|
||||
Content = fruits.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
BarDisplayLength = fruits / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Juice Streams",
|
||||
Name = BeatmapStatisticStrings.JuiceStreams,
|
||||
Content = juiceStreams.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
BarDisplayLength = juiceStreams / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Banana Showers",
|
||||
Name = BeatmapStatisticStrings.BananaShowers,
|
||||
Content = bananaShowers.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
BarDisplayLength = Math.Min(bananaShowers / 10f, 1),
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
@@ -219,5 +220,40 @@ namespace osu.Game.Rulesets.Catch.Edit
|
||||
distanceSnapGrid.StartTime = sourceHitObject.GetEndTime();
|
||||
distanceSnapGrid.StartX = sourceHitObject.EffectiveX;
|
||||
}
|
||||
|
||||
#region Clipboard handling
|
||||
|
||||
public override string ConvertSelectionToString()
|
||||
=> string.Join(',', EditorBeatmap.SelectedHitObjects.Cast<CatchHitObject>().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString()));
|
||||
|
||||
// 1,2,3,4 ...
|
||||
private static readonly Regex selection_regex = new Regex(@"^\d+(,\d+)*$", RegexOptions.Compiled);
|
||||
|
||||
public override void SelectFromTimestamp(double timestamp, string objectDescription)
|
||||
{
|
||||
if (!selection_regex.IsMatch(objectDescription))
|
||||
return;
|
||||
|
||||
List<CatchHitObject> remainingHitObjects = EditorBeatmap.HitObjects.Cast<CatchHitObject>().Where(h => h.StartTime >= timestamp).ToList();
|
||||
string[] splitDescription = objectDescription.Split(',');
|
||||
|
||||
for (int i = 0; i < splitDescription.Length; i++)
|
||||
{
|
||||
if (!int.TryParse(splitDescription[i], out int combo) || combo < 1)
|
||||
continue;
|
||||
|
||||
CatchHitObject? current = remainingHitObjects.FirstOrDefault(h => h.IndexInCurrentCombo + 1 == combo);
|
||||
|
||||
if (current == null)
|
||||
continue;
|
||||
|
||||
EditorBeatmap.SelectedHitObjects.Add(current);
|
||||
|
||||
if (i < splitDescription.Length - 1)
|
||||
remainingHitObjects = remainingHitObjects.Where(h => h != current && h.StartTime >= current.StartTime).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
if (!IsExactlyOneSettingChanged(CircleSize, ApproachRate, OverallDifficulty, DrainRate))
|
||||
return string.Empty;
|
||||
|
||||
if (!CircleSize.IsDefault) return format("CS", CircleSize);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -64,5 +65,12 @@ namespace osu.Game.Rulesets.Catch.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, Position, null, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is CatchReplayFrame catchFrame
|
||||
&& Time == catchFrame.Time
|
||||
&& Position == catchFrame.Position
|
||||
&& Dashing == catchFrame.Dashing
|
||||
&& Actions.SequenceEqual(catchFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")]
|
||||
public partial class TestSceneLegacyReplayPlayback : LegacyReplayPlaybackTestScene
|
||||
{
|
||||
protected override Ruleset CreateRuleset() => new ManiaRuleset();
|
||||
@@ -72,13 +71,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -137d, HitResult.Miss },
|
||||
new object[] { 5f, -138d, HitResult.Miss },
|
||||
new object[] { 5f, 111d, HitResult.Ok },
|
||||
new object[] { 5f, 112d, HitResult.Miss },
|
||||
new object[] { 5f, 113d, HitResult.Miss },
|
||||
new object[] { 5f, 114d, HitResult.Miss },
|
||||
new object[] { 5f, 135d, HitResult.Miss },
|
||||
new object[] { 5f, 136d, HitResult.Miss },
|
||||
new object[] { 5f, 137d, HitResult.Miss },
|
||||
new object[] { 5f, 138d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 5f, 112d, HitResult.Miss },
|
||||
// new object[] { 5f, 113d, HitResult.Miss },
|
||||
// new object[] { 5f, 114d, HitResult.Miss },
|
||||
// new object[] { 5f, 135d, HitResult.Miss },
|
||||
// new object[] { 5f, 136d, HitResult.Miss },
|
||||
// new object[] { 5f, 137d, HitResult.Miss },
|
||||
// new object[] { 5f, 138d, HitResult.Miss },
|
||||
|
||||
// OD = 9.3 test cases.
|
||||
// PERFECT hit window is [ -14ms, 14ms]
|
||||
@@ -99,13 +99,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 9.3f, 70d, HitResult.Ok },
|
||||
new object[] { 9.3f, 71d, HitResult.Ok },
|
||||
new object[] { 9.3f, 98d, HitResult.Ok },
|
||||
new object[] { 9.3f, 99d, HitResult.Miss },
|
||||
new object[] { 9.3f, 100d, HitResult.Miss },
|
||||
new object[] { 9.3f, 101d, HitResult.Miss },
|
||||
new object[] { 9.3f, 122d, HitResult.Miss },
|
||||
new object[] { 9.3f, 123d, HitResult.Miss },
|
||||
new object[] { 9.3f, 124d, HitResult.Miss },
|
||||
new object[] { 9.3f, 125d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 9.3f, 99d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 100d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 101d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 122d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 123d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 124d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 125d, HitResult.Miss },
|
||||
new object[] { 9.3f, -98d, HitResult.Ok },
|
||||
new object[] { 9.3f, -99d, HitResult.Ok },
|
||||
new object[] { 9.3f, -100d, HitResult.Meh },
|
||||
@@ -145,13 +146,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -137d, HitResult.Miss },
|
||||
new object[] { 5f, -138d, HitResult.Miss },
|
||||
new object[] { 5f, 111d, HitResult.Ok },
|
||||
new object[] { 5f, 112d, HitResult.Miss },
|
||||
new object[] { 5f, 113d, HitResult.Miss },
|
||||
new object[] { 5f, 114d, HitResult.Miss },
|
||||
new object[] { 5f, 135d, HitResult.Miss },
|
||||
new object[] { 5f, 136d, HitResult.Miss },
|
||||
new object[] { 5f, 137d, HitResult.Miss },
|
||||
new object[] { 5f, 138d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 5f, 112d, HitResult.Miss },
|
||||
// new object[] { 5f, 113d, HitResult.Miss },
|
||||
// new object[] { 5f, 114d, HitResult.Miss },
|
||||
// new object[] { 5f, 135d, HitResult.Miss },
|
||||
// new object[] { 5f, 136d, HitResult.Miss },
|
||||
// new object[] { 5f, 137d, HitResult.Miss },
|
||||
// new object[] { 5f, 138d, HitResult.Miss },
|
||||
|
||||
// OD = 9.3 test cases.
|
||||
// PERFECT hit window is [ -16ms, 16ms]
|
||||
@@ -172,13 +174,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 9.3f, 70d, HitResult.Ok },
|
||||
new object[] { 9.3f, 71d, HitResult.Ok },
|
||||
new object[] { 9.3f, 98d, HitResult.Ok },
|
||||
new object[] { 9.3f, 99d, HitResult.Miss },
|
||||
new object[] { 9.3f, 100d, HitResult.Miss },
|
||||
new object[] { 9.3f, 101d, HitResult.Miss },
|
||||
new object[] { 9.3f, 122d, HitResult.Miss },
|
||||
new object[] { 9.3f, 123d, HitResult.Miss },
|
||||
new object[] { 9.3f, 124d, HitResult.Miss },
|
||||
new object[] { 9.3f, 125d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 9.3f, 99d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 100d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 101d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 122d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 123d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 124d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 125d, HitResult.Miss },
|
||||
new object[] { 9.3f, -98d, HitResult.Ok },
|
||||
new object[] { 9.3f, -99d, HitResult.Ok },
|
||||
new object[] { 9.3f, -100d, HitResult.Meh },
|
||||
@@ -207,13 +210,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 3.1f, 88d, HitResult.Ok },
|
||||
new object[] { 3.1f, 89d, HitResult.Ok },
|
||||
new object[] { 3.1f, 116d, HitResult.Ok },
|
||||
new object[] { 3.1f, 117d, HitResult.Miss },
|
||||
new object[] { 3.1f, 118d, HitResult.Miss },
|
||||
new object[] { 3.1f, 119d, HitResult.Miss },
|
||||
new object[] { 3.1f, 140d, HitResult.Miss },
|
||||
new object[] { 3.1f, 141d, HitResult.Miss },
|
||||
new object[] { 3.1f, 142d, HitResult.Miss },
|
||||
new object[] { 3.1f, 143d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 3.1f, 117d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 118d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 119d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 140d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 141d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 142d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 143d, HitResult.Miss },
|
||||
new object[] { 3.1f, -116d, HitResult.Ok },
|
||||
new object[] { 3.1f, -117d, HitResult.Ok },
|
||||
new object[] { 3.1f, -118d, HitResult.Meh },
|
||||
@@ -253,13 +257,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -122d, HitResult.Miss },
|
||||
new object[] { 5f, -123d, HitResult.Miss },
|
||||
new object[] { 5f, 96d, HitResult.Ok },
|
||||
new object[] { 5f, 97d, HitResult.Miss },
|
||||
new object[] { 5f, 98d, HitResult.Miss },
|
||||
new object[] { 5f, 99d, HitResult.Miss },
|
||||
new object[] { 5f, 120d, HitResult.Miss },
|
||||
new object[] { 5f, 121d, HitResult.Miss },
|
||||
new object[] { 5f, 122d, HitResult.Miss },
|
||||
new object[] { 5f, 123d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 5f, 97d, HitResult.Miss },
|
||||
// new object[] { 5f, 98d, HitResult.Miss },
|
||||
// new object[] { 5f, 99d, HitResult.Miss },
|
||||
// new object[] { 5f, 120d, HitResult.Miss },
|
||||
// new object[] { 5f, 121d, HitResult.Miss },
|
||||
// new object[] { 5f, 122d, HitResult.Miss },
|
||||
// new object[] { 5f, 123d, HitResult.Miss },
|
||||
|
||||
// OD = 3.1 test cases.
|
||||
// PERFECT hit window is [ -16ms, 16ms]
|
||||
@@ -280,13 +285,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 3.1f, 78d, HitResult.Ok },
|
||||
new object[] { 3.1f, 79d, HitResult.Ok },
|
||||
new object[] { 3.1f, 96d, HitResult.Ok },
|
||||
new object[] { 3.1f, 97d, HitResult.Miss },
|
||||
new object[] { 3.1f, 98d, HitResult.Miss },
|
||||
new object[] { 3.1f, 99d, HitResult.Miss },
|
||||
new object[] { 3.1f, 120d, HitResult.Miss },
|
||||
new object[] { 3.1f, 121d, HitResult.Miss },
|
||||
new object[] { 3.1f, 122d, HitResult.Miss },
|
||||
new object[] { 3.1f, 123d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 3.1f, 97d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 98d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 99d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 120d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 121d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 122d, HitResult.Miss },
|
||||
// new object[] { 3.1f, 123d, HitResult.Miss },
|
||||
new object[] { 3.1f, -96d, HitResult.Ok },
|
||||
new object[] { 3.1f, -97d, HitResult.Ok },
|
||||
new object[] { 3.1f, -98d, HitResult.Meh },
|
||||
@@ -327,13 +333,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -98d, HitResult.Miss },
|
||||
new object[] { 5f, -99d, HitResult.Miss },
|
||||
new object[] { 5f, 79d, HitResult.Ok },
|
||||
new object[] { 5f, 80d, HitResult.Miss },
|
||||
new object[] { 5f, 81d, HitResult.Miss },
|
||||
new object[] { 5f, 82d, HitResult.Miss },
|
||||
new object[] { 5f, 96d, HitResult.Miss },
|
||||
new object[] { 5f, 97d, HitResult.Miss },
|
||||
new object[] { 5f, 98d, HitResult.Miss },
|
||||
new object[] { 5f, 99d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 5f, 80d, HitResult.Miss },
|
||||
// new object[] { 5f, 81d, HitResult.Miss },
|
||||
// new object[] { 5f, 82d, HitResult.Miss },
|
||||
// new object[] { 5f, 96d, HitResult.Miss },
|
||||
// new object[] { 5f, 97d, HitResult.Miss },
|
||||
// new object[] { 5f, 98d, HitResult.Miss },
|
||||
// new object[] { 5f, 99d, HitResult.Miss },
|
||||
|
||||
// OD = 9.3 test cases.
|
||||
// This leads to "effective" OD of 13.02.
|
||||
@@ -356,13 +363,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 9.3f, 50d, HitResult.Ok },
|
||||
new object[] { 9.3f, 51d, HitResult.Ok },
|
||||
new object[] { 9.3f, 69d, HitResult.Ok },
|
||||
new object[] { 9.3f, 70d, HitResult.Miss },
|
||||
new object[] { 9.3f, 71d, HitResult.Miss },
|
||||
new object[] { 9.3f, 72d, HitResult.Miss },
|
||||
new object[] { 9.3f, 86d, HitResult.Miss },
|
||||
new object[] { 9.3f, 87d, HitResult.Miss },
|
||||
new object[] { 9.3f, 88d, HitResult.Miss },
|
||||
new object[] { 9.3f, 89d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 9.3f, 70d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 71d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 72d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 86d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 87d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 88d, HitResult.Miss },
|
||||
// new object[] { 9.3f, 89d, HitResult.Miss },
|
||||
new object[] { 9.3f, -69d, HitResult.Ok },
|
||||
new object[] { 9.3f, -70d, HitResult.Ok },
|
||||
new object[] { 9.3f, -71d, HitResult.Meh },
|
||||
@@ -402,13 +410,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -191d, HitResult.Miss },
|
||||
new object[] { 5f, -192d, HitResult.Miss },
|
||||
new object[] { 5f, 155d, HitResult.Ok },
|
||||
new object[] { 5f, 156d, HitResult.Miss },
|
||||
new object[] { 5f, 157d, HitResult.Miss },
|
||||
new object[] { 5f, 158d, HitResult.Miss },
|
||||
new object[] { 5f, 189d, HitResult.Miss },
|
||||
new object[] { 5f, 190d, HitResult.Miss },
|
||||
new object[] { 5f, 191d, HitResult.Miss },
|
||||
new object[] { 5f, 192d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 5f, 156d, HitResult.Miss },
|
||||
// new object[] { 5f, 157d, HitResult.Miss },
|
||||
// new object[] { 5f, 158d, HitResult.Miss },
|
||||
// new object[] { 5f, 189d, HitResult.Miss },
|
||||
// new object[] { 5f, 190d, HitResult.Miss },
|
||||
// new object[] { 5f, 191d, HitResult.Miss },
|
||||
// new object[] { 5f, 192d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_non_convert_double_time_test_cases =
|
||||
@@ -440,13 +449,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -205d, HitResult.Miss },
|
||||
new object[] { 5f, -206d, HitResult.Miss },
|
||||
new object[] { 5f, 167d, HitResult.Ok },
|
||||
new object[] { 5f, 168d, HitResult.Miss },
|
||||
new object[] { 5f, 169d, HitResult.Miss },
|
||||
new object[] { 5f, 170d, HitResult.Miss },
|
||||
new object[] { 5f, 203d, HitResult.Miss },
|
||||
new object[] { 5f, 204d, HitResult.Miss },
|
||||
new object[] { 5f, 205d, HitResult.Miss },
|
||||
new object[] { 5f, 206d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 5f, 168d, HitResult.Miss },
|
||||
// new object[] { 5f, 169d, HitResult.Miss },
|
||||
// new object[] { 5f, 170d, HitResult.Miss },
|
||||
// new object[] { 5f, 203d, HitResult.Miss },
|
||||
// new object[] { 5f, 204d, HitResult.Miss },
|
||||
// new object[] { 5f, 205d, HitResult.Miss },
|
||||
// new object[] { 5f, 206d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_non_convert_half_time_test_cases =
|
||||
@@ -478,13 +488,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
new object[] { 5f, -103d, HitResult.Miss },
|
||||
new object[] { 5f, -104d, HitResult.Miss },
|
||||
new object[] { 5f, 83d, HitResult.Ok },
|
||||
new object[] { 5f, 84d, HitResult.Miss },
|
||||
new object[] { 5f, 85d, HitResult.Miss },
|
||||
new object[] { 5f, 86d, HitResult.Miss },
|
||||
new object[] { 5f, 101d, HitResult.Miss },
|
||||
new object[] { 5f, 102d, HitResult.Miss },
|
||||
new object[] { 5f, 103d, HitResult.Miss },
|
||||
new object[] { 5f, 104d, HitResult.Miss },
|
||||
// coverage of broken "can't hit meh late" behaviour, which is intentionally not being reproduced
|
||||
// new object[] { 5f, 84d, HitResult.Miss },
|
||||
// new object[] { 5f, 85d, HitResult.Miss },
|
||||
// new object[] { 5f, 86d, HitResult.Miss },
|
||||
// new object[] { 5f, 101d, HitResult.Miss },
|
||||
// new object[] { 5f, 102d, HitResult.Miss },
|
||||
// new object[] { 5f, 103d, HitResult.Miss },
|
||||
// new object[] { 5f, 104d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private const double note_time = 300;
|
||||
@@ -517,6 +528,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
RunTest($@"SV2 single note @ OD{overallDifficulty}", beatmap, $@"SV2 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
||||
[TestCaseSource(nameof(score_v1_non_convert_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1NonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
@@ -544,6 +556,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
RunTest($@"SV1 single note @ OD{overallDifficulty}", beatmap, $@"SV1 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
||||
[TestCaseSource(nameof(score_v1_convert_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1Convert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
@@ -572,6 +585,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
RunTest($@"SV1 convert single note @ OD{overallDifficulty}", beatmap, $@"SV1 convert {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
||||
[TestCaseSource(nameof(score_v1_non_convert_hard_rock_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndHardRockNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
@@ -600,6 +614,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
RunTest($@"SV1+HR single note @ OD{overallDifficulty}", beatmap, $@"SV1+HR {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
||||
[TestCaseSource(nameof(score_v1_non_convert_easy_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndEasyNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
@@ -628,6 +643,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
RunTest($@"SV1+EZ single note @ OD{overallDifficulty}", beatmap, $@"SV1+EZ {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
||||
[TestCaseSource(nameof(score_v1_non_convert_double_time_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndDoubleTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
@@ -656,6 +672,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
RunTest($@"SV1+DT single note @ OD{overallDifficulty}", beatmap, $@"SV1+DT {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[Ignore("Tests expected to fail until stable's detailed treatment of hit windows in mania is reproduced.")]
|
||||
[TestCaseSource(nameof(score_v1_non_convert_half_time_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1AndHalfTimeNonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")]
|
||||
public partial class TestSceneReplayStability : ReplayStabilityTestScene
|
||||
{
|
||||
private static readonly object[][] test_cases =
|
||||
@@ -22,87 +21,79 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
// while round brackets `()` represent *open* or *exclusive* bounds.
|
||||
|
||||
// OD = 5 test cases.
|
||||
// PERFECT hit window is [ -19.4ms, 19.4ms]
|
||||
// GREAT hit window is [ -49.0ms, 49.0ms]
|
||||
// GOOD hit window is [ -82.0ms, 82.0ms]
|
||||
// OK hit window is [-112.0ms, 112.0ms]
|
||||
// MEH hit window is [-136.0ms, 136.0ms]
|
||||
// MISS hit window is [-173.0ms, 173.0ms]
|
||||
// PERFECT hit window is [ -19.5ms, 19.5ms]
|
||||
// GREAT hit window is [ -49.5ms, 49.5ms]
|
||||
// GOOD hit window is [ -82.5ms, 82.5ms]
|
||||
// OK hit window is [-112.5ms, 112.5ms]
|
||||
// MEH hit window is [-136.5ms, 136.5ms]
|
||||
// MISS hit window is [-173.5ms, 173.5ms]
|
||||
new object[] { 5f, -19d, HitResult.Perfect },
|
||||
new object[] { 5f, -19.2d, HitResult.Perfect },
|
||||
new object[] { 5f, -19.38d, HitResult.Perfect },
|
||||
// new object[] { 5f, -19.4d, HitResult.Perfect }, <- in theory this should work, in practice it does not (fails even before encode & rounding due to floating point precision issues)
|
||||
new object[] { 5f, -19.44d, HitResult.Great },
|
||||
new object[] { 5f, -19.7d, HitResult.Great },
|
||||
new object[] { 5f, -20d, HitResult.Great },
|
||||
new object[] { 5f, -48d, HitResult.Great },
|
||||
new object[] { 5f, -48.4d, HitResult.Great },
|
||||
new object[] { 5f, -48.7d, HitResult.Great },
|
||||
new object[] { 5f, -49d, HitResult.Great },
|
||||
new object[] { 5f, -49.2d, HitResult.Good },
|
||||
new object[] { 5f, -49.2d, HitResult.Great },
|
||||
new object[] { 5f, -49.7d, HitResult.Good },
|
||||
new object[] { 5f, -50d, HitResult.Good },
|
||||
new object[] { 5f, -81d, HitResult.Good },
|
||||
new object[] { 5f, -81.2d, HitResult.Good },
|
||||
new object[] { 5f, -81.7d, HitResult.Good },
|
||||
new object[] { 5f, -82d, HitResult.Good },
|
||||
new object[] { 5f, -82.2d, HitResult.Ok },
|
||||
new object[] { 5f, -82.2d, HitResult.Good },
|
||||
new object[] { 5f, -82.7d, HitResult.Ok },
|
||||
new object[] { 5f, -83d, HitResult.Ok },
|
||||
new object[] { 5f, -111d, HitResult.Ok },
|
||||
new object[] { 5f, -111.2d, HitResult.Ok },
|
||||
new object[] { 5f, -111.7d, HitResult.Ok },
|
||||
new object[] { 5f, -112d, HitResult.Ok },
|
||||
new object[] { 5f, -112.2d, HitResult.Meh },
|
||||
new object[] { 5f, -112.2d, HitResult.Ok },
|
||||
new object[] { 5f, -112.7d, HitResult.Meh },
|
||||
new object[] { 5f, -113d, HitResult.Meh },
|
||||
new object[] { 5f, -135d, HitResult.Meh },
|
||||
new object[] { 5f, -135.2d, HitResult.Meh },
|
||||
new object[] { 5f, -135.8d, HitResult.Meh },
|
||||
new object[] { 5f, -136d, HitResult.Meh },
|
||||
new object[] { 5f, -136.2d, HitResult.Miss },
|
||||
new object[] { 5f, -136.2d, HitResult.Meh },
|
||||
new object[] { 5f, -136.7d, HitResult.Miss },
|
||||
new object[] { 5f, -137d, HitResult.Miss },
|
||||
|
||||
// OD = 9.3 test cases.
|
||||
// PERFECT hit window is [ -14.67ms, 14.67ms]
|
||||
// GREAT hit window is [ -36.10ms, 36.10ms]
|
||||
// GOOD hit window is [ -69.10ms, 69.10ms]
|
||||
// OK hit window is [ -99.10ms, 99.10ms]
|
||||
// MEH hit window is [-123.10ms, 123.10ms]
|
||||
// MISS hit window is [-160.10ms, 160.10ms]
|
||||
// PERFECT hit window is [ -14.5ms, 14.5ms]
|
||||
// GREAT hit window is [ -36.5ms, 36.5ms]
|
||||
// GOOD hit window is [ -69.5ms, 69.5ms]
|
||||
// OK hit window is [ -99.5ms, 99.5ms]
|
||||
// MEH hit window is [-123.5ms, 123.5ms]
|
||||
// MISS hit window is [-160.5ms, 160.5ms]
|
||||
new object[] { 9.3f, 14d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 14.2d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 14.6d, HitResult.Perfect },
|
||||
// new object[] { 9.3f, 14.67d, HitResult.Perfect }, <- in theory this should work, in practice it does not (fails even before encode & rounding due to floating point precision issues)
|
||||
new object[] { 9.3f, 14.7d, HitResult.Great },
|
||||
new object[] { 9.3f, 15d, HitResult.Great },
|
||||
new object[] { 9.3f, 35d, HitResult.Great },
|
||||
new object[] { 9.3f, 35.3d, HitResult.Great },
|
||||
new object[] { 9.3f, 35.8d, HitResult.Great },
|
||||
new object[] { 9.3f, 36.05d, HitResult.Great },
|
||||
new object[] { 9.3f, 36.3d, HitResult.Good },
|
||||
new object[] { 9.3f, 36.3d, HitResult.Great },
|
||||
new object[] { 9.3f, 36.7d, HitResult.Good },
|
||||
new object[] { 9.3f, 37d, HitResult.Good },
|
||||
new object[] { 9.3f, 68d, HitResult.Good },
|
||||
new object[] { 9.3f, 68.4d, HitResult.Good },
|
||||
new object[] { 9.3f, 68.9d, HitResult.Good },
|
||||
new object[] { 9.3f, 69.07d, HitResult.Good },
|
||||
new object[] { 9.3f, 69.25d, HitResult.Ok },
|
||||
new object[] { 9.3f, 69.25d, HitResult.Good },
|
||||
new object[] { 9.3f, 69.85d, HitResult.Ok },
|
||||
new object[] { 9.3f, 70d, HitResult.Ok },
|
||||
new object[] { 9.3f, 98d, HitResult.Ok },
|
||||
new object[] { 9.3f, 98.3d, HitResult.Ok },
|
||||
new object[] { 9.3f, 98.6d, HitResult.Ok },
|
||||
new object[] { 9.3f, 99d, HitResult.Ok },
|
||||
new object[] { 9.3f, 99.3d, HitResult.Meh },
|
||||
new object[] { 9.3f, 99.3d, HitResult.Ok },
|
||||
new object[] { 9.3f, 99.7d, HitResult.Meh },
|
||||
new object[] { 9.3f, 100d, HitResult.Meh },
|
||||
new object[] { 9.3f, 122d, HitResult.Meh },
|
||||
new object[] { 9.3f, 122.34d, HitResult.Meh },
|
||||
new object[] { 9.3f, 122.57d, HitResult.Meh },
|
||||
new object[] { 9.3f, 123.04d, HitResult.Meh },
|
||||
new object[] { 9.3f, 123.45d, HitResult.Miss },
|
||||
new object[] { 9.3f, 123.45d, HitResult.Meh },
|
||||
new object[] { 9.3f, 123.95d, HitResult.Miss },
|
||||
new object[] { 9.3f, 124d, HitResult.Miss },
|
||||
};
|
||||
@@ -110,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestHitWindowStability(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 100;
|
||||
const double note_time = 300;
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
|
||||
@@ -42,14 +43,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Notes",
|
||||
Name = BeatmapStatisticStrings.Notes,
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = notes.ToString(),
|
||||
BarDisplayLength = notes / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hold Notes",
|
||||
Name = BeatmapStatisticStrings.HoldNotes,
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = holdNotes.ToString(),
|
||||
BarDisplayLength = holdNotes / (float)sum,
|
||||
|
||||
@@ -197,6 +197,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||
public override void OnKilled()
|
||||
{
|
||||
base.OnKilled();
|
||||
// flush the final state of holding on kill.
|
||||
// this matters because some skin implementations like legacy skin
|
||||
// insert drawables in the hierarchy that are not a child of this DHO
|
||||
// (see `LegacyBodyPiece` and related machinations with `lightContainer` being added at column level)
|
||||
isHolding.Value = Result.IsHolding(Time.Current);
|
||||
(bodyPiece.Drawable as IHoldNoteBody)?.Recycle();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -47,5 +48,8 @@ namespace osu.Game.Rulesets.Mania.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, keys, null, ReplayButtonState.None);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is ManiaReplayFrame maniaFrame && Time == maniaFrame.Time && Actions.SequenceEqual(maniaFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,30 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using System;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Scoring
|
||||
{
|
||||
public class ManiaHitWindows : HitWindows
|
||||
{
|
||||
private static readonly DifficultyRange perfect_window_range = new DifficultyRange(22.4D, 19.4D, 13.9D);
|
||||
private static readonly DifficultyRange great_window_range = new DifficultyRange(64, 49, 34);
|
||||
private static readonly DifficultyRange good_window_range = new DifficultyRange(97, 82, 67);
|
||||
private static readonly DifficultyRange ok_window_range = new DifficultyRange(127, 112, 97);
|
||||
private static readonly DifficultyRange meh_window_range = new DifficultyRange(151, 136, 121);
|
||||
private static readonly DifficultyRange miss_window_range = new DifficultyRange(188, 173, 158);
|
||||
|
||||
private readonly double multiplier;
|
||||
|
||||
private double perfect;
|
||||
private double great;
|
||||
private double good;
|
||||
private double ok;
|
||||
private double meh;
|
||||
private double miss;
|
||||
|
||||
public ManiaHitWindows()
|
||||
: this(1)
|
||||
{
|
||||
@@ -36,11 +51,41 @@ namespace osu.Game.Rulesets.Mania.Scoring
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DifficultyRange[] GetRanges() => base.GetRanges().Select(r =>
|
||||
new DifficultyRange(
|
||||
r.Result,
|
||||
r.Min * multiplier,
|
||||
r.Average * multiplier,
|
||||
r.Max * multiplier)).ToArray();
|
||||
public override void SetDifficulty(double difficulty)
|
||||
{
|
||||
perfect = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, perfect_window_range) * multiplier) + 0.5;
|
||||
great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, great_window_range) * multiplier) + 0.5;
|
||||
good = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, good_window_range) * multiplier) + 0.5;
|
||||
ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, ok_window_range) * multiplier) + 0.5;
|
||||
meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, meh_window_range) * multiplier) + 0.5;
|
||||
miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, miss_window_range) * multiplier) + 0.5;
|
||||
}
|
||||
|
||||
public override double WindowFor(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Perfect:
|
||||
return perfect;
|
||||
|
||||
case HitResult.Great:
|
||||
return great;
|
||||
|
||||
case HitResult.Good:
|
||||
return good;
|
||||
|
||||
case HitResult.Ok:
|
||||
return ok;
|
||||
|
||||
case HitResult.Meh:
|
||||
return meh;
|
||||
|
||||
case HitResult.Miss:
|
||||
return miss;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(result), result, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
@@ -36,22 +35,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
[Test]
|
||||
public void TestPlayfieldBasedSize()
|
||||
{
|
||||
ModFlashlight mod = new OsuModFlashlight();
|
||||
OsuModFlashlight flashlight;
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = mod,
|
||||
Mods = [flashlight = new OsuModFlashlight(), new OsuModBarrelRoll()],
|
||||
PassCondition = () =>
|
||||
{
|
||||
var flashlightOverlay = Player.DrawableRuleset.Overlays
|
||||
.ChildrenOfType<ModFlashlight<OsuHitObject>.Flashlight>()
|
||||
.First();
|
||||
|
||||
return Precision.AlmostEquals(mod.DefaultFlashlightSize * .5f, flashlightOverlay.GetSize());
|
||||
// the combo check is here because the flashlight radius decreases for the first time at 100 combo
|
||||
// and hardcoding it here eliminates the need to meddle in flashlight internals further by e.g. exposing `GetComboScaleFor()`
|
||||
return flashlightOverlay.GetSize() < flashlight.DefaultFlashlightSize && Player.GameplayState.ScoreProcessor.Combo.Value < 100;
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("adjust playfield scale", () =>
|
||||
Player.DrawableRuleset.Playfield.Scale = new Vector2(.5f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -13,7 +13,6 @@ using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
@@ -22,21 +21,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public partial class TestSceneOsuModRelax : OsuModTestScene
|
||||
{
|
||||
private readonly HitCircle hitObject;
|
||||
private readonly HitWindows hitWindows = new OsuHitWindows();
|
||||
|
||||
public TestSceneOsuModRelax()
|
||||
{
|
||||
hitWindows.SetDifficulty(9);
|
||||
|
||||
hitObject = new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(100, 100),
|
||||
HitWindows = hitWindows
|
||||
};
|
||||
}
|
||||
|
||||
protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModRelaxTestPlayer(CurrentTestData, AllowFail);
|
||||
|
||||
[Test]
|
||||
@@ -46,12 +30,21 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () => new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject> { hitObject }
|
||||
Difficulty = { OverallDifficulty = 9 },
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(100, 100),
|
||||
HitWindows = new OsuHitWindows()
|
||||
}
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2()),
|
||||
new OsuReplayFrame(hitObject.StartTime, hitObject.Position),
|
||||
new OsuReplayFrame(100, new Vector2(100)),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1
|
||||
});
|
||||
@@ -63,13 +56,22 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () => new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject> { hitObject }
|
||||
Difficulty = { OverallDifficulty = 9 },
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 1000,
|
||||
Position = new Vector2(100, 100),
|
||||
HitWindows = new OsuHitWindows()
|
||||
}
|
||||
}
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(0, new Vector2(hitObject.X - 22, hitObject.Y - 22)), // must be an edge hit for the cursor to not stay on the object for too long
|
||||
new OsuReplayFrame(hitObject.StartTime - OsuModRelax.RELAX_LENIENCY, new Vector2(hitObject.X - 22, hitObject.Y - 22)),
|
||||
new OsuReplayFrame(hitObject.StartTime, new Vector2(0)),
|
||||
new OsuReplayFrame(0, new Vector2(78, 78)), // must be an edge hit for the cursor to not stay on the object for too long
|
||||
new OsuReplayFrame(1000 - OsuModRelax.RELAX_LENIENCY, new Vector2(78, 78)),
|
||||
new OsuReplayFrame(1000, new Vector2(0)),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 1
|
||||
});
|
||||
|
||||
@@ -110,23 +110,23 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
new Spinner
|
||||
{
|
||||
StartTime = 0,
|
||||
Duration = 1000,
|
||||
Duration = 3000,
|
||||
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||
},
|
||||
new Slider
|
||||
{
|
||||
StartTime = 2500,
|
||||
StartTime = 4500,
|
||||
RepeatCount = 0,
|
||||
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero),
|
||||
new PathControlPoint(new Vector2(100, 0)),
|
||||
new PathControlPoint(new Vector2(200, 0)),
|
||||
})
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = 4500,
|
||||
StartTime = 10000,
|
||||
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -17,7 +17,6 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")]
|
||||
public partial class TestSceneLegacyReplayPlayback : LegacyReplayPlaybackTestScene
|
||||
{
|
||||
protected override Ruleset CreateRuleset() => new OsuRuleset();
|
||||
|
||||
@@ -14,26 +14,27 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneOsuHitObjectSamples)));
|
||||
|
||||
[TestCase("normal-hitnormal")]
|
||||
[TestCase("hitnormal")]
|
||||
public void TestDefaultCustomSampleFromBeatmap(string expectedSample)
|
||||
[TestCase("normal-hitnormal2", "normal-hitnormal")]
|
||||
[TestCase("hitnormal", "hitnormal")]
|
||||
public void TestDefaultCustomSampleFromBeatmap(string beatmapSkinSampleName, string userSkinSampleName)
|
||||
{
|
||||
SetupSkins(expectedSample, expectedSample);
|
||||
SetupSkins(beatmapSkinSampleName, userSkinSampleName);
|
||||
|
||||
CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu");
|
||||
|
||||
AssertBeatmapLookup(expectedSample);
|
||||
AssertBeatmapLookup(beatmapSkinSampleName);
|
||||
}
|
||||
|
||||
[TestCase("normal-hitnormal")]
|
||||
[TestCase("hitnormal")]
|
||||
public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample)
|
||||
[TestCase("", "normal-hitnormal")]
|
||||
[TestCase("normal-hitnormal", "normal-hitnormal")]
|
||||
[TestCase("", "hitnormal")]
|
||||
public void TestDefaultCustomSampleFromUserSkinFallback(string beatmapSkinSampleName, string userSkinSampleName)
|
||||
{
|
||||
SetupSkins(string.Empty, expectedSample);
|
||||
SetupSkins(beatmapSkinSampleName, userSkinSampleName);
|
||||
|
||||
CreateTestWithBeatmap("osu-hitobject-beatmap-custom-sample-bank.osu");
|
||||
|
||||
AssertUserLookup(expectedSample);
|
||||
AssertUserLookup(userSkinSampleName);
|
||||
}
|
||||
|
||||
[TestCase("normal-hitnormal2")]
|
||||
|
||||
@@ -78,6 +78,16 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddAssert("smoke button press recorded to replay", () => Player.Score.Replay.Frames.OfType<OsuReplayFrame>().Any(f => f.Actions.SequenceEqual([OsuAction.Smoke])));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPressAndReleaseOnSameFrame()
|
||||
{
|
||||
seekTo(0);
|
||||
AddStep("move cursor to circle", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield.HitObjectContainer.AliveObjects.Single()));
|
||||
AddStep("press X", () => InputManager.PressKey(Key.X));
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
AddAssert("right button press recorded to replay", () => Player.Score.Replay.Frames.OfType<OsuReplayFrame>().Any(f => f.Actions.SequenceEqual([OsuAction.RightButton])));
|
||||
}
|
||||
|
||||
private void seekTo(double time)
|
||||
{
|
||||
AddStep($"seek to {time}ms", () => Player.GameplayClockContainer.Seek(time));
|
||||
|
||||
@@ -13,7 +13,6 @@ using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")]
|
||||
public partial class TestSceneReplayStability : ReplayStabilityTestScene
|
||||
{
|
||||
private static readonly object[][] test_cases =
|
||||
@@ -23,53 +22,49 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
// while round brackets `()` represent *open* or *exclusive* bounds.
|
||||
|
||||
// OD = 5 test cases.
|
||||
// GREAT hit window is [ -50ms, 50ms]
|
||||
// OK hit window is [-100ms, 100ms]
|
||||
// MEH hit window is [-150ms, 150ms]
|
||||
// MISS hit window is [-400ms, 400ms]
|
||||
// GREAT hit window is [ -49.5ms, 49.5ms]
|
||||
// OK hit window is [ -99.5ms, 99.5ms]
|
||||
// MEH hit window is [-149.5ms, 149.5ms]
|
||||
new object[] { 5f, 49d, HitResult.Great },
|
||||
new object[] { 5f, 49.2d, HitResult.Great },
|
||||
new object[] { 5f, 49.7d, HitResult.Great },
|
||||
new object[] { 5f, 50d, HitResult.Great },
|
||||
new object[] { 5f, 49.7d, HitResult.Ok },
|
||||
new object[] { 5f, 50d, HitResult.Ok },
|
||||
new object[] { 5f, 50.4d, HitResult.Ok },
|
||||
new object[] { 5f, 50.9d, HitResult.Ok },
|
||||
new object[] { 5f, 51d, HitResult.Ok },
|
||||
new object[] { 5f, 99d, HitResult.Ok },
|
||||
new object[] { 5f, 99.2d, HitResult.Ok },
|
||||
new object[] { 5f, 99.7d, HitResult.Ok },
|
||||
new object[] { 5f, 100d, HitResult.Ok },
|
||||
new object[] { 5f, 99.7d, HitResult.Meh },
|
||||
new object[] { 5f, 100d, HitResult.Meh },
|
||||
new object[] { 5f, 100.4d, HitResult.Meh },
|
||||
new object[] { 5f, 100.9d, HitResult.Meh },
|
||||
new object[] { 5f, 101d, HitResult.Meh },
|
||||
new object[] { 5f, 149d, HitResult.Meh },
|
||||
new object[] { 5f, 149.2d, HitResult.Meh },
|
||||
new object[] { 5f, 149.7d, HitResult.Meh },
|
||||
new object[] { 5f, 150d, HitResult.Meh },
|
||||
new object[] { 5f, 149.7d, HitResult.Miss },
|
||||
new object[] { 5f, 150d, HitResult.Miss },
|
||||
new object[] { 5f, 150.4d, HitResult.Miss },
|
||||
new object[] { 5f, 150.9d, HitResult.Miss },
|
||||
new object[] { 5f, 151d, HitResult.Miss },
|
||||
|
||||
// OD = 5.7 test cases.
|
||||
// GREAT hit window is [ -45.8ms, 45.8ms]
|
||||
// OK hit window is [ -94.4ms, 94.4ms]
|
||||
// MEH hit window is [-143.0ms, 143.0ms]
|
||||
// MISS hit window is [-400.0ms, 400.0ms]
|
||||
new object[] { 5.7f, 45d, HitResult.Great },
|
||||
new object[] { 5.7f, 45.2d, HitResult.Great },
|
||||
new object[] { 5.7f, 45.8d, HitResult.Great },
|
||||
new object[] { 5.7f, 45.9d, HitResult.Ok },
|
||||
new object[] { 5.7f, 46d, HitResult.Ok },
|
||||
new object[] { 5.7f, 46.4d, HitResult.Ok },
|
||||
new object[] { 5.7f, 94d, HitResult.Ok },
|
||||
new object[] { 5.7f, 94.2d, HitResult.Ok },
|
||||
new object[] { 5.7f, 94.4d, HitResult.Ok },
|
||||
new object[] { 5.7f, 94.48d, HitResult.Ok },
|
||||
new object[] { 5.7f, 94.9d, HitResult.Meh },
|
||||
new object[] { 5.7f, 95d, HitResult.Meh },
|
||||
new object[] { 5.7f, 95.4d, HitResult.Meh },
|
||||
// GREAT hit window is [ -44.5ms, 44.5ms]
|
||||
// OK hit window is [ -93.5ms, 93.5ms]
|
||||
// MEH hit window is [-142.5ms, 142.5ms]
|
||||
new object[] { 5.7f, 44d, HitResult.Great },
|
||||
new object[] { 5.7f, 44.2d, HitResult.Great },
|
||||
new object[] { 5.7f, 44.8d, HitResult.Ok },
|
||||
new object[] { 5.7f, 45d, HitResult.Ok },
|
||||
new object[] { 5.7f, 45.4d, HitResult.Ok },
|
||||
new object[] { 5.7f, 93d, HitResult.Ok },
|
||||
new object[] { 5.7f, 93.4d, HitResult.Ok },
|
||||
new object[] { 5.7f, 93.9d, HitResult.Meh },
|
||||
new object[] { 5.7f, 94d, HitResult.Meh },
|
||||
new object[] { 5.7f, 94.4d, HitResult.Meh },
|
||||
new object[] { 5.7f, 142d, HitResult.Meh },
|
||||
new object[] { 5.7f, 142.7d, HitResult.Meh },
|
||||
new object[] { 5.7f, 143d, HitResult.Meh },
|
||||
new object[] { 5.7f, 142.2d, HitResult.Meh },
|
||||
new object[] { 5.7f, 142.7d, HitResult.Miss },
|
||||
new object[] { 5.7f, 143d, HitResult.Miss },
|
||||
new object[] { 5.7f, 143.4d, HitResult.Miss },
|
||||
new object[] { 5.7f, 143.9d, HitResult.Miss },
|
||||
new object[] { 5.7f, 144d, HitResult.Miss },
|
||||
|
||||
@@ -484,6 +484,47 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
AddAssert("no miss judgements recorded", () => judgementResults.All(r => r.Type.IsHit()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sliders are common to by 1/2 or 1/4 beat length in order to place the circle on the next beat.
|
||||
/// This tests a user pressing the next circle in the window between the last tick and the end of the slider (<see cref="SliderEventGenerator.TAIL_LENIENCY"/>).
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestHitNextCircleDuringTailLeniency()
|
||||
{
|
||||
const double bpm = 240;
|
||||
const double beat_length = 60000 / bpm;
|
||||
const double slider_start = time_slider_start;
|
||||
const double slider_end = slider_start + beat_length;
|
||||
const double last_tick_time = slider_end + SliderEventGenerator.TAIL_LENIENCY;
|
||||
const double next_circle_time = slider_end + beat_length / 4;
|
||||
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame { Position = Vector2.Zero, Actions = { OsuAction.LeftButton }, Time = time_slider_start },
|
||||
new OsuReplayFrame { Position = new Vector2(140, 0), Actions = { OsuAction.RightButton }, Time = last_tick_time + 20 },
|
||||
},
|
||||
[
|
||||
new Slider
|
||||
{
|
||||
StartTime = slider_start,
|
||||
Position = new Vector2(0, 0),
|
||||
TickDistanceMultiplier = 10, // no ticks
|
||||
Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
{
|
||||
Vector2.Zero,
|
||||
new Vector2(100, 0),
|
||||
}, 100),
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = next_circle_time,
|
||||
Position = new Vector2(140, 0)
|
||||
}
|
||||
], bpm: bpm);
|
||||
|
||||
AddAssert("all judgements are hit", () => judgementResults.All(j => j.Type.IsHit()));
|
||||
}
|
||||
|
||||
private void assertAllMaxJudgements()
|
||||
{
|
||||
AddAssert("All judgements max", () =>
|
||||
@@ -522,6 +563,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}, slider_path_length),
|
||||
};
|
||||
|
||||
performTest(frames, [slider], bpm, tickRate);
|
||||
}
|
||||
|
||||
private void performTest(List<ReplayFrame> frames, List<OsuHitObject> objects, double? bpm = null, int? tickRate = null)
|
||||
{
|
||||
AddStep("load player", () =>
|
||||
{
|
||||
var cpi = new ControlPointInfo();
|
||||
@@ -531,7 +577,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap<OsuHitObject>
|
||||
{
|
||||
HitObjects = { slider },
|
||||
HitObjects = objects,
|
||||
BeatmapInfo =
|
||||
{
|
||||
Difficulty = new BeatmapDifficulty
|
||||
|
||||
@@ -52,8 +52,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 99, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 99, slider_end_position, OsuAction.LeftButton),
|
||||
});
|
||||
|
||||
assertHeadJudgement(HitResult.Ok);
|
||||
@@ -70,8 +70,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 100, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 100, slider_end_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 99, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 99, slider_end_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.SliderVelocityMultiplier = 2;
|
||||
@@ -91,8 +91,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_end_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.TickDistanceMultiplier = 0.2f;
|
||||
@@ -116,8 +116,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_end_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_end_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.SliderVelocityMultiplier = 2;
|
||||
@@ -165,8 +165,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.LINEAR, new[]
|
||||
@@ -195,8 +195,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.LINEAR, new[]
|
||||
@@ -224,8 +224,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
@@ -259,8 +259,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
@@ -289,8 +289,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_start_position, OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
@@ -320,8 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
performTest(new List<ReplayFrame>
|
||||
{
|
||||
new OsuReplayFrame(time_slider_start + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 150, slider_start_position - new Vector2(20), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_start + 149, slider_start_position - new Vector2(20), OsuAction.LeftButton),
|
||||
new OsuReplayFrame(time_slider_end + 149, slider_start_position - new Vector2(20), OsuAction.LeftButton),
|
||||
}, s =>
|
||||
{
|
||||
s.Path = new SliderPath(PathType.PERFECT_CURVE, new[]
|
||||
|
||||
@@ -22,6 +22,7 @@ using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Tests;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
@@ -107,6 +108,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest]
|
||||
public void TestVibrateWithoutSpinningOnCentreWithDoubleTime()
|
||||
{
|
||||
List<ReplayFrame> frames = new List<ReplayFrame>();
|
||||
|
||||
@@ -476,15 +476,24 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
private class TestHitWindows : HitWindows
|
||||
{
|
||||
private static readonly DifficultyRange[] ranges =
|
||||
{
|
||||
new DifficultyRange(HitResult.Great, 500, 500, 500),
|
||||
new DifficultyRange(HitResult.Miss, early_miss_window, early_miss_window, early_miss_window),
|
||||
};
|
||||
|
||||
public override bool IsHitResultAllowed(HitResult result) => result == HitResult.Great || result == HitResult.Miss;
|
||||
|
||||
protected override DifficultyRange[] GetRanges() => ranges;
|
||||
public override void SetDifficulty(double difficulty) { }
|
||||
|
||||
public override double WindowFor(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Great:
|
||||
return 500;
|
||||
|
||||
case HitResult.Miss:
|
||||
return early_miss_window;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(result), result, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private partial class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
@@ -22,21 +23,21 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = "Circles",
|
||||
Name = BeatmapStatisticStrings.Circles,
|
||||
Content = circles.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
BarDisplayLength = circles / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = "Sliders",
|
||||
Name = BeatmapStatisticStrings.Sliders,
|
||||
Content = sliders.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
BarDisplayLength = sliders / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Spinners",
|
||||
Name = BeatmapStatisticStrings.Spinners,
|
||||
Content = spinners.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
BarDisplayLength = Math.Min(spinners / 10f, 1),
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
if (!IsExactlyOneSettingChanged(CircleSize, ApproachRate, OverallDifficulty, DrainRate))
|
||||
return string.Empty;
|
||||
|
||||
if (!CircleSize.IsDefault) return format("CS", CircleSize);
|
||||
|
||||
@@ -19,7 +19,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
internal class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
public class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
|
||||
{
|
||||
public override string Name => "Magnetised";
|
||||
public override string Acronym => "MG";
|
||||
|
||||
@@ -115,10 +115,6 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
#region Reduce AR (IApplicableToDifficulty)
|
||||
|
||||
public void ReadFromDifficulty(IBeatmapDifficultyInfo difficulty)
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyToDifficulty(BeatmapDifficulty difficulty)
|
||||
{
|
||||
// Decrease AR to increase preempt time
|
||||
|
||||
@@ -21,12 +21,12 @@ namespace osu.Game.Rulesets.Osu.Objects
|
||||
/// <summary>
|
||||
/// The RPM required to clear the spinner at ODs [ 0, 5, 10 ].
|
||||
/// </summary>
|
||||
private static readonly (int min, int mid, int max) clear_rpm_range = (90, 150, 225);
|
||||
private static readonly DifficultyRange clear_rpm_range = new DifficultyRange(90, 150, 225);
|
||||
|
||||
/// <summary>
|
||||
/// The RPM required to complete the spinner and receive full score at ODs [ 0, 5, 10 ].
|
||||
/// </summary>
|
||||
private static readonly (int min, int mid, int max) complete_rpm_range = (250, 380, 430);
|
||||
private static readonly DifficultyRange complete_rpm_range = new DifficultyRange(250, 380, 430);
|
||||
|
||||
public double EndTime
|
||||
{
|
||||
|
||||
@@ -373,10 +373,9 @@ namespace osu.Game.Rulesets.Osu
|
||||
preempt /= rate;
|
||||
adjustedDifficulty.ApproachRate = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(preempt, OsuHitObject.PREEMPT_MAX, OsuHitObject.PREEMPT_MID, OsuHitObject.PREEMPT_MIN);
|
||||
|
||||
var greatHitWindowRange = OsuHitWindows.OSU_RANGES.Single(range => range.Result == HitResult.Great);
|
||||
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, OsuHitWindows.GREAT_WINDOW_RANGE);
|
||||
greatHitWindow /= rate;
|
||||
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, OsuHitWindows.GREAT_WINDOW_RANGE);
|
||||
|
||||
return adjustedDifficulty;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -47,5 +48,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is OsuReplayFrame osuFrame && Time == osuFrame.Time && Position == osuFrame.Position && Actions.SequenceEqual(osuFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Scoring
|
||||
{
|
||||
public class OsuHitWindows : HitWindows
|
||||
{
|
||||
public static readonly DifficultyRange GREAT_WINDOW_RANGE = new DifficultyRange(80, 50, 20);
|
||||
public static readonly DifficultyRange OK_WINDOW_RANGE = new DifficultyRange(140, 100, 60);
|
||||
public static readonly DifficultyRange MEH_WINDOW_RANGE = new DifficultyRange(200, 150, 100);
|
||||
|
||||
/// <summary>
|
||||
/// osu! ruleset has a fixed miss window regardless of difficulty settings.
|
||||
/// </summary>
|
||||
public const double MISS_WINDOW = 400;
|
||||
|
||||
internal static readonly DifficultyRange[] OSU_RANGES =
|
||||
{
|
||||
new DifficultyRange(HitResult.Great, 80, 50, 20),
|
||||
new DifficultyRange(HitResult.Ok, 140, 100, 60),
|
||||
new DifficultyRange(HitResult.Meh, 200, 150, 100),
|
||||
new DifficultyRange(HitResult.Miss, MISS_WINDOW, MISS_WINDOW, MISS_WINDOW),
|
||||
};
|
||||
private double great;
|
||||
private double ok;
|
||||
private double meh;
|
||||
|
||||
public override bool IsHitResultAllowed(HitResult result)
|
||||
{
|
||||
@@ -34,6 +36,32 @@ namespace osu.Game.Rulesets.Osu.Scoring
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DifficultyRange[] GetRanges() => OSU_RANGES;
|
||||
public override void SetDifficulty(double difficulty)
|
||||
{
|
||||
great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, GREAT_WINDOW_RANGE)) - 0.5;
|
||||
ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, OK_WINDOW_RANGE)) - 0.5;
|
||||
meh = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, MEH_WINDOW_RANGE)) - 0.5;
|
||||
}
|
||||
|
||||
public override double WindowFor(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Great:
|
||||
return great;
|
||||
|
||||
case HitResult.Ok:
|
||||
return ok;
|
||||
|
||||
case HitResult.Meh:
|
||||
return meh;
|
||||
|
||||
case HitResult.Miss:
|
||||
return MISS_WINDOW;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(result), result, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,10 +44,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
|
||||
SnakingOut.BindTo(configSnakingOut);
|
||||
|
||||
BorderColour = skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
||||
BorderColour = GetBorderColour(skin);
|
||||
}
|
||||
|
||||
protected virtual Color4 GetBodyAccentColour(ISkinSource skin, Color4 hitObjectAccentColour) =>
|
||||
skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? hitObjectAccentColour;
|
||||
protected virtual Color4 GetBorderColour(ISkinSource skin) => Color4.White;
|
||||
|
||||
protected virtual Color4 GetBodyAccentColour(ISkinSource skin, Color4 hitObjectAccentColour) => hitObjectAccentColour;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
{
|
||||
protected override DrawableSliderPath CreateSliderPath() => new LegacyDrawableSliderPath();
|
||||
|
||||
protected override Color4 GetBorderColour(ISkinSource skin)
|
||||
=> skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderBorder)?.Value ?? Color4.White;
|
||||
|
||||
protected override Color4 GetBodyAccentColour(ISkinSource skin, Color4 hitObjectAccentColour)
|
||||
{
|
||||
// legacy skins use a constant value for slider track alpha, regardless of the source colour.
|
||||
return base.GetBodyAccentColour(skin, hitObjectAccentColour).Opacity(0.7f);
|
||||
}
|
||||
=> (skin.GetConfig<OsuSkinColour, Color4>(OsuSkinColour.SliderTrackOverride)?.Value ?? hitObjectAccentColour).Opacity(0.7f);
|
||||
|
||||
private partial class LegacyDrawableSliderPath : DrawableSliderPath
|
||||
{
|
||||
|
||||
@@ -171,13 +171,13 @@ namespace osu.Game.Rulesets.Taiko.Tests.Judgements
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 6 });
|
||||
beatmap.ControlPointInfo.Add(0, new EffectControlPoint { ScrollSpeed = 10 });
|
||||
|
||||
var hitWindows = new HitWindows();
|
||||
var hitWindows = new DefaultHitWindows();
|
||||
hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty);
|
||||
|
||||
PerformTest(new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(0),
|
||||
new TaikoReplayFrame(hit_time - hitWindows.WindowFor(HitResult.Great), TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(hit_time - (hitWindows.WindowFor(HitResult.Great) + 0.1), TaikoAction.LeftCentre),
|
||||
}, beatmap);
|
||||
|
||||
AssertJudgementCount(1);
|
||||
|
||||
@@ -148,5 +148,96 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 8 && Player.ScoreProcessor.Accuracy.Value == 1
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Regression tests a case of 1/3rd conversion where there are exactly div-3 number of hitobjects.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestOnlyOneThirdConversion()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModSimplifiedRhythm
|
||||
{
|
||||
OneThirdConversion = { Value = true },
|
||||
},
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () => new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Hit { StartTime = 1000, Type = HitType.Centre },
|
||||
new Hit { StartTime = 1333, Type = HitType.Centre }, // mod removes this
|
||||
new Hit { StartTime = 1666, Type = HitType.Centre }, // mod moves this to 1500
|
||||
new Hit { StartTime = 2000, Type = HitType.Centre },
|
||||
new Hit { StartTime = 2333, Type = HitType.Centre }, // mod removes this
|
||||
new Hit { StartTime = 2666, Type = HitType.Centre }, // mod moves this to 2500
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(1200),
|
||||
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(1700),
|
||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(2200),
|
||||
new TaikoReplayFrame(2500, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(2700),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 4 && Player.ScoreProcessor.Accuracy.Value == 1
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Regression tests a case of 1/6th conversion where there are exactly div-6 number of hitobjects.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestOnlyOneSixthConversion() => CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new TaikoModSimplifiedRhythm
|
||||
{
|
||||
OneSixthConversion = { Value = true }
|
||||
},
|
||||
Autoplay = false,
|
||||
CreateBeatmap = () => new Beatmap
|
||||
{
|
||||
HitObjects = new List<HitObject>
|
||||
{
|
||||
new Hit { StartTime = 1000, Type = HitType.Centre },
|
||||
new Hit { StartTime = 1166, Type = HitType.Centre }, // mod removes this
|
||||
new Hit { StartTime = 1333, Type = HitType.Centre }, // mod moves this to 1250
|
||||
new Hit { StartTime = 1500, Type = HitType.Centre },
|
||||
new Hit { StartTime = 1666, Type = HitType.Centre }, // mod removes this
|
||||
new Hit { StartTime = 1833, Type = HitType.Centre }, // mod moves this to 1750
|
||||
new Hit { StartTime = 2000, Type = HitType.Centre },
|
||||
new Hit { StartTime = 2166, Type = HitType.Centre }, // mod removes this
|
||||
new Hit { StartTime = 2333, Type = HitType.Centre }, // mod moves this to 2250
|
||||
new Hit { StartTime = 2500, Type = HitType.Centre },
|
||||
new Hit { StartTime = 2666, Type = HitType.Centre }, // mod removes this
|
||||
new Hit { StartTime = 2833, Type = HitType.Centre }, // mod moves this to 2750
|
||||
},
|
||||
},
|
||||
ReplayFrames = new List<ReplayFrame>
|
||||
{
|
||||
new TaikoReplayFrame(1000, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(1200),
|
||||
new TaikoReplayFrame(1250, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(1450),
|
||||
new TaikoReplayFrame(1500, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(1600),
|
||||
new TaikoReplayFrame(1750, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(1800),
|
||||
new TaikoReplayFrame(2000, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(2200),
|
||||
new TaikoReplayFrame(2250, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(2450),
|
||||
new TaikoReplayFrame(2500, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(2600),
|
||||
new TaikoReplayFrame(2750, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(2800),
|
||||
},
|
||||
PassCondition = () => Player.ScoreProcessor.Combo.Value == 8 && Player.ScoreProcessor.Accuracy.Value == 1
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
[Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")]
|
||||
public partial class TestSceneLegacyReplayPlayback : LegacyReplayPlaybackTestScene
|
||||
{
|
||||
protected override string? ExportLocation => null;
|
||||
@@ -177,7 +176,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new TaikoModHardRock()]
|
||||
Mods = [new TaikoModEasy()]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Tests
|
||||
{
|
||||
[Ignore("These tests are expected to fail until an acceptable solution for various replay playback issues concerning rounding of replay frame times & hit windows is found.")]
|
||||
public partial class TestSceneReplayStability : ReplayStabilityTestScene
|
||||
{
|
||||
private static readonly object[][] test_cases =
|
||||
@@ -22,40 +21,38 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
// while round brackets `()` represent *open* or *exclusive* bounds.
|
||||
|
||||
// OD = 5 test cases.
|
||||
// GREAT hit window is [-35ms, 35ms]
|
||||
// OK hit window is [-80ms, 80ms]
|
||||
// MISS hit window is [-95ms, 95ms]
|
||||
// GREAT hit window is [-34.5ms, 34.5ms]
|
||||
// OK hit window is [-79.5ms, 79.5ms]
|
||||
// MISS hit window is [-94.5ms, 94.5ms]
|
||||
new object[] { 5f, -34d, HitResult.Great },
|
||||
new object[] { 5f, -34.2d, HitResult.Great },
|
||||
new object[] { 5f, -34.7d, HitResult.Great },
|
||||
new object[] { 5f, -35d, HitResult.Great },
|
||||
new object[] { 5f, -34.7d, HitResult.Ok },
|
||||
new object[] { 5f, -35d, HitResult.Ok },
|
||||
new object[] { 5f, -35.2d, HitResult.Ok },
|
||||
new object[] { 5f, -35.8d, HitResult.Ok },
|
||||
new object[] { 5f, -36d, HitResult.Ok },
|
||||
new object[] { 5f, -79d, HitResult.Ok },
|
||||
new object[] { 5f, -79.3d, HitResult.Ok },
|
||||
new object[] { 5f, -79.7d, HitResult.Ok },
|
||||
new object[] { 5f, -80d, HitResult.Ok },
|
||||
new object[] { 5f, -79.7d, HitResult.Miss },
|
||||
new object[] { 5f, -80d, HitResult.Miss },
|
||||
new object[] { 5f, -80.2d, HitResult.Miss },
|
||||
new object[] { 5f, -80.8d, HitResult.Miss },
|
||||
new object[] { 5f, -81d, HitResult.Miss },
|
||||
|
||||
// OD = 7.8 test cases.
|
||||
// GREAT hit window is [-26.6ms, 26.6ms]
|
||||
// OK hit window is [-63.2ms, 63.2ms]
|
||||
// MISS hit window is [-81.0ms, 81.0ms]
|
||||
new object[] { 7.8f, -26d, HitResult.Great },
|
||||
new object[] { 7.8f, -26.4d, HitResult.Great },
|
||||
new object[] { 7.8f, -26.59d, HitResult.Great },
|
||||
new object[] { 7.8f, -26.8d, HitResult.Ok },
|
||||
new object[] { 7.8f, -27d, HitResult.Ok },
|
||||
new object[] { 7.8f, -27.1d, HitResult.Ok },
|
||||
new object[] { 7.8f, -63d, HitResult.Ok },
|
||||
new object[] { 7.8f, -63.18d, HitResult.Ok },
|
||||
new object[] { 7.8f, -63.4d, HitResult.Ok },
|
||||
new object[] { 7.8f, -63.7d, HitResult.Miss },
|
||||
new object[] { 7.8f, -64d, HitResult.Miss },
|
||||
new object[] { 7.8f, -64.2d, HitResult.Miss },
|
||||
// GREAT hit window is [-25.5ms, 25.5ms]
|
||||
// OK hit window is [-62.5ms, 62.5ms]
|
||||
// MISS hit window is [-80.5ms, 80.5ms]
|
||||
new object[] { 7.8f, -25d, HitResult.Great },
|
||||
new object[] { 7.8f, -25.4d, HitResult.Great },
|
||||
new object[] { 7.8f, -25.8d, HitResult.Ok },
|
||||
new object[] { 7.8f, -26d, HitResult.Ok },
|
||||
new object[] { 7.8f, -26.1d, HitResult.Ok },
|
||||
new object[] { 7.8f, -62d, HitResult.Ok },
|
||||
new object[] { 7.8f, -62.4d, HitResult.Ok },
|
||||
new object[] { 7.8f, -62.7d, HitResult.Miss },
|
||||
new object[] { 7.8f, -63d, HitResult.Miss },
|
||||
new object[] { 7.8f, -63.2d, HitResult.Miss },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
|
||||
@@ -14,26 +14,27 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
||||
|
||||
protected override IResourceStore<byte[]> RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples)));
|
||||
|
||||
[TestCase("taiko-normal-hitnormal")]
|
||||
[TestCase("hitnormal")]
|
||||
public void TestDefaultCustomSampleFromBeatmap(string expectedSample)
|
||||
[TestCase("taiko-normal-hitnormal2", "taiko-normal-hitnormal")]
|
||||
[TestCase("hitnormal", "hitnormal")]
|
||||
public void TestDefaultCustomSampleFromBeatmap(string beatmapSkinSampleName, string userSkinSampleName)
|
||||
{
|
||||
SetupSkins(expectedSample, expectedSample);
|
||||
SetupSkins(beatmapSkinSampleName, userSkinSampleName);
|
||||
|
||||
CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu");
|
||||
|
||||
AssertBeatmapLookup(expectedSample);
|
||||
AssertBeatmapLookup(beatmapSkinSampleName);
|
||||
}
|
||||
|
||||
[TestCase("taiko-normal-hitnormal")]
|
||||
[TestCase("hitnormal")]
|
||||
public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample)
|
||||
[TestCase("", "taiko-normal-hitnormal")]
|
||||
[TestCase("taiko-normal-hitnormal", "taiko-normal-hitnormal")]
|
||||
[TestCase("", "hitnormal")]
|
||||
public void TestDefaultCustomSampleFromUserSkinFallback(string beatmapSkinSampleName, string userSkinSampleName)
|
||||
{
|
||||
SetupSkins(string.Empty, expectedSample);
|
||||
SetupSkins(beatmapSkinSampleName, userSkinSampleName);
|
||||
|
||||
CreateTestWithBeatmap("taiko-hitobject-beatmap-custom-sample-bank.osu");
|
||||
|
||||
AssertUserLookup(expectedSample);
|
||||
AssertUserLookup(userSkinSampleName);
|
||||
}
|
||||
|
||||
[TestCase("taiko-normal-hitnormal2")]
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
@@ -22,21 +23,21 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hits",
|
||||
Name = BeatmapStatisticStrings.Hits,
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = hits.ToString(),
|
||||
BarDisplayLength = hits / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Drumrolls",
|
||||
Name = BeatmapStatisticStrings.Drumrolls,
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = drumRolls.ToString(),
|
||||
BarDisplayLength = drumRolls / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Swells",
|
||||
Name = BeatmapStatisticStrings.Swells,
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
Content = swells.ToString(),
|
||||
BarDisplayLength = Math.Min(swells / 10f, 1),
|
||||
|
||||
@@ -25,16 +25,16 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
{
|
||||
get
|
||||
{
|
||||
if (UserAdjustedSettingsCount != 1)
|
||||
if (!IsExactlyOneSettingChanged(ScrollSpeed, OverallDifficulty, DrainRate))
|
||||
return string.Empty;
|
||||
|
||||
if (!ScrollSpeed.IsDefault) return format("SC", ScrollSpeed);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate);
|
||||
if (!ScrollSpeed.IsDefault) return format("SC", ScrollSpeed, 2);
|
||||
if (!OverallDifficulty.IsDefault) return format("OD", OverallDifficulty, 1);
|
||||
if (!DrainRate.IsDefault) return format("HP", DrainRate, 1);
|
||||
|
||||
return string.Empty;
|
||||
|
||||
string format(string acronym, DifficultyBindable bindable) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(1)}";
|
||||
string format(string acronym, DifficultyBindable bindable, int digits) => $"{acronym}{bindable.Value!.Value.ToStandardFormattedString(digits)}";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
var taikoBeatmap = (TaikoBeatmap)beatmap;
|
||||
var controlPointInfo = taikoBeatmap.ControlPointInfo;
|
||||
|
||||
Hit[] hits = taikoBeatmap.HitObjects.Where(obj => obj is Hit).Cast<Hit>().ToArray();
|
||||
Hit[] hits = taikoBeatmap.HitObjects.OfType<Hit>().ToArray();
|
||||
|
||||
if (hits.Length == 0)
|
||||
return;
|
||||
@@ -61,10 +61,10 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
if (inPattern)
|
||||
{
|
||||
// pattern continues
|
||||
if (snapValue == baseRhythm) continue;
|
||||
if (snapValue == baseRhythm)
|
||||
continue;
|
||||
|
||||
inPattern = false;
|
||||
|
||||
processPattern(i);
|
||||
}
|
||||
else
|
||||
@@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko.Mods
|
||||
if (indexInPattern % 3 == 1)
|
||||
taikoBeatmap.HitObjects.Remove(hits[j]);
|
||||
else if (indexInPattern % 3 == 2)
|
||||
hits[j].StartTime = hits[j + 1].StartTime - controlPointInfo.TimingPointAt(hits[j].StartTime).BeatLength / adjustedRhythm;
|
||||
hits[j].StartTime = hits[j - 2].StartTime + controlPointInfo.TimingPointAt(hits[j].StartTime).BeatLength / adjustedRhythm;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
@@ -42,5 +43,8 @@ namespace osu.Game.Rulesets.Taiko.Replays
|
||||
|
||||
return new LegacyReplayFrame(Time, null, null, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TaikoReplayFrame taikoFrame && Time == taikoFrame.Time && Actions.SequenceEqual(taikoFrame.Actions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Scoring
|
||||
{
|
||||
public class TaikoHitWindows : HitWindows
|
||||
{
|
||||
internal static readonly DifficultyRange[] TAIKO_RANGES =
|
||||
{
|
||||
new DifficultyRange(HitResult.Great, 50, 35, 20),
|
||||
new DifficultyRange(HitResult.Ok, 120, 80, 50),
|
||||
new DifficultyRange(HitResult.Miss, 135, 95, 70),
|
||||
};
|
||||
public static readonly DifficultyRange GREAT_WINDOW_RANGE = new DifficultyRange(50, 35, 20);
|
||||
public static readonly DifficultyRange OK_WINDOW_RANGE = new DifficultyRange(120, 80, 50);
|
||||
public static readonly DifficultyRange MISS_WINDOW_RANGE = new DifficultyRange(135, 95, 70);
|
||||
|
||||
private double great;
|
||||
private double ok;
|
||||
private double miss;
|
||||
|
||||
public override bool IsHitResultAllowed(HitResult result)
|
||||
{
|
||||
@@ -27,6 +30,29 @@ namespace osu.Game.Rulesets.Taiko.Scoring
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DifficultyRange[] GetRanges() => TAIKO_RANGES;
|
||||
public override void SetDifficulty(double difficulty)
|
||||
{
|
||||
great = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, GREAT_WINDOW_RANGE)) - 0.5;
|
||||
ok = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, OK_WINDOW_RANGE)) - 0.5;
|
||||
miss = Math.Floor(IBeatmapDifficultyInfo.DifficultyRange(difficulty, MISS_WINDOW_RANGE)) - 0.5;
|
||||
}
|
||||
|
||||
public override double WindowFor(HitResult result)
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case HitResult.Great:
|
||||
return great;
|
||||
|
||||
case HitResult.Ok:
|
||||
return ok;
|
||||
|
||||
case HitResult.Miss:
|
||||
return miss;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(result), result, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,10 +274,9 @@ namespace osu.Game.Rulesets.Taiko
|
||||
{
|
||||
BeatmapDifficulty adjustedDifficulty = new BeatmapDifficulty(difficulty);
|
||||
|
||||
var greatHitWindowRange = TaikoHitWindows.TAIKO_RANGES.Single(range => range.Result == HitResult.Great);
|
||||
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
double greatHitWindow = IBeatmapDifficultyInfo.DifficultyRange(adjustedDifficulty.OverallDifficulty, TaikoHitWindows.GREAT_WINDOW_RANGE);
|
||||
greatHitWindow /= rate;
|
||||
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, greatHitWindowRange.Min, greatHitWindowRange.Average, greatHitWindowRange.Max);
|
||||
adjustedDifficulty.OverallDifficulty = (float)IBeatmapDifficultyInfo.InverseDifficultyRange(greatHitWindow, TaikoHitWindows.GREAT_WINDOW_RANGE);
|
||||
|
||||
return adjustedDifficulty;
|
||||
}
|
||||
|
||||
@@ -211,6 +211,31 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
Assert.That(decodedAfterEncode.skin.Configuration.CustomComboColours, Has.Count.EqualTo(8));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEncodeStabilityOfSliderWithFractionalCoordinates()
|
||||
{
|
||||
Slider originalSlider = new Slider
|
||||
{
|
||||
Position = new Vector2(0.6f),
|
||||
Path = new SliderPath(new[]
|
||||
{
|
||||
new PathControlPoint(Vector2.Zero, PathType.PERFECT_CURVE),
|
||||
new PathControlPoint(new Vector2(25.6f, 78.4f)),
|
||||
new PathControlPoint(new Vector2(55.8f, 34.2f)),
|
||||
})
|
||||
};
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects = { originalSlider }
|
||||
};
|
||||
|
||||
var encoded = encodeToLegacy((beatmap, new TestLegacySkin(beatmaps_resource_store, string.Empty)));
|
||||
var decodedAfterEncode = decodeFromLegacy(encoded, string.Empty, version: LegacyBeatmapEncoder.FIRST_LAZER_VERSION);
|
||||
var decodedSlider = (Slider)decodedAfterEncode.beatmap.HitObjects[0];
|
||||
Assert.That(decodedSlider.Path.ControlPoints.Select(p => p.Position),
|
||||
Is.EquivalentTo(originalSlider.Path.ControlPoints.Select(p => p.Position)));
|
||||
}
|
||||
|
||||
private bool areComboColoursEqual(IHasComboColours a, IHasComboColours b)
|
||||
{
|
||||
// equal to null, no need to SequenceEqual
|
||||
@@ -233,11 +258,11 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private (IBeatmap beatmap, TestLegacySkin skin) decodeFromLegacy(Stream stream, string name)
|
||||
private (IBeatmap beatmap, TestLegacySkin skin) decodeFromLegacy(Stream stream, string name, int version = LegacyDecoder<Beatmap>.LATEST_VERSION)
|
||||
{
|
||||
using (var reader = new LineBufferedReader(stream))
|
||||
{
|
||||
var beatmap = new LegacyBeatmapDecoder { ApplyOffsets = false }.Decode(reader);
|
||||
var beatmap = new LegacyBeatmapDecoder(version) { ApplyOffsets = false }.Decode(reader);
|
||||
var beatmapSkin = new TestLegacySkin(beatmaps_resource_store, name);
|
||||
stream.Seek(0, SeekOrigin.Begin);
|
||||
beatmapSkin.Configuration = new LegacySkinDecoder().Decode(reader);
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
@@ -27,33 +25,34 @@ namespace osu.Game.Tests.Editing
|
||||
public partial class TestSceneHitObjectComposerDistanceSnapping : EditorClockTestScene
|
||||
{
|
||||
private TestHitObjectComposer composer = null!;
|
||||
|
||||
[Cached(typeof(EditorBeatmap))]
|
||||
[Cached(typeof(IBeatSnapProvider))]
|
||||
private readonly EditorBeatmap editorBeatmap;
|
||||
|
||||
protected override Container<Drawable> Content { get; } = new PopoverContainer { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
public TestSceneHitObjectComposerDistanceSnapping()
|
||||
{
|
||||
base.Content.Add(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
editorBeatmap = new EditorBeatmap(new OsuBeatmap
|
||||
{
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
}),
|
||||
Content
|
||||
},
|
||||
});
|
||||
}
|
||||
private EditorBeatmap editorBeatmap = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Child = composer = new TestHitObjectComposer();
|
||||
editorBeatmap = new EditorBeatmap(new OsuBeatmap
|
||||
{
|
||||
BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo },
|
||||
});
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies =
|
||||
[
|
||||
(typeof(EditorBeatmap), editorBeatmap),
|
||||
(typeof(IBeatSnapProvider), editorBeatmap)
|
||||
],
|
||||
Children = new Drawable[]
|
||||
{
|
||||
editorBeatmap,
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = composer = new TestHitObjectComposer()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BeatDivisor.Value = 1;
|
||||
|
||||
@@ -247,16 +246,23 @@ namespace osu.Game.Tests.Editing
|
||||
}
|
||||
|
||||
private void assertSnapDistance(float expectedDistance, IHasSliderVelocity? hasSliderVelocity = null)
|
||||
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistance(hasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"distance is {expectedDistance}", () => composer.DistanceSnapProvider.GetBeatSnapDistance(hasSliderVelocity),
|
||||
() => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private void assertDurationToDistance(double duration, float expectedDistance, HitObject? referenceObject = null)
|
||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}", () => composer.DistanceSnapProvider.DurationToDistance(duration, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"duration = {duration} -> distance = {expectedDistance}",
|
||||
() => composer.DistanceSnapProvider.DurationToDistance(duration, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity),
|
||||
() => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private void assertDistanceToDuration(float distance, double expectedDuration, HitObject? referenceObject = null)
|
||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}", () => composer.DistanceSnapProvider.DistanceToDuration(distance, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"distance = {distance} -> duration = {expectedDuration}",
|
||||
() => composer.DistanceSnapProvider.DistanceToDuration(distance, referenceObject?.StartTime ?? 0, referenceObject as IHasSliderVelocity),
|
||||
() => Is.EqualTo(expectedDuration).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private void assertSnappedDistance(float distance, float expectedDistance, HitObject? referenceObject = null)
|
||||
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)", () => composer.DistanceSnapProvider.FindSnappedDistance(distance, referenceObject?.GetEndTime() ?? 0, referenceObject as IHasSliderVelocity), () => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
=> AddAssert($"distance = {distance} -> distance = {expectedDistance} (snapped)",
|
||||
() => composer.DistanceSnapProvider.FindSnappedDistance(distance, referenceObject?.GetEndTime() ?? 0, referenceObject as IHasSliderVelocity),
|
||||
() => Is.EqualTo(expectedDistance).Within(Precision.FLOAT_EPSILON));
|
||||
|
||||
private partial class TestHitObjectComposer : OsuHitObjectComposer
|
||||
{
|
||||
|
||||
@@ -359,7 +359,7 @@ namespace osu.Game.Tests.Gameplay
|
||||
}
|
||||
|
||||
public override Judgement CreateJudgement() => new TestJudgement(maxResult);
|
||||
protected override HitWindows CreateHitWindows() => new HitWindows();
|
||||
protected override HitWindows CreateHitWindows() => new DefaultHitWindows();
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
{
|
||||
|
||||
@@ -64,11 +64,9 @@ namespace osu.Game.Tests.Gameplay
|
||||
/// <summary>
|
||||
/// Tests that a hitobject which provides a custom sample set of 2 retrieves the following samples from the beatmap skin:
|
||||
/// normal-hitnormal2
|
||||
/// normal-hitnormal
|
||||
/// hitnormal
|
||||
/// </summary>
|
||||
[TestCase("normal-hitnormal2")]
|
||||
[TestCase("normal-hitnormal")]
|
||||
[TestCase("hitnormal")]
|
||||
public void TestDefaultCustomSampleFromBeatmap(string expectedSample)
|
||||
{
|
||||
@@ -162,7 +160,6 @@ namespace osu.Game.Tests.Gameplay
|
||||
/// Tests that a control point that provides a custom sample of 2 causes <see cref="TestDefaultCustomSampleFromBeatmap"/>.
|
||||
/// </summary>
|
||||
[TestCase("normal-hitnormal2")]
|
||||
[TestCase("normal-hitnormal")]
|
||||
[TestCase("hitnormal")]
|
||||
public void TestControlPointCustomSampleFromBeatmap(string sampleName)
|
||||
{
|
||||
|
||||
@@ -166,11 +166,7 @@ namespace osu.Game.Tests.Mods
|
||||
/// </summary>
|
||||
private BeatmapDifficulty applyDifficulty(BeatmapDifficulty difficulty)
|
||||
{
|
||||
// ensure that ReadFromDifficulty doesn't pollute the values.
|
||||
var newDifficulty = difficulty.Clone();
|
||||
|
||||
testMod.ReadFromDifficulty(difficulty);
|
||||
|
||||
testMod.ApplyToDifficulty(newDifficulty);
|
||||
return newDifficulty;
|
||||
}
|
||||
|
||||
@@ -259,9 +259,6 @@ namespace osu.Game.Tests.Mods
|
||||
new MultiplayerTestScenario(true, true, [new OsuModPerfect()], []),
|
||||
new MultiplayerTestScenario(true, true, [new OsuModDoubleTime()], []),
|
||||
new MultiplayerTestScenario(true, true, [new OsuModNightcore()], []),
|
||||
new MultiplayerTestScenario(true, true, [new OsuModHidden()], []),
|
||||
new MultiplayerTestScenario(true, true, [new OsuModFlashlight()], []),
|
||||
new MultiplayerTestScenario(true, true, [new OsuModAccuracyChallenge()], []),
|
||||
new MultiplayerTestScenario(true, true, [new OsuModDifficultyAdjust()], []),
|
||||
new MultiplayerTestScenario(true, true, [new ModWindUp()], []),
|
||||
new MultiplayerTestScenario(true, true, [new ModWindDown()], []),
|
||||
@@ -345,10 +342,47 @@ namespace osu.Game.Tests.Mods
|
||||
{
|
||||
foreach (var mod in ruleset.CreateAllMods())
|
||||
{
|
||||
if (mod.ValidForFreestyleAsRequiredMod && mod.UserPlayable && !commonAcronyms.Contains(mod.Acronym))
|
||||
if (mod.ValidForFreestyleAsRequiredMod && !mod.UserPlayable)
|
||||
Assert.Fail($"Mod {mod.GetType().ReadableName()} declares {nameof(Mod.ValidForFreestyleAsRequiredMod)} but is not playable!");
|
||||
|
||||
if (mod.ValidForFreestyleAsRequiredMod && !mod.HasImplementation)
|
||||
Assert.Fail($"Mod {mod.GetType().ReadableName()} declares {nameof(Mod.ValidForFreestyleAsRequiredMod)} but is not implemented!");
|
||||
|
||||
if (mod.ValidForFreestyleAsRequiredMod && mod.UserPlayable && mod.HasImplementation && !commonAcronyms.Contains(mod.Acronym))
|
||||
Assert.Fail($"{mod.GetType().ReadableName()} declares {nameof(Mod.ValidForFreestyleAsRequiredMod)} but does not exist in all four basic rulesets!");
|
||||
if (!mod.ValidForFreestyleAsRequiredMod && mod.UserPlayable && commonAcronyms.Contains(mod.Acronym))
|
||||
Assert.Fail($"{mod.GetType().ReadableName()} does not declare {nameof(Mod.ValidForFreestyleAsRequiredMod)} but exists in all four basic rulesets!");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestModsValidForRequiredFreestyleAreConsistentlyCompatibleAcrossRulesets()
|
||||
{
|
||||
Dictionary<(string firstMod, string secondMod), bool> compatibilityMap = new Dictionary<(string, string), bool>();
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
for (int rulesetId = 0; rulesetId < 4; ++rulesetId)
|
||||
{
|
||||
var rulesetStore = new AssemblyRulesetStore();
|
||||
var ruleset = rulesetStore.GetRuleset(rulesetId)!.CreateInstance();
|
||||
|
||||
var modsValidForFreestyleAsRequired = ruleset.CreateAllMods().Where(m => m.ValidForFreestyleAsRequiredMod).OrderBy(m => m.Acronym).ToList();
|
||||
|
||||
for (int i = 0; i < modsValidForFreestyleAsRequired.Count; i++)
|
||||
{
|
||||
for (int j = i; j < modsValidForFreestyleAsRequired.Count; ++j)
|
||||
{
|
||||
var first = modsValidForFreestyleAsRequired[i];
|
||||
var second = modsValidForFreestyleAsRequired[j];
|
||||
|
||||
bool compatible = ModUtils.CheckCompatibleSet([first, second]);
|
||||
|
||||
if (!compatibilityMap.TryGetValue((first.Acronym, second.Acronym), out bool previousCompatible))
|
||||
compatibilityMap[(first.Acronym, second.Acronym)] = compatible;
|
||||
else if (previousCompatible != compatible)
|
||||
Assert.Fail($"{first.Acronym} and {second.Acronym} declare {nameof(Mod.ValidForFreestyleAsRequiredMod)} while not being consistently compatible in all four rulesets!");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,16 +85,6 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
|
||||
}
|
||||
|
||||
/*
|
||||
* The following tests have been written a bit strangely (they don't check exact
|
||||
* bound equality with what the filter says).
|
||||
* This is to account for floating-point arithmetic issues.
|
||||
* For example, specifying a bpm<140 filter would previously match beatmaps with BPM
|
||||
* of 139.99999, which would be displayed in the UI as 140.
|
||||
* Due to this the tests check the last tick inside the range and the first tick
|
||||
* outside of the range.
|
||||
*/
|
||||
|
||||
[TestCase("star")]
|
||||
[TestCase("stars")]
|
||||
public void TestApplyStarQueries(string variant)
|
||||
@@ -105,11 +95,31 @@ namespace osu.Game.Tests.NonVisual.Filtering
|
||||
Assert.AreEqual("easy", filterCriteria.SearchText.Trim());
|
||||
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
|
||||
Assert.IsNotNull(filterCriteria.StarDifficulty.Max);
|
||||
Assert.Greater(filterCriteria.StarDifficulty.Max, 3.99d);
|
||||
Assert.Less(filterCriteria.StarDifficulty.Max, 4.00d);
|
||||
Assert.AreEqual(filterCriteria.StarDifficulty.Max, 4.00d);
|
||||
Assert.IsNull(filterCriteria.StarDifficulty.Min);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStarQueriesInclusive()
|
||||
{
|
||||
const string query = "stars>=6";
|
||||
var filterCriteria = new FilterCriteria();
|
||||
FilterQueryParser.ApplyQueries(filterCriteria, query);
|
||||
Assert.AreEqual(filterCriteria.StarDifficulty.Min, 6.00d);
|
||||
Assert.True(filterCriteria.StarDifficulty.IsLowerInclusive);
|
||||
Assert.IsNull(filterCriteria.StarDifficulty.Max);
|
||||
}
|
||||
|
||||
/*
|
||||
* The following tests have been written a bit strangely (they don't check exact
|
||||
* bound equality with what the filter says).
|
||||
* This is to account for floating-point arithmetic issues.
|
||||
* For example, specifying a bpm<140 filter would previously match beatmaps with BPM
|
||||
* of 139.99999, which would be displayed in the UI as 140.
|
||||
* Due to this the tests check the last tick inside the range and the first tick
|
||||
* outside of the range.
|
||||
*/
|
||||
|
||||
[Test]
|
||||
public void TestApplyApproachRateQueries()
|
||||
{
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
public void TestResultIfOnlyParentHitWindowIsEmpty()
|
||||
{
|
||||
var testObject = new TestHitObject(HitWindows.Empty);
|
||||
HitObject nested = new TestHitObject(new HitWindows());
|
||||
HitObject nested = new TestHitObject(new DefaultHitWindows());
|
||||
testObject.AddNested(nested);
|
||||
testDrawableRuleset.HitObjects = new List<HitObject> { testObject };
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace osu.Game.Tests.NonVisual
|
||||
[Test]
|
||||
public void TestResultIfParentHitWindowsIsNotEmpty()
|
||||
{
|
||||
var testObject = new TestHitObject(new HitWindows());
|
||||
HitObject nested = new TestHitObject(new HitWindows());
|
||||
var testObject = new TestHitObject(new DefaultHitWindows());
|
||||
HitObject nested = new TestHitObject(new DefaultHitWindows());
|
||||
testObject.AddNested(nested);
|
||||
testDrawableRuleset.HitObjects = new List<HitObject> { testObject };
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.NonVisual
|
||||
HitObject nested = new TestHitObject(HitWindows.Empty);
|
||||
firstObject.AddNested(nested);
|
||||
|
||||
var secondObject = new TestHitObject(new HitWindows());
|
||||
var secondObject = new TestHitObject(new DefaultHitWindows());
|
||||
testDrawableRuleset.HitObjects = new List<HitObject> { firstObject, secondObject };
|
||||
|
||||
Assert.AreSame(testDrawableRuleset.FirstAvailableHitWindows, secondObject.HitWindows);
|
||||
|
||||
@@ -21,5 +21,18 @@ namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
Assert.AreEqual(expectedOutput, input.FormatAccuracy().ToString());
|
||||
}
|
||||
|
||||
[TestCase(3, "3.00")]
|
||||
[TestCase(3.3, "3.30")]
|
||||
[TestCase(3.55, "3.55")]
|
||||
[TestCase(3.553, "3.55")]
|
||||
[TestCase(3.557, "3.55")]
|
||||
[TestCase(3.9999, "3.99")]
|
||||
[TestCase(3.999999, "3.99")]
|
||||
[TestCase(4, "4.00")]
|
||||
public void TestStarRatingFormatting(double input, string expectedOutput)
|
||||
{
|
||||
Assert.AreEqual(expectedOutput, input.FormatStarRating().ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,6 +383,9 @@ namespace osu.Game.Tests.NonVisual
|
||||
IsImportant = isImportant;
|
||||
FrameIndex = frameIndex;
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TestReplayFrame testFrame && Time == testFrame.Time && IsImportant == testFrame.IsImportant && FrameIndex == testFrame.FrameIndex;
|
||||
}
|
||||
|
||||
private class TestInputHandler : FramedReplayInputHandler<TestReplayFrame>
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
// 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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osu.Game.Updater;
|
||||
|
||||
namespace osu.Game.Tests.NonVisual
|
||||
{
|
||||
[HeadlessTest]
|
||||
public partial class TestSceneUpdateManager : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(INotificationOverlay))]
|
||||
private readonly INotificationOverlay notifications = new TestNotificationOverlay();
|
||||
|
||||
private TestUpdateManager manager = null!;
|
||||
private OsuConfigManager config = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetupSteps()
|
||||
{
|
||||
AddStep("add manager", () =>
|
||||
{
|
||||
config = new OsuConfigManager(LocalStorage);
|
||||
config.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
|
||||
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
CachedDependencies = [(typeof(OsuConfigManager), config)],
|
||||
Child = manager = new TestUpdateManager()
|
||||
};
|
||||
});
|
||||
|
||||
// Updates should be checked when the object is loaded for the first time.
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("1 check completed", () => manager.Completions, () => Is.EqualTo(1));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates should be checked when the release stream is changed.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestReleaseStreamChanged()
|
||||
{
|
||||
AddStep("change release stream", () => config.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Tachyon));
|
||||
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("2 checks completed", () => manager.Completions, () => Is.EqualTo(2));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
|
||||
AddStep("change release stream", () => config.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Lazer));
|
||||
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("3 checks completed", () => manager.Completions, () => Is.EqualTo(3));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changing the release stream should start a new invocation and cancel the existing one.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestNewInvocationOnReleaseStreamChanged()
|
||||
{
|
||||
AddStep("change release stream", () => config.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Tachyon));
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("change release stream", () => config.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Lazer));
|
||||
AddUntilStep("3 invocations", () => manager.Invocations, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("2 checks completed", () => manager.Completions, () => Is.EqualTo(2));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates should be checked when the user requests them to.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestUserRequest()
|
||||
{
|
||||
AddStep("request check", () => manager.CheckForUpdate());
|
||||
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("2 checks completed", () => manager.Completions, () => Is.EqualTo(2));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
|
||||
AddStep("request check", () => manager.CheckForUpdate());
|
||||
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("3 checks completed", () => manager.Completions, () => Is.EqualTo(3));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User requests should start a new invocation and cancel the existing one.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestUserRequestOverridesExistingCheck()
|
||||
{
|
||||
// This part covering double user input is not really possible because the settings button is disabled during the check,
|
||||
// but it's kept here for sanity in-case the update manager is used as a standalone object elsewhere.
|
||||
|
||||
AddStep("request check", () => manager.CheckForUpdate());
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("request check", () => manager.CheckForUpdate());
|
||||
AddUntilStep("3 invocations", () => manager.Invocations, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("2 checks completed", () => manager.Completions, () => Is.EqualTo(2));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
|
||||
// This next part tests for the user requesting an update during a background check, and is possible to occur in practice.
|
||||
|
||||
AddStep("change release stream", () => config.SetValue(OsuSetting.ReleaseStream, ReleaseStream.Tachyon));
|
||||
AddUntilStep("check pending", () => manager.IsPending);
|
||||
AddStep("request check", () => manager.CheckForUpdate());
|
||||
AddUntilStep("5 invocations", () => manager.Invocations, () => Is.EqualTo(5));
|
||||
|
||||
AddStep("complete check", () => manager.Complete());
|
||||
AddUntilStep("3 checks completed", () => manager.Completions, () => Is.EqualTo(3));
|
||||
AddUntilStep("no check pending", () => !manager.IsPending);
|
||||
}
|
||||
|
||||
private partial class TestUpdateManager : UpdateManager
|
||||
{
|
||||
public bool IsPending { get; private set; }
|
||||
public int Invocations { get; private set; }
|
||||
public int Completions { get; private set; }
|
||||
|
||||
private TaskCompletionSource<bool>? pendingCheck;
|
||||
|
||||
protected override async Task<bool> PerformUpdateCheck(CancellationToken cancellationToken)
|
||||
{
|
||||
Invocations++;
|
||||
|
||||
var check = pendingCheck = new TaskCompletionSource<bool>();
|
||||
IsPending = true;
|
||||
|
||||
try
|
||||
{
|
||||
bool result = await check.Task.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
Completions++;
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
pendingCheck?.SetResult(true);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestNotificationOverlay : INotificationOverlay
|
||||
{
|
||||
public void Post(Notification notification)
|
||||
{
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
}
|
||||
|
||||
public IBindable<int> UnreadCount { get; } = new Bindable<int>();
|
||||
|
||||
public IEnumerable<Notification> AllNotifications { get; } = Enumerable.Empty<Notification>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,7 @@ namespace osu.Game.Tests.Resources
|
||||
{
|
||||
// Create random metadata, then we can check if sorting works based on these
|
||||
Artist = "Some Artist " + RNG.Next(0, 9),
|
||||
Title = $"Some Song (set id {setId:000}) {Guid.NewGuid()}",
|
||||
Title = $"Some Song (set id {setId:000000}) {Guid.NewGuid()}",
|
||||
Author = { Username = "Some Guy " + RNG.Next(0, 9) },
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ using osu.Game.Screens.Backgrounds;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.PlayerSettings;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
@@ -325,7 +325,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
private void setupUserSettings()
|
||||
{
|
||||
AddUntilStep("Song select is current", () => songSelect.IsCurrentScreen());
|
||||
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null);
|
||||
AddUntilStep("Song select has selection", () => songSelect.Carousel?.CurrentSelection != null);
|
||||
AddStep("Set default user settings", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModNoFail() };
|
||||
@@ -340,7 +340,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
rulesets?.Dispose();
|
||||
}
|
||||
|
||||
private partial class DummySongSelect : PlaySongSelect
|
||||
private partial class DummySongSelect : SoloSongSelect
|
||||
{
|
||||
private FadeAccessibleBackground background;
|
||||
|
||||
@@ -355,7 +355,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
public readonly Bindable<double> DimLevel = new BindableDouble();
|
||||
public readonly Bindable<double> BlurLevel = new BindableDouble();
|
||||
|
||||
public new BeatmapCarousel Carousel => base.Carousel;
|
||||
public BeatmapCarousel Carousel => this.ChildrenOfType<BeatmapCarousel>().SingleOrDefault();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
@@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
public bool IsBackgroundDimmed() => background.CurrentColour == OsuColour.Gray(1f - background.CurrentDim);
|
||||
|
||||
public bool IsBackgroundUndimmed() => background.CurrentColour == Color4.White;
|
||||
public bool IsBackgroundUndimmed() => background.CurrentColour == new Color4(0.9f, 0.9f, 0.9f, 1f);
|
||||
|
||||
public bool IsUserBlurApplied() => Precision.AlmostEquals(background.CurrentBlur, new Vector2((float)BlurLevel.Value * BackgroundScreenBeatmap.USER_BLUR_FACTOR), 0.1f);
|
||||
|
||||
@@ -376,7 +376,7 @@ namespace osu.Game.Tests.Visual.Background
|
||||
|
||||
public bool IsBackgroundVisible() => background.CurrentAlpha == 1;
|
||||
|
||||
public bool IsBackgroundBlur() => Precision.AlmostEquals(background.CurrentBlur, new Vector2(BACKGROUND_BLUR), 0.1f);
|
||||
public bool IsBackgroundBlur() => Precision.AlmostBigger(background.CurrentBlur.X, 0, 0.1f);
|
||||
|
||||
public bool CheckBackgroundBlur(Vector2 expected) => Precision.AlmostEquals(background.CurrentBlur, expected, 0.1f);
|
||||
|
||||
|
||||
@@ -615,6 +615,25 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUndoAfterQuickDeletingObjectWhileDragged()
|
||||
{
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(
|
||||
new HitCircle { StartTime = 0, Position = new Vector2(200, 200) }
|
||||
));
|
||||
|
||||
moveMouseToObject(() => EditorBeatmap.HitObjects[0]);
|
||||
|
||||
AddStep("hold left click", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("drag hitobject to different position", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.BottomRight));
|
||||
AddStep("click middle mouse button", () => InputManager.Click(MouseButton.Middle));
|
||||
AddStep("release left click", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("no hitobjects in beatmap", () => EditorBeatmap.HitObjects.Count, () => Is.Zero);
|
||||
|
||||
AddStep("undo", () => Editor.Undo());
|
||||
AddAssert("one hitobject in beatmap", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShiftModifierMaintainsAspectRatio()
|
||||
{
|
||||
|
||||
@@ -232,8 +232,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 });
|
||||
});
|
||||
|
||||
ensureEditorLoaded();
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog);
|
||||
@@ -274,6 +274,14 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
{
|
||||
string? difficultyName = Editor.ChildrenOfType<EditorBeatmap>().SingleOrDefault()?.BeatmapInfo.DifficultyName;
|
||||
return difficultyName != null && difficultyName != firstDifficultyName;
|
||||
});
|
||||
|
||||
ensureEditorLoaded();
|
||||
|
||||
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
|
||||
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
|
||||
AddStep("add effect points", () =>
|
||||
@@ -285,8 +293,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 });
|
||||
});
|
||||
|
||||
ensureEditorLoaded();
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
|
||||
AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new TaikoRuleset().RulesetInfo));
|
||||
|
||||
AddUntilStep("wait for created", () =>
|
||||
@@ -629,6 +637,8 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
StartTime = 1000
|
||||
}
|
||||
}));
|
||||
|
||||
ensureEditorLoaded();
|
||||
AddStep("save beatmap", () => Editor.Save());
|
||||
AddStep("try to create new catch difficulty", () => Editor.CreateNewDifficulty(new CatchRuleset().RulesetInfo));
|
||||
|
||||
@@ -765,7 +775,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3"));
|
||||
}
|
||||
|
||||
private void ensureEditorLoaded() => AddUntilStep("wait for editor load", () => Editor.IsLoaded && DialogOverlay.IsLoaded);
|
||||
private void ensureEditorLoaded() => AddUntilStep("wait for editor load", () => Editor.ReadyForUse && DialogOverlay.IsLoaded);
|
||||
|
||||
private void createNewDifficulty()
|
||||
{
|
||||
|
||||
@@ -15,7 +15,7 @@ using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
@@ -190,7 +190,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("Set tags again", () => EditorBeatmap.BeatmapInfo.Metadata.Tags = tags_to_discard);
|
||||
|
||||
AddStep("Exit editor", () => Editor.Exit());
|
||||
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
AddAssert("Tags reverted correctly", () => Game.Beatmap.Value.BeatmapInfo.Metadata.Tags == tags_to_save);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
() => Is.EqualTo(1));
|
||||
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("entered song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
|
||||
addStepClickLink("00:00:000 (1)", waitForSeek: false);
|
||||
AddUntilStep("received 'must be in edit'",
|
||||
@@ -151,12 +151,12 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("Present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
||||
AddUntilStep("Wait for song select", () =>
|
||||
Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||
&& songSelect.BeatmapSetsLoaded
|
||||
&& Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect
|
||||
&& songSelect.CarouselItemsPresented
|
||||
);
|
||||
AddStep("Switch ruleset", () => Game.Ruleset.Value = ruleset);
|
||||
AddStep("Open editor for ruleset", () =>
|
||||
((PlaySongSelect)Game.ScreenStack.CurrentScreen)
|
||||
((SoloSongSelect)Game.ScreenStack.CurrentScreen)
|
||||
.Edit(beatmapSet.Beatmaps.Last(beatmap => beatmap.Ruleset.Name == ruleset.Name))
|
||||
);
|
||||
AddUntilStep("Wait for editor open", () => editor?.ReadyForUse == true);
|
||||
|
||||
@@ -45,6 +45,44 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we already have an old score with enough hit events and the new score doesn't have enough, continue displaying the old one rather than showing the user "play too short" message.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestTooShortToDisplay_HasPreviousValidScore()
|
||||
{
|
||||
const double average_error = -4.5;
|
||||
const double initial_offset = -2;
|
||||
|
||||
AddStep("Set offset non-neutral", () => offsetControl.Current.Value = initial_offset);
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
|
||||
AddStep("Set reference score", () =>
|
||||
{
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
};
|
||||
});
|
||||
|
||||
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
|
||||
AddStep("Set short reference score", () =>
|
||||
{
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
};
|
||||
});
|
||||
|
||||
AddUntilStep("Still calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
|
||||
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotEnoughTimedHitEvents()
|
||||
{
|
||||
@@ -125,10 +163,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error);
|
||||
|
||||
AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
||||
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
|
||||
recreateControl();
|
||||
AddStep("Set same reference score", () => offsetControl.ReferenceScore.Value = referenceScore);
|
||||
@@ -141,6 +176,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestCalibrationFromNonZero()
|
||||
{
|
||||
ScoreInfo referenceScore = null!;
|
||||
const double average_error = -4.5;
|
||||
const double initial_offset = -2;
|
||||
|
||||
@@ -148,7 +184,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
AddStep("Set reference score", () =>
|
||||
{
|
||||
offsetControl.ReferenceScore.Value = new ScoreInfo
|
||||
offsetControl.ReferenceScore.Value = referenceScore = new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
@@ -158,9 +194,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == initial_offset - average_error);
|
||||
|
||||
AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
||||
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||
|
||||
recreateControl();
|
||||
AddStep("Set same reference score", () => offsetControl.ReferenceScore.Value = referenceScore);
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
}
|
||||
|
||||
@@ -209,10 +246,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||
AddAssert("Offset is adjusted", () => offsetControl.Current.Value, () => Is.EqualTo(initial_offset - average_error));
|
||||
|
||||
AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
||||
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
|
||||
AddStep("Clean up beatmap", () => Realm.Write(r => r.RemoveAll<BeatmapInfo>()));
|
||||
}
|
||||
@@ -236,10 +270,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("Has calibration button", () => offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
AddStep("Press button", () => offsetControl.ChildrenOfType<SettingsButton>().Single().TriggerClick());
|
||||
AddAssert("Offset is adjusted", () => offsetControl.Current.Value == -average_error);
|
||||
|
||||
AddUntilStep("Button is disabled", () => !offsetControl.ChildrenOfType<SettingsButton>().Single().Enabled.Value);
|
||||
AddStep("Remove reference score", () => offsetControl.ReferenceScore.Value = null);
|
||||
AddAssert("No calibration button", () => !offsetControl.ChildrenOfType<SettingsButton>().Any());
|
||||
}
|
||||
|
||||
private void recreateControl()
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("toggle expanded", () =>
|
||||
{
|
||||
if (leaderboard.IsNotNull())
|
||||
leaderboard.ForceExpand.Value = !leaderboard.ForceExpand.Value;
|
||||
leaderboard.CollapseDuringGameplay.Value = !leaderboard.CollapseDuringGameplay.Value;
|
||||
});
|
||||
|
||||
AddSliderStep("set player score", 50, 5000000, 1222333, v => playerScore.Value = v);
|
||||
|
||||
@@ -9,7 +9,6 @@ using osu.Game.Audio;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@@ -21,6 +20,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private bool seek;
|
||||
|
||||
[Test]
|
||||
[FlakyTest]
|
||||
public void TestAllSamplesStopDuringSeek()
|
||||
{
|
||||
DrawableSlider? slider = null;
|
||||
@@ -73,8 +73,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
//
|
||||
// We want to keep seeking while asserting various test conditions, so
|
||||
// continue to seek until we unset the flag.
|
||||
var gameplayClockContainer = Player.ChildrenOfType<GameplayClockContainer>().First();
|
||||
gameplayClockContainer.Seek(gameplayClockContainer.CurrentTime > 30000 ? 0 : 60000);
|
||||
var gameplayClockContainer = Player?.GameplayClockContainer;
|
||||
gameplayClockContainer?.Seek(gameplayClockContainer.CurrentTime > 30000 ? 0 : 60000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -65,30 +65,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
HitWindows = new HitWindows(),
|
||||
HitWindows = new DefaultHitWindows(),
|
||||
StartTime = t += spacing,
|
||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL) }
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
HitWindows = new HitWindows(),
|
||||
HitWindows = new DefaultHitWindows(),
|
||||
StartTime = t += spacing,
|
||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE) }
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
HitWindows = new HitWindows(),
|
||||
HitWindows = new DefaultHitWindows(),
|
||||
StartTime = t += spacing,
|
||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_NORMAL, HitSampleInfo.BANK_SOFT) },
|
||||
},
|
||||
new HitCircle
|
||||
{
|
||||
HitWindows = new HitWindows(),
|
||||
HitWindows = new DefaultHitWindows(),
|
||||
StartTime = t += spacing,
|
||||
},
|
||||
new Slider
|
||||
{
|
||||
HitWindows = new HitWindows(),
|
||||
HitWindows = new DefaultHitWindows(),
|
||||
StartTime = t += spacing,
|
||||
Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, Vector2.UnitY * 200 }),
|
||||
Samples = new[] { new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT) },
|
||||
|
||||
@@ -136,7 +136,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
IBindableList<GameplayLeaderboardScore> IGameplayLeaderboardProvider.Scores => Scores;
|
||||
public BindableList<GameplayLeaderboardScore> Scores { get; } = new BindableList<GameplayLeaderboardScore>();
|
||||
public bool IsPartial { get; } = false;
|
||||
|
||||
public TestGameplayLeaderboardProvider()
|
||||
{
|
||||
@@ -147,8 +146,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
User = new APIUser { Username = $"User {i}" },
|
||||
TotalScore = (20 - i) * 50_000,
|
||||
Accuracy = i * 0.05,
|
||||
Combo = i * 50
|
||||
}, i == 19));
|
||||
MaxCombo = i * 50,
|
||||
}, i == 19, GameplayLeaderboardScore.ComboDisplayMode.Highest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,16 @@ using NUnit.Framework;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@@ -157,6 +162,51 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("Jumped forwards", () => Player.GameplayClockContainer.CurrentTime - lastTime > 500);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestReplayDoesNotFailUntilRunningOutOfFrames()
|
||||
{
|
||||
var score = new Score
|
||||
{
|
||||
ScoreInfo = TestResources.CreateTestScoreInfo(Beatmap.Value.BeatmapInfo),
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new OsuReplayFrame(0, Vector2.Zero),
|
||||
new OsuReplayFrame(10000, Vector2.Zero),
|
||||
}
|
||||
}
|
||||
};
|
||||
score.ScoreInfo.Mods = [];
|
||||
score.ScoreInfo.Rank = ScoreRank.F;
|
||||
AddStep("set global state", () =>
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset;
|
||||
SelectedMods.Value = score.ScoreInfo.Mods;
|
||||
});
|
||||
AddStep("create player", () => Player = new TestReplayPlayer(score, showResults: false));
|
||||
AddStep("load player", () => LoadScreen(Player));
|
||||
AddUntilStep("wait for loaded", () => Player.IsCurrentScreen());
|
||||
AddStep("seek to 8000", () => Player.Seek(8000));
|
||||
AddUntilStep("wait for fail", () => Player.GameplayState.HasFailed);
|
||||
AddAssert("player failed after 10000", () => Player.GameplayClockContainer.CurrentTime, () => Is.GreaterThanOrEqualTo(10000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayerLoaderSettingsHover()
|
||||
{
|
||||
loadPlayerWithBeatmap();
|
||||
|
||||
AddUntilStep("wait for settings overlay hidden", () => settingsOverlay().Expanded.Value, () => Is.False);
|
||||
AddStep("move mouse to right of screen", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopRight));
|
||||
AddUntilStep("wait for settings overlay visible", () => settingsOverlay().Expanded.Value, () => Is.True);
|
||||
AddStep("move mouse to centre of screen", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre));
|
||||
AddUntilStep("wait for settings overlay hidden", () => settingsOverlay().Expanded.Value, () => Is.False);
|
||||
|
||||
PlayerSettingsOverlay settingsOverlay() => Player.ChildrenOfType<PlayerSettingsOverlay>().Single();
|
||||
}
|
||||
|
||||
private void loadPlayerWithBeatmap(IBeatmap? beatmap = null)
|
||||
{
|
||||
AddStep("create player", () =>
|
||||
|
||||
@@ -317,6 +317,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Position = position;
|
||||
Actions.AddRange(actions);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TestReplayFrame testFrame && Time == testFrame.Time && Position == testFrame.Position && Actions.SequenceEqual(testFrame.Actions);
|
||||
}
|
||||
|
||||
public enum TestAction
|
||||
|
||||
@@ -15,6 +15,7 @@ using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Tests.Gameplay;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -37,6 +38,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||
|
||||
[Cached(typeof(IGameplayLeaderboardProvider))]
|
||||
private EmptyGameplayLeaderboardProvider leaderboardProvider = new EmptyGameplayLeaderboardProvider();
|
||||
|
||||
public TestSceneSkinEditorMultipleSkins()
|
||||
{
|
||||
scoreProcessor = gameplayState.ScoreProcessor;
|
||||
|
||||
@@ -21,6 +21,7 @@ using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Gameplay;
|
||||
|
||||
@@ -42,6 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached(typeof(IGameplayClock))]
|
||||
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new TrackVirtual(60000), false, false);
|
||||
|
||||
[Cached(typeof(IGameplayLeaderboardProvider))]
|
||||
private EmptyGameplayLeaderboardProvider leaderboardProvider = new EmptyGameplayLeaderboardProvider();
|
||||
|
||||
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
||||
|
||||
// best way to check without exposing.
|
||||
|
||||
@@ -69,6 +69,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest]
|
||||
public void TestFadeOnIdle()
|
||||
{
|
||||
createTest();
|
||||
@@ -144,7 +145,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDoesntFadeOnMouseDown()
|
||||
[FlakyTest]
|
||||
public void TestDoesNotFadeOnMouseDown()
|
||||
{
|
||||
createTest();
|
||||
|
||||
|
||||
@@ -353,6 +353,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
return new LegacyReplayFrame(Time, Position.X, Position.Y, state);
|
||||
}
|
||||
|
||||
public override bool IsEquivalentTo(ReplayFrame other)
|
||||
=> other is TestReplayFrame testFrame && Time == testFrame.Time && Position == testFrame.Position && Actions.SequenceEqual(testFrame.Actions);
|
||||
}
|
||||
|
||||
public enum TestAction
|
||||
|
||||
@@ -150,6 +150,24 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
});
|
||||
});
|
||||
|
||||
// cross-reference: `TestSceneOverallRanking.TestRoundingTreatment()`.
|
||||
AddStep("Test rounding treatment", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
transientUpdateDisplay.LatestUpdate.Value = new ScoreBasedUserStatisticsUpdate(
|
||||
new ScoreInfo(),
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 111_111,
|
||||
PP = 5071.495M
|
||||
},
|
||||
new UserStatistics
|
||||
{
|
||||
GlobalRank = 111_111,
|
||||
PP = 5072.99M
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("No change 1", () =>
|
||||
{
|
||||
var transientUpdateDisplay = this.ChildrenOfType<TransientUserStatisticsUpdateDisplay>().Single();
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public void TestScoreUpdates()
|
||||
{
|
||||
AddRepeatStep("update state", UpdateUserStatesRandomly, 100);
|
||||
AddToggleStep("switch compact mode", expanded => Leaderboard!.ForceExpand.Value = expanded);
|
||||
AddToggleStep("switch compact mode", collapsed => Leaderboard!.CollapseDuringGameplay.Value = collapsed);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
ForceExpand = { Value = true }
|
||||
CollapseDuringGameplay = { Value = false }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -303,6 +303,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
|
||||
[Test]
|
||||
[FlakyTest]
|
||||
public void TestMostInSyncUserIsAudioSource()
|
||||
{
|
||||
start(new[] { PLAYER_1_ID, PLAYER_2_ID });
|
||||
|
||||
@@ -44,14 +44,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Team2Score = { BindTarget = LeaderboardProvider.TeamScores[1] }
|
||||
}, Add);
|
||||
|
||||
LoadComponentAsync(new GameplayMatchScoreDisplay
|
||||
GameplayMatchScoreDisplay matchScoreDisplay;
|
||||
LoadComponentAsync(matchScoreDisplay = new GameplayMatchScoreDisplay
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Team1Score = { BindTarget = LeaderboardProvider.TeamScores[0] },
|
||||
Team2Score = { BindTarget = LeaderboardProvider.TeamScores[1] },
|
||||
Expanded = { BindTarget = Leaderboard!.ForceExpand },
|
||||
}, Add);
|
||||
|
||||
Leaderboard!.CollapseDuringGameplay.BindValueChanged(_ => matchScoreDisplay.Expanded.Value = !Leaderboard.CollapseDuringGameplay.Value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,9 +59,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetupSteps()
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddUntilStep("wait for mod select removed", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Count(), () => Is.Zero);
|
||||
|
||||
AddStep("load match", () =>
|
||||
{
|
||||
room = new Room { Name = "Test Room" };
|
||||
@@ -305,7 +308,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for join", () => RoomJoined);
|
||||
|
||||
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||
AddAssert("mod select shows unranked", () => this.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
|
||||
AddUntilStep("mod select shows unranked", () => this.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
|
||||
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
||||
|
||||
AddStep("select flashlight", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
|
||||
|
||||
@@ -56,6 +56,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddUntilStep("wait for mod select removed", () => this.ChildrenOfType<FreeModSelectOverlay>().Count(), () => Is.Zero);
|
||||
|
||||
AddStep("reset", () =>
|
||||
{
|
||||
room = new Room();
|
||||
|
||||
@@ -165,23 +165,75 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Name = "A host-only room",
|
||||
QueueMode = QueueMode.HostOnly,
|
||||
Type = MatchType.HeadToHead,
|
||||
RoomID = 1337,
|
||||
}),
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "An all-players, team-versus room",
|
||||
QueueMode = QueueMode.AllPlayers,
|
||||
Type = MatchType.TeamVersus
|
||||
Type = MatchType.TeamVersus,
|
||||
RoomID = 1338,
|
||||
}),
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "A round-robin room",
|
||||
QueueMode = QueueMode.AllPlayersRoundRobin,
|
||||
Type = MatchType.HeadToHead
|
||||
Type = MatchType.HeadToHead,
|
||||
RoomID = 1339,
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoomWithLongTitle()
|
||||
{
|
||||
AddStep("create rooms", () => Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new[]
|
||||
{
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "This room has a very very long title enough to make the external link button reach the participants list on the right side unless the test window is very wide, at which point I don't know, hi.",
|
||||
QueueMode = QueueMode.HostOnly,
|
||||
Type = MatchType.HeadToHead,
|
||||
RoomID = 1337,
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRoomWithUpdatedRoomID()
|
||||
{
|
||||
Room room = null!;
|
||||
|
||||
AddStep("create rooms", () => Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new[]
|
||||
{
|
||||
new MultiplayerRoomPanel(room = new Room
|
||||
{
|
||||
Name = "This room has a very very long title enough to make the external link button reach the participants list on the right side unless the test window is very wide, at which point I don't know, hi.",
|
||||
QueueMode = QueueMode.HostOnly,
|
||||
Type = MatchType.HeadToHead,
|
||||
}),
|
||||
}
|
||||
});
|
||||
AddWaitStep("wait", 3);
|
||||
AddStep("set room ID", () => room.RoomID = 1337);
|
||||
AddWaitStep("wait", 3);
|
||||
AddStep("clear room ID", () => room.RoomID = null);
|
||||
}
|
||||
|
||||
private RoomPanel createLoungeRoom(Room room)
|
||||
{
|
||||
room.Host ??= new APIUser { Username = "peppy", Id = 2 };
|
||||
|
||||
@@ -26,8 +26,8 @@ using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.GameplayTest;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("exit", () => getEditor().Exit());
|
||||
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect
|
||||
&& songSelect.Beatmap.Value is DummyWorkingBeatmap);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("switch ruleset at song select", () => Game.Ruleset.Value = new ManiaRuleset().RulesetInfo);
|
||||
|
||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||
AddStep("open editor", () => ((SoloSongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||
|
||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||
AddAssert("editor ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo));
|
||||
@@ -187,8 +187,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
});
|
||||
AddAssert("gameplay ruleset is osu!", () => Game.Ruleset.Value, () => Is.EqualTo(new OsuRuleset().RulesetInfo));
|
||||
|
||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(SoloSongSelect).Yield()));
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
AddAssert("previous ruleset restored", () => Game.Ruleset.Value.Equals(new ManiaRuleset().RulesetInfo));
|
||||
}
|
||||
|
||||
@@ -289,8 +289,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("user request play", () => Game.MusicController.Play(requestedByUser: true));
|
||||
AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying);
|
||||
|
||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(SoloSongSelect).Yield()));
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
|
||||
AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying);
|
||||
AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true));
|
||||
@@ -352,13 +352,13 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
||||
AddUntilStep("wait for song select",
|
||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||
&& songSelect.BeatmapSetsLoaded);
|
||||
&& Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect
|
||||
&& songSelect.CarouselItemsPresented);
|
||||
}
|
||||
|
||||
private void openEditor()
|
||||
{
|
||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||
AddStep("open editor", () => ((SoloSongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
@@ -40,7 +40,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
InputManager.Key(Key.P);
|
||||
});
|
||||
|
||||
AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddAssert("state is play", () => buttons.State == ButtonSystemState.Play);
|
||||
|
||||
AddStep("press P", () => InputManager.Key(Key.P));
|
||||
AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
AddAssert("entered song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -54,10 +54,10 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
PushAndConfirm(() => new PlaySongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault);
|
||||
AddUntilStep("wait for carousel load", () => songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for carousel load", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("enter gameplay", () => InputManager.Key(Key.Enter));
|
||||
|
||||
@@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
.AsEnumerable()
|
||||
.First(k => k.RulesetName == "osu" && k.ActionInt == 0);
|
||||
|
||||
private Screens.Select.SongSelect songSelect => Game.ScreenStack.CurrentScreen as Screens.Select.SongSelect;
|
||||
private SoloSongSelect songSelect => Game.ScreenStack.CurrentScreen as SoloSongSelect;
|
||||
|
||||
private Player player => Game.ScreenStack.CurrentScreen as Player;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK.Input;
|
||||
|
||||
@@ -83,9 +84,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
private void loadToPlayerNonBreakTime()
|
||||
{
|
||||
Player? player = null;
|
||||
Screens.Select.SongSelect songSelect = null!;
|
||||
PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
SoloSongSelect songSelect = null!;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
@@ -25,6 +25,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
@@ -83,6 +84,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestCursorHidesWhenIdle()
|
||||
{
|
||||
AddStep("move mouse inside game bounds", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.TopLeft + new Vector2(20)));
|
||||
AddStep("click mouse", () => InputManager.Click(MouseButton.Left));
|
||||
AddUntilStep("wait until idle", () => Game.IsIdle.Value);
|
||||
AddUntilStep("menu cursor hidden", () => Game.GlobalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
|
||||
|
||||
@@ -17,9 +17,9 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK.Input;
|
||||
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
@@ -44,17 +44,17 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestPerformAtSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
|
||||
AddStep("perform immediately", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(SoloSongSelect) }));
|
||||
AddAssert("did perform", () => actionPerformed);
|
||||
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||
AddAssert("screen didn't change", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerformAtMenuFromSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true));
|
||||
AddUntilStep("returned to menu", () => Game.ScreenStack.CurrentScreen is MainMenu);
|
||||
@@ -69,8 +69,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader);
|
||||
|
||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestPlaySongSelect) }));
|
||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is TestPlaySongSelect);
|
||||
AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(SoloSongSelect) }));
|
||||
AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect);
|
||||
AddAssert("did perform", () => actionPerformed);
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
private void importAndWaitForSongSelect()
|
||||
{
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
@@ -81,11 +81,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
presentAndConfirm(osuImport);
|
||||
|
||||
var maniaImport = importBeatmap(2, new ManiaRuleset().RulesetInfo);
|
||||
confirmBeatmapInSongSelect(maniaImport);
|
||||
presentAndConfirm(maniaImport);
|
||||
|
||||
var catchImport = importBeatmap(3, new CatchRuleset().RulesetInfo);
|
||||
confirmBeatmapInSongSelect(catchImport);
|
||||
presentAndConfirm(catchImport);
|
||||
|
||||
// Ruleset is always changed.
|
||||
@@ -103,11 +101,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
presentAndConfirm(osuImport);
|
||||
|
||||
var maniaImport = importBeatmap(2, new ManiaRuleset().RulesetInfo);
|
||||
confirmBeatmapInSongSelect(maniaImport);
|
||||
presentAndConfirm(maniaImport);
|
||||
|
||||
var catchImport = importBeatmap(3, new CatchRuleset().RulesetInfo);
|
||||
confirmBeatmapInSongSelect(catchImport);
|
||||
presentAndConfirm(catchImport);
|
||||
|
||||
// force ruleset to osu!mania
|
||||
@@ -178,14 +174,14 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
AddUntilStep("wait for carousel loaded", () =>
|
||||
{
|
||||
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
|
||||
var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen;
|
||||
return songSelect.ChildrenOfType<BeatmapCarousel>().SingleOrDefault()?.IsLoaded == true;
|
||||
});
|
||||
|
||||
AddUntilStep("beatmap in song select", () =>
|
||||
{
|
||||
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
|
||||
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().BeatmapSets.Any(b => b.MatchesOnlineID(getImport()));
|
||||
var songSelect = (SoloSongSelect)Game.ScreenStack.CurrentScreen;
|
||||
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().GetCarouselItems()!.Any(i => i.Model is BeatmapSetInfo bsi && bsi.MatchesOnlineID(getImport()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -193,7 +189,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
|
||||
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID));
|
||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset));
|
||||
}
|
||||
@@ -203,7 +199,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 1024 + 2;
|
||||
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
|
||||
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 1024 + 2));
|
||||
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.OnlineID, () => Is.EqualTo(expectedRulesetOnlineID ?? getImport().Beatmaps.First().Ruleset.OnlineID));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,8 @@ using osu.Game.Screens;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using FilterControl = osu.Game.Screens.SelectV2.FilterControl;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
@@ -96,9 +97,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestFromSongSelectWithFilter([Values] ScorePresentType type)
|
||||
{
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("filter to nothing", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).FilterControl.CurrentTextSearch.Value = "fdsajkl;fgewq");
|
||||
AddStep("filter to nothing", () => ((SoloSongSelect)Game.ScreenStack.CurrentScreen).ChildrenOfType<FilterControl>().Single().Search("fdsajkl;fgewq"));
|
||||
AddUntilStep("wait for no results", () => Beatmap.IsDefault);
|
||||
|
||||
var firstImport = importScore(1, new CatchRuleset().RulesetInfo);
|
||||
@@ -109,7 +110,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestFromSongSelectWithConvertRulesetChange([Values] ScorePresentType type)
|
||||
{
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("set convert to false", () => Game.LocalConfig.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
|
||||
|
||||
@@ -121,7 +122,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestFromSongSelect([Values] ScorePresentType type)
|
||||
{
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
|
||||
var firstImport = importScore(1);
|
||||
presentAndConfirm(firstImport, type);
|
||||
@@ -134,7 +135,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type)
|
||||
{
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
|
||||
var firstImport = importScore(1);
|
||||
presentAndConfirm(firstImport, type);
|
||||
@@ -147,7 +148,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestPresentTwoImportsWithSameOnlineIDButDifferentHashes([Values] ScorePresentType type)
|
||||
{
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
|
||||
var firstImport = importScore(1);
|
||||
presentAndConfirm(firstImport, type);
|
||||
@@ -160,7 +161,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestScoreRefetchIgnoresEmptyHash()
|
||||
{
|
||||
AddStep("enter song select", () => Game.ChildrenOfType<ButtonSystem>().Single().OnSolo?.Invoke());
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("song select is current", () => Game.ScreenStack.CurrentScreen is SoloSongSelect songSelect && songSelect.CarouselItemsPresented);
|
||||
|
||||
importScore(-1, hash: string.Empty);
|
||||
importScore(3, hash: @"deadbeef");
|
||||
|
||||
@@ -26,7 +26,6 @@ using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Online.Notifications.WebSocket;
|
||||
using osu.Game.Online.Notifications.WebSocket.Events;
|
||||
using osu.Game.Overlays;
|
||||
@@ -49,20 +48,13 @@ using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.PlayerSettings;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osu.Game.Screens.Select.Options;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using BeatmapCarousel = osu.Game.Screens.Select.BeatmapCarousel;
|
||||
using CollectionDropdown = osu.Game.Collections.CollectionDropdown;
|
||||
using FilterControl = osu.Game.Screens.Select.FilterControl;
|
||||
using FooterButtonRandom = osu.Game.Screens.Select.FooterButtonRandom;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
@@ -146,62 +138,70 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitSongSelectWithEscape()
|
||||
{
|
||||
TestPlaySongSelect songSelect = null;
|
||||
SoloSongSelect songSelect = null;
|
||||
ModSelectOverlay modSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () =>
|
||||
{
|
||||
modSelect = songSelect!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect.Show();
|
||||
});
|
||||
AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible);
|
||||
pushEscape();
|
||||
AddAssert("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||
AddAssert("Overlay was hidden", () => modSelect.State.Value == Visibility.Hidden);
|
||||
exitViaEscapeAndConfirm();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEnterGameplayWhileFilteringToNoSelection()
|
||||
{
|
||||
TestPlaySongSelect songSelect = null;
|
||||
SoloSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("force selection", () =>
|
||||
AddStep("force selection and change filter immediately", () =>
|
||||
{
|
||||
songSelect.FinaliseSelection();
|
||||
songSelect.FilterControl.CurrentTextSearch.Value = "test";
|
||||
InputManager.Key(Key.Enter);
|
||||
songSelect.ChildrenOfType<FilterControl>().Single().Search("test");
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player", () => !songSelect.IsCurrentScreen());
|
||||
AddStep("return to song select", () => songSelect.MakeCurrent());
|
||||
|
||||
AddUntilStep("wait for selection lost", () => songSelect.Beatmap.IsDefault);
|
||||
AddUntilStep("selection not lost", () => !songSelect.Beatmap.IsDefault);
|
||||
AddUntilStep("placeholder visible", () => songSelect.ChildrenOfType<NoResultsPlaceholder>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSongSelectBackActionHandling()
|
||||
{
|
||||
TestPlaySongSelect songSelect = null;
|
||||
SoloSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
|
||||
AddUntilStep("wait for filter control", () => filterControlTextBox().IsLoaded);
|
||||
|
||||
AddStep("set filter", () => filterControlTextBox().Current.Value = "test");
|
||||
AddStep("press back", () => InputManager.Click(MouseButton.Button1));
|
||||
|
||||
AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect);
|
||||
AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen, () => Is.EqualTo(songSelect));
|
||||
AddAssert("filter cleared", () => string.IsNullOrEmpty(filterControlTextBox().Current.Value));
|
||||
|
||||
AddStep("set filter again", () => filterControlTextBox().Current.Value = "test");
|
||||
AddStep("open collections dropdown", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(songSelect.ChildrenOfType<CollectionDropdown>().Single());
|
||||
InputManager.MoveMouseTo(songSelect.ChildrenOfType<Screens.SelectV2.CollectionDropdown>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("press back once", () => InputManager.Click(MouseButton.Button1));
|
||||
AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect);
|
||||
AddAssert("collections dropdown closed", () => songSelect
|
||||
.ChildrenOfType<CollectionDropdown>().Single()
|
||||
.ChildrenOfType<Screens.SelectV2.CollectionDropdown>().Single()
|
||||
.ChildrenOfType<Dropdown<CollectionFilterMenuItem>.DropdownMenu>().Single().State == MenuState.Closed);
|
||||
|
||||
AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1));
|
||||
@@ -210,17 +210,17 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("press back a third time", () => InputManager.Click(MouseButton.Button1));
|
||||
ConfirmAtMainMenu();
|
||||
|
||||
TextBox filterControlTextBox() => songSelect.ChildrenOfType<FilterControl.FilterControlTextBox>().Single();
|
||||
FilterControl.SongSelectSearchTextBox filterControlTextBox() => songSelect.ChildrenOfType<FilterControl.SongSelectSearchTextBox>().Single();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSongSelectRandomRewindButton()
|
||||
{
|
||||
Guid? originalSelection = null;
|
||||
TestPlaySongSelect songSelect = null;
|
||||
SoloSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("Add two beatmaps", () =>
|
||||
{
|
||||
@@ -247,41 +247,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
[Test]
|
||||
public void TestSongSelectScrollHandling()
|
||||
{
|
||||
TestPlaySongSelect songSelect = null;
|
||||
double scrollPosition = 0;
|
||||
|
||||
AddStep("set game volume to max", () => Game.Dependencies.Get<FrameworkConfigManager>().SetValue(FrameworkSetting.VolumeUniversal, 1d));
|
||||
AddUntilStep("wait for volume overlay to hide", () => Game.ChildrenOfType<VolumeOverlay>().SingleOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("store scroll position", () => scrollPosition = getCarouselScrollPosition());
|
||||
|
||||
AddStep("move to left side", () => InputManager.MoveMouseTo(
|
||||
songSelect.ChildrenOfType<Screens.Select.SongSelect.LeftSideInteractionContainer>().Single().ScreenSpaceDrawQuad.TopLeft + new Vector2(1)));
|
||||
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1));
|
||||
AddAssert("carousel didn't move", getCarouselScrollPosition, () => Is.EqualTo(scrollPosition));
|
||||
|
||||
AddRepeatStep("alt-scroll down", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.AltLeft);
|
||||
InputManager.ScrollVerticalBy(-1);
|
||||
InputManager.ReleaseKey(Key.AltLeft);
|
||||
}, 5);
|
||||
AddAssert("game volume decreased", () => Game.Dependencies.Get<FrameworkConfigManager>().Get<double>(FrameworkSetting.VolumeUniversal), () => Is.LessThan(1));
|
||||
|
||||
AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType<BeatmapCarousel>().Single()));
|
||||
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1));
|
||||
AddAssert("carousel moved", getCarouselScrollPosition, () => Is.Not.EqualTo(scrollPosition));
|
||||
|
||||
double getCarouselScrollPosition() => Game.ChildrenOfType<UserTrackingScrollContainer<DrawableCarouselItem>>().Single().Current;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNewSongSelectScrollHandling()
|
||||
{
|
||||
SoloSongSelect songSelect = null;
|
||||
double scrollPosition = 0;
|
||||
@@ -293,6 +258,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for beatmap", () => Game.ChildrenOfType<PanelBeatmapSet>().Any());
|
||||
|
||||
// TODO: this logic can likely be removed when we fix https://github.com/ppy/osu/issues/33379
|
||||
// It should be probably be immediate in this case.
|
||||
AddWaitStep("wait for scroll", 10);
|
||||
|
||||
AddStep("store scroll position", () => scrollPosition = getCarouselScrollPosition());
|
||||
@@ -325,7 +292,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
}, 5);
|
||||
AddAssert("game volume decreased", () => Game.Dependencies.Get<FrameworkConfigManager>().Get<double>(FrameworkSetting.VolumeUniversal), () => Is.LessThan(1));
|
||||
|
||||
AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType<Screens.SelectV2.BeatmapCarousel>().Single()));
|
||||
AddStep("move to carousel", () => InputManager.MoveMouseTo(songSelect.ChildrenOfType<BeatmapCarousel>().Single()));
|
||||
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-1));
|
||||
AddAssert("carousel moved", getCarouselScrollPosition, () => Is.Not.EqualTo(scrollPosition));
|
||||
|
||||
@@ -339,21 +306,21 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestOpenModSelectOverlayUsingAction()
|
||||
{
|
||||
TestPlaySongSelect songSelect = null;
|
||||
SoloSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("Overlay was shown", () => songSelect!.ChildrenOfType<ModSelectOverlay>().Single().State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttemptPlayBeatmapWrongHashFails()
|
||||
{
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
@@ -384,11 +351,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestAttemptPlayBeatmapMissingFails()
|
||||
{
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
@@ -418,9 +385,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
Player player = null;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
@@ -461,9 +428,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
Player player = null;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
@@ -515,9 +482,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
Player player = null;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
@@ -558,9 +525,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
Player player = null;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
@@ -663,7 +630,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
playToResults();
|
||||
|
||||
ScoreInfo score = null;
|
||||
LeaderboardScore scorePanel = null;
|
||||
BeatmapLeaderboardScore scorePanel = null;
|
||||
|
||||
AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score);
|
||||
|
||||
@@ -672,18 +639,11 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action!.Invoke());
|
||||
|
||||
AddStep("show local scores",
|
||||
() => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
|
||||
() => Game.ChildrenOfType<Dropdown<BeatmapLeaderboardScope>>().First().Current.Value = BeatmapLeaderboardScope.Local);
|
||||
|
||||
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
||||
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<BeatmapLeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
||||
|
||||
AddStep("open options", () => InputManager.Key(Key.F3));
|
||||
|
||||
AddStep("choose clear all scores", () => InputManager.Key(Key.Number4));
|
||||
|
||||
AddUntilStep("wait for dialog display", () => ((Drawable)Game.Dependencies.Get<IDialogOverlay>()).IsLoaded);
|
||||
AddUntilStep("wait for dialog", () => Game.Dependencies.Get<IDialogOverlay>().CurrentDialog != null);
|
||||
AddStep("confirm deletion", () => InputManager.Key(Key.Number1));
|
||||
AddUntilStep("wait for dialog dismissed", () => Game.Dependencies.Get<IDialogOverlay>().CurrentDialog == null);
|
||||
AddStep("Clear all scores", () => Game.Dependencies.Get<ScoreManager>().Delete());
|
||||
|
||||
AddUntilStep("ensure score is pending deletion", () => Game.Realm.Run(r => r.Find<ScoreInfo>(score.ID)?.DeletePending == true));
|
||||
|
||||
@@ -696,7 +656,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
playToResults();
|
||||
|
||||
ScoreInfo score = null;
|
||||
LeaderboardScore scorePanel = null;
|
||||
BeatmapLeaderboardScore scorePanel = null;
|
||||
|
||||
AddStep("get score", () => score = ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score);
|
||||
|
||||
@@ -705,9 +665,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action!.Invoke());
|
||||
|
||||
AddStep("show local scores",
|
||||
() => Game.ChildrenOfType<BeatmapDetailAreaTabControl>().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem<BeatmapLeaderboardScope>(BeatmapLeaderboardScope.Local));
|
||||
() => Game.ChildrenOfType<Dropdown<BeatmapLeaderboardScope>>().First().Current.Value = BeatmapLeaderboardScope.Local);
|
||||
|
||||
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<LeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
||||
AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType<BeatmapLeaderboardScore>().FirstOrDefault(s => s.Score.Equals(score))) != null);
|
||||
|
||||
AddStep("right click panel", () =>
|
||||
{
|
||||
@@ -718,7 +678,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("click delete", () =>
|
||||
{
|
||||
var dropdownItem = Game
|
||||
.ChildrenOfType<PlayBeatmapDetailArea>().First()
|
||||
.ChildrenOfType<BeatmapLeaderboardWedge>().First()
|
||||
.ChildrenOfType<OsuContextMenu>().First()
|
||||
.ChildrenOfType<DrawableOsuMenuItem>().First(i => i.Item.Text.ToString() == "Delete");
|
||||
|
||||
@@ -744,9 +704,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
|
||||
|
||||
@@ -777,9 +737,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestMenuMakesMusic()
|
||||
{
|
||||
TestPlaySongSelect songSelect = null;
|
||||
SoloSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
|
||||
AddUntilStep("wait for no track", () => Game.MusicController.CurrentTrack.IsDummyDevice);
|
||||
|
||||
@@ -791,26 +751,32 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestPushSongSelectAndPressBackButtonImmediately()
|
||||
{
|
||||
AddStep("push song select", () => Game.ScreenStack.Push(new TestPlaySongSelect()));
|
||||
AddStep("push song select", () => Game.ScreenStack.Push(new SoloSongSelect()));
|
||||
AddStep("press back button", () => Game.ChildrenOfType<BackButton>().First().Action!.Invoke());
|
||||
AddWaitStep("wait two frames", 2);
|
||||
|
||||
ConfirmAtMainMenu();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExitSongSelectWithClick()
|
||||
{
|
||||
TestPlaySongSelect songSelect = null;
|
||||
SoloSongSelect songSelect = null;
|
||||
ModSelectOverlay modSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () =>
|
||||
{
|
||||
modSelect = songSelect!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect.Show();
|
||||
});
|
||||
AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("Move mouse to dimmed area", () => InputManager.MoveMouseTo(new Vector2(
|
||||
songSelect.ScreenSpaceDrawQuad.TopLeft.X + 1,
|
||||
songSelect.ScreenSpaceDrawQuad.TopLeft.Y + songSelect.ScreenSpaceDrawQuad.Height / 2)));
|
||||
AddStep("Click left mouse button", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||
AddUntilStep("Overlay was hidden", () => modSelect.State.Value == Visibility.Hidden);
|
||||
exitViaBackButtonAndConfirm();
|
||||
}
|
||||
|
||||
@@ -875,10 +841,18 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded);
|
||||
|
||||
TestPlaySongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
SoloSongSelect songSelect = null;
|
||||
ModSelectOverlay modSelect = null;
|
||||
|
||||
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () =>
|
||||
{
|
||||
modSelect = songSelect!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect.Show();
|
||||
});
|
||||
AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("Show mods overlay", () => modSelect.Show());
|
||||
|
||||
AddStep("Change ruleset to osu!taiko", () =>
|
||||
{
|
||||
@@ -889,7 +863,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.OnlineID == 1);
|
||||
|
||||
AddAssert("Mods overlay still visible", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("Mods overlay still visible", () => modSelect.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -899,10 +873,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
TestPlaySongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
SoloSongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("Show options overlay", () => songSelect.BeatmapOptionsOverlay.Show());
|
||||
AddStep("Show options overlay", () => InputManager.Key(Key.F3));
|
||||
AddUntilStep("Options overlay visible", () => this.ChildrenOfType<FooterButtonOptions.Popover>().SingleOrDefault()?.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("Change ruleset to osu!taiko", () =>
|
||||
{
|
||||
@@ -913,7 +889,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.OnlineID == 1);
|
||||
|
||||
AddAssert("Options overlay still visible", () => songSelect.BeatmapOptionsOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("Options overlay still visible", () => this.ChildrenOfType<FooterButtonOptions.Popover>().Single().State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -1185,7 +1161,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitGameFromSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
exitViaEscapeAndConfirm();
|
||||
|
||||
pushEscape(); // returns to osu! logo
|
||||
@@ -1200,6 +1176,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitWithHoldDisabled()
|
||||
{
|
||||
AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType<DialogOverlay>().SingleOrDefault() != null);
|
||||
|
||||
AddStep("set hold delay to 0", () => Game.LocalConfig.SetValue(OsuSetting.UIHoldActivationDelay, 0.0));
|
||||
|
||||
AddStep("press escape twice rapidly", () =>
|
||||
@@ -1255,10 +1233,10 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("close settings sidebar", () => InputManager.Key(Key.Escape));
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
AddRepeatStep("go to solo", () => InputManager.Key(Key.P), 3);
|
||||
AddUntilStep("wait for song select", () => (songSelect = Game.ScreenStack.CurrentScreen as Screens.Select.SongSelect) != null);
|
||||
AddUntilStep("wait for beatmap sets loaded", () => songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for song select", () => (songSelect = Game.ScreenStack.CurrentScreen as Screens.SelectV2.SongSelect) != null);
|
||||
AddUntilStep("wait for beatmap sets loaded", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("switch to osu! ruleset", () =>
|
||||
{
|
||||
@@ -1268,7 +1246,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
});
|
||||
AddStep("touch beatmap wedge", () =>
|
||||
{
|
||||
var wedge = Game.ChildrenOfType<BeatmapInfoWedge>().Single();
|
||||
var wedge = Game.ChildrenOfType<BeatmapTitleWedge>().Single();
|
||||
var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.BeginTouch(touch);
|
||||
InputManager.EndTouch(touch);
|
||||
@@ -1284,7 +1262,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf<ModTouchDevice>());
|
||||
AddStep("touch beatmap wedge", () =>
|
||||
{
|
||||
var wedge = Game.ChildrenOfType<BeatmapInfoWedge>().Single();
|
||||
var wedge = Game.ChildrenOfType<BeatmapTitleWedge>().Single();
|
||||
var touch = new Touch(TouchSource.Touch2, wedge.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.BeginTouch(touch);
|
||||
InputManager.EndTouch(touch);
|
||||
@@ -1301,7 +1279,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("click beatmap wedge", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(Game.ChildrenOfType<BeatmapInfoWedge>().Single());
|
||||
InputManager.MoveMouseTo(Game.ChildrenOfType<BeatmapTitleWedge>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("touch device mod not activated", () => Game.SelectedMods.Value, () => Has.None.InstanceOf<ModTouchDevice>());
|
||||
@@ -1312,7 +1290,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
BeatmapSetInfo beatmapSet = null;
|
||||
|
||||
PushAndConfirm(() => new TestPlaySongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
AddStep("import beatmap", () => beatmapSet = BeatmapImportHelper.LoadQuickOszIntoOsu(Game).GetResultSafely());
|
||||
AddUntilStep("wait for selected", () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet));
|
||||
AddStep("select", () => InputManager.Key(Key.Enter));
|
||||
@@ -1342,9 +1320,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitSongSelectAndImmediatelyClickLogo()
|
||||
{
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
@@ -1373,9 +1351,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
BeatmapSetInfo beatmap = null;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
@@ -1404,9 +1382,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
Screens.SelectV2.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
@@ -1444,12 +1422,5 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
|
||||
ConfirmAtMainMenu();
|
||||
}
|
||||
|
||||
public partial class TestPlaySongSelect : PlaySongSelect
|
||||
{
|
||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||
|
||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.Testing;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@@ -26,17 +27,19 @@ using osu.Game.Screens.Edit.Components;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using static osu.Game.Tests.Visual.Navigation.TestSceneScreenNavigation;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
public partial class TestSceneSkinEditorNavigation : OsuGameTestScene
|
||||
{
|
||||
private TestPlaySongSelect songSelect;
|
||||
private SoloSongSelect songSelect;
|
||||
private ModSelectOverlay modSelect => songSelect.ChildrenOfType<ModSelectOverlay>().First();
|
||||
|
||||
private SkinEditor skinEditor => Game.ChildrenOfType<SkinEditor>().FirstOrDefault();
|
||||
|
||||
[Test]
|
||||
@@ -331,10 +334,10 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public void TestModOverlayClosesOnOpeningSkinEditor()
|
||||
{
|
||||
advanceToSongSelect();
|
||||
AddStep("open mod overlay", () => songSelect.ModSelectOverlay.Show());
|
||||
AddStep("open mod overlay", () => modSelect.Show());
|
||||
|
||||
openSkinEditor();
|
||||
AddUntilStep("mod overlay closed", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
|
||||
AddUntilStep("mod overlay closed", () => modSelect.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -448,8 +451,8 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
private void advanceToSongSelect()
|
||||
{
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.CarouselItemsPresented);
|
||||
}
|
||||
|
||||
private void openSkinEditor()
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Ranking;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Tests copied out of `TestSceneScreenNavigation` which are specific to song select.
|
||||
/// These are for SongSelectV2. Eventually, the tests in the above class should be deleted along with old song select.
|
||||
/// </summary>
|
||||
public partial class TestSceneSongSelectNavigation : OsuGameTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestRetryFromResults()
|
||||
{
|
||||
var getOriginalPlayer = playToResults();
|
||||
|
||||
AddStep("attempt to retry", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).ChildrenOfType<HotkeyRetryOverlay>().First().Action());
|
||||
AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPushSongSelectAndClickBottomLeftCorner()
|
||||
{
|
||||
AddStep("push song select", () => Game.ScreenStack.Push(new SoloSongSelect()));
|
||||
|
||||
// TODO: without this step, a critical bug will be hit, see inline comment in `OsuGame.handleBackButton`.
|
||||
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect select && select.IsLoaded);
|
||||
|
||||
AddStep("click in corner", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.BottomLeft);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
ConfirmAtMainMenu();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPushSongSelectAndPressBackButtonImmediately()
|
||||
{
|
||||
AddStep("push song select", () => Game.ScreenStack.Push(new SoloSongSelect()));
|
||||
|
||||
// TODO: without this step, a critical bug will be hit, see inline comment in `OsuGame.handleBackButton`.
|
||||
AddUntilStep("Wait for song select", () => Game.ScreenStack.CurrentScreen is SoloSongSelect select && select.IsLoaded);
|
||||
|
||||
AddStep("press back button", () => Game.ChildrenOfType<ScreenBackButton>().First().Action!.Invoke());
|
||||
|
||||
ConfirmAtMainMenu();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEditBeatmap()
|
||||
{
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("open menu", () => InputManager.Key(Key.F3));
|
||||
AddStep("trigger edit", () =>
|
||||
{
|
||||
// TODO: should be 5, not 4.
|
||||
InputManager.Key(Key.Number4);
|
||||
});
|
||||
|
||||
waitForScreen<Editor>();
|
||||
|
||||
pushEscape();
|
||||
waitForScreen<SoloSongSelect>();
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestSongContinuesAfterExitPlayer(bool withUserPause)
|
||||
{
|
||||
Player? player = null;
|
||||
|
||||
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
if (withUserPause)
|
||||
AddStep("pause", () => Game.Dependencies.Get<MusicController>().Stop(true));
|
||||
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
DismissAnyNotifications();
|
||||
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for fail", () => player?.GameplayState.HasFailed, () => Is.True);
|
||||
|
||||
AddUntilStep("wait for track stop", () => !Game.MusicController.IsPlaying);
|
||||
AddAssert("Ensure time before preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime);
|
||||
|
||||
pushEscape();
|
||||
|
||||
AddUntilStep("wait for track playing", () => Game.MusicController.IsPlaying);
|
||||
AddAssert("Ensure time wasn't reset to preview point", () => Game.MusicController.CurrentTrack.CurrentTime < beatmap().BeatmapInfo.Metadata.PreviewTime);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAutoplayShortcutReturnsInitialModsOnExit()
|
||||
{
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
|
||||
|
||||
AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("open mod select", () => InputManager.Key(Key.F1));
|
||||
AddStep("search magnetised", () => this.ChildrenOfType<ModSelectOverlay>().Single().SearchTerm = "MG");
|
||||
AddStep("select", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddAssert("magnetised selected", () => Game.SelectedMods.Value.Single(), Is.TypeOf<OsuModMagnetised>);
|
||||
AddStep("configure mod", () => ((OsuModMagnetised)Game.SelectedMods.Value.Single()).AttractionStrength.Value = 1.0f);
|
||||
|
||||
pushEscape();
|
||||
pushEscape();
|
||||
|
||||
AddStep("press ctrl+enter", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ControlLeft);
|
||||
InputManager.Key(Key.Enter);
|
||||
InputManager.ReleaseKey(Key.ControlLeft);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
DismissAnyNotifications();
|
||||
return Game.ScreenStack.CurrentScreen is Player;
|
||||
});
|
||||
|
||||
AddAssert("only autoplay selected", () => Game.SelectedMods.Value.Single(), Is.TypeOf<OsuModAutoplay>);
|
||||
|
||||
pushEscape();
|
||||
waitForScreen<SoloSongSelect>();
|
||||
|
||||
AddAssert("magnetised selected", () => Game.SelectedMods.Value.Single(), Is.TypeOf<OsuModMagnetised>);
|
||||
AddAssert("mod configured", () => ((OsuModMagnetised)Game.SelectedMods.Value.Single()).AttractionStrength.Value, () => Is.EqualTo(1.0f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLeaderboardCorrectInPlayer()
|
||||
{
|
||||
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game, virtualTrack: true).WaitSafely());
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
AddStep("switch to next difficulty and immediately press enter", () =>
|
||||
{
|
||||
InputManager.Key(Key.Down);
|
||||
Schedule(() => InputManager.Key(Key.Enter));
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
DismissAnyNotifications();
|
||||
return Game.ScreenStack.CurrentScreen is Player;
|
||||
});
|
||||
AddAssert("leaderboard matches gameplay beatmap", () => Game.ChildrenOfType<LeaderboardManager>().Single().CurrentCriteria?.Beatmap, () => Is.EqualTo(beatmap().BeatmapInfo));
|
||||
}
|
||||
|
||||
private Func<Player> playToResults()
|
||||
{
|
||||
var player = playToCompletion();
|
||||
AddUntilStep("wait for results", () => (Game.ScreenStack.CurrentScreen as ResultsScreen)?.IsLoaded == true);
|
||||
return player;
|
||||
}
|
||||
|
||||
private Func<Player> playToCompletion()
|
||||
{
|
||||
Player? player = null;
|
||||
|
||||
IWorkingBeatmap beatmap() => Game.Beatmap.Value;
|
||||
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail(), new OsuModDoubleTime { SpeedChange = { Value = 2 } } });
|
||||
|
||||
pushEnter();
|
||||
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
DismissAnyNotifications();
|
||||
return (player = Game.ScreenStack.CurrentScreen as Player) != null;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for track playing", () => beatmap().Track.IsRunning);
|
||||
AddStep("seek to near end", () => player.ChildrenOfType<GameplayClockContainer>().First().Seek(beatmap().Beatmap.HitObjects[^1].StartTime - 1000));
|
||||
AddUntilStep("wait for complete", () => player?.GameplayState.HasPassed, () => Is.True);
|
||||
|
||||
return () => player!;
|
||||
}
|
||||
|
||||
private void waitForScreen<T>() where T : OsuScreen =>
|
||||
AddUntilStep($"Wait for {typeof(T).ReadableName()}", () => Game.ScreenStack.CurrentScreen is T screen && screen.IsLoaded);
|
||||
|
||||
private void pushEnter() =>
|
||||
AddStep("Press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
private void pushEscape() =>
|
||||
AddStep("Press escape", () => InputManager.Key(Key.Escape));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user