mirror of
https://github.com/ppy/osu.git
synced 2026-06-05 17:54:14 +08:00
Merge branch 'master' into feat/block-users
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.321.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.419.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -16,26 +17,30 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
|
||||
int fruits = HitObjects.Count(s => s is Fruit);
|
||||
int juiceStreams = HitObjects.Count(s => s is JuiceStream);
|
||||
int bananaShowers = HitObjects.Count(s => s is BananaShower);
|
||||
int sum = Math.Max(1, fruits + juiceStreams);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Fruit Count",
|
||||
Name = @"Fruits",
|
||||
Content = fruits.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
BarDisplayLength = fruits / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Juice Stream Count",
|
||||
Name = @"Juice Streams",
|
||||
Content = juiceStreams.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
BarDisplayLength = juiceStreams / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Banana Shower Count",
|
||||
Name = @"Banana Showers",
|
||||
Content = bananaShowers.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
BarDisplayLength = Math.Min(bananaShowers / 10f, 1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.BaseVelocity,
|
||||
HintText = EditorSetupStrings.BaseVelocityDescription,
|
||||
KeyboardStep = 0.1f,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||
{
|
||||
Default = 1.4,
|
||||
@@ -89,6 +90,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.TickRate,
|
||||
HintText = EditorSetupStrings.TickRateDescription,
|
||||
KeyboardStep = 1,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||
{
|
||||
Default = 1,
|
||||
|
||||
@@ -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 NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
|
||||
namespace osu.Game.Rulesets.Mania.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class ManiaFilterCriteriaTest
|
||||
{
|
||||
[TestCase]
|
||||
public void TestKeysEqualSingleValue()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.Equal, "1");
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
Mods = [new ManiaModKey1()]
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestKeysEqualMultipleValues()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.Equal, "1,3,5,7");
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
Mods = [new ManiaModKey1()]
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestKeysNotEqualSingleValue()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "1");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
Mods = [new ManiaModKey1()]
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestKeysNotEqualMultipleValues()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "1,3,5,7");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
Mods = [new ManiaModKey1()]
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestKeysGreaterOrEqualThan()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 1 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 2 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 5 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new RulesetInfo { OnlineID = 0 }, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria
|
||||
{
|
||||
Mods = [new ManiaModKey7()]
|
||||
}));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestFilterIntersection()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.Greater, "4");
|
||||
criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "7");
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 3 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 4 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 5 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.False(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 7 }),
|
||||
new FilterCriteria()));
|
||||
|
||||
Assert.True(criteria.Matches(
|
||||
new BeatmapInfo(new ManiaRuleset().RulesetInfo, new BeatmapDifficulty { CircleSize = 9 }),
|
||||
new FilterCriteria()));
|
||||
}
|
||||
|
||||
[TestCase]
|
||||
public void TestInvalidFilters()
|
||||
{
|
||||
var criteria = new ManiaFilterCriteria();
|
||||
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.Equal, "some text"));
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.NotEqual, "4,some text"));
|
||||
Assert.False(criteria.TryParseCustomKeywordCriteria("keys", Operator.GreaterOrEqual, "4,5,6"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
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();
|
||||
|
||||
protected override string? ExportLocation => null;
|
||||
|
||||
private static readonly object[][] score_v2_test_cases =
|
||||
{
|
||||
// With respect to notation,
|
||||
// square brackets `[]` represent *closed* or *inclusive* bounds,
|
||||
// while round brackets `()` represent *open* or *exclusive* bounds.
|
||||
|
||||
// Note that mania hitwindows are heavily idiosyncratic,
|
||||
// and if you *think* a number here is wrong, probably double check.
|
||||
|
||||
// Known issues / complexities:
|
||||
// - There is a disparate set of hitwindow ranges for: score V1 non-converts, score V1 converts, and score V2 (regardless of convert)
|
||||
// - It is NEVER POSSIBLE to get a MEH result when late; exceeding the OK hit windows will result in a MISS.
|
||||
// Additionally, the OK hit window when late is EXCLUSIVE / OPEN rather than INCLUSIVE / CLOSED.
|
||||
// Relevant stable source: https://github.com/peppy/osu-stable-reference/blob/996648fba06baf4e7d2e0b248959399444017895/osu!/GameplayElements/HitObjectManagerMania.cs#L737-L751
|
||||
// - There is also a seemingly mania-specific issue wherein key inputs registered before time instant 0 get truncated to time 0,
|
||||
// which is why the beatmaps used below make sure not to cross that boundary (the note starts at t=300ms).
|
||||
// This is not an issue in osu! or taiko.
|
||||
// The source of this behaviour has not been investigated in detail.
|
||||
|
||||
// OD = 5 test cases.
|
||||
// PERFECT hit window is [ -19ms, 19ms]
|
||||
// GREAT hit window is [ -49ms, 49ms]
|
||||
// GOOD hit window is [ -82ms, 82ms]
|
||||
// OK hit window is [-112ms, 112ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-136ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 5f, -18d, HitResult.Perfect },
|
||||
new object[] { 5f, -19d, HitResult.Perfect },
|
||||
new object[] { 5f, -20d, HitResult.Great },
|
||||
new object[] { 5f, -21d, HitResult.Great },
|
||||
new object[] { 5f, -48d, HitResult.Great },
|
||||
new object[] { 5f, -49d, HitResult.Great },
|
||||
new object[] { 5f, -50d, HitResult.Good },
|
||||
new object[] { 5f, -51d, HitResult.Good },
|
||||
new object[] { 5f, -81d, HitResult.Good },
|
||||
new object[] { 5f, -82d, HitResult.Good },
|
||||
new object[] { 5f, -83d, HitResult.Ok },
|
||||
new object[] { 5f, -84d, HitResult.Ok },
|
||||
new object[] { 5f, -111d, HitResult.Ok },
|
||||
new object[] { 5f, -112d, HitResult.Ok },
|
||||
new object[] { 5f, -113d, HitResult.Meh },
|
||||
new object[] { 5f, -114d, HitResult.Meh },
|
||||
new object[] { 5f, -135d, HitResult.Meh },
|
||||
new object[] { 5f, -136d, HitResult.Meh },
|
||||
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 },
|
||||
|
||||
// OD = 9.3 test cases.
|
||||
// PERFECT hit window is [ -14ms, 14ms]
|
||||
// GREAT hit window is [ -36ms, 36ms]
|
||||
// GOOD hit window is [ -69ms, 69ms]
|
||||
// OK hit window is [ -99ms, 99ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-123ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 9.3f, 13d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 14d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 15d, HitResult.Great },
|
||||
new object[] { 9.3f, 16d, HitResult.Great },
|
||||
new object[] { 9.3f, 35d, HitResult.Great },
|
||||
new object[] { 9.3f, 36d, HitResult.Great },
|
||||
new object[] { 9.3f, 37d, HitResult.Good },
|
||||
new object[] { 9.3f, 38d, HitResult.Good },
|
||||
new object[] { 9.3f, 68d, HitResult.Good },
|
||||
new object[] { 9.3f, 69d, HitResult.Good },
|
||||
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 },
|
||||
new object[] { 9.3f, -98d, HitResult.Ok },
|
||||
new object[] { 9.3f, -99d, HitResult.Ok },
|
||||
new object[] { 9.3f, -100d, HitResult.Meh },
|
||||
new object[] { 9.3f, -101d, HitResult.Meh },
|
||||
new object[] { 9.3f, -122d, HitResult.Meh },
|
||||
new object[] { 9.3f, -123d, HitResult.Meh },
|
||||
new object[] { 9.3f, -124d, HitResult.Miss },
|
||||
new object[] { 9.3f, -125d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_non_convert_test_cases =
|
||||
{
|
||||
// OD = 5 test cases.
|
||||
// PERFECT hit window is [ -16ms, 16ms]
|
||||
// GREAT hit window is [ -49ms, 49ms]
|
||||
// GOOD hit window is [ -82ms, 82ms]
|
||||
// OK hit window is [-112ms, 112ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-136ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 5f, -15d, HitResult.Perfect },
|
||||
new object[] { 5f, -16d, HitResult.Perfect },
|
||||
new object[] { 5f, -17d, HitResult.Great },
|
||||
new object[] { 5f, -18d, HitResult.Great },
|
||||
new object[] { 5f, -48d, HitResult.Great },
|
||||
new object[] { 5f, -49d, HitResult.Great },
|
||||
new object[] { 5f, -50d, HitResult.Good },
|
||||
new object[] { 5f, -51d, HitResult.Good },
|
||||
new object[] { 5f, -81d, HitResult.Good },
|
||||
new object[] { 5f, -82d, HitResult.Good },
|
||||
new object[] { 5f, -83d, HitResult.Ok },
|
||||
new object[] { 5f, -84d, HitResult.Ok },
|
||||
new object[] { 5f, -111d, HitResult.Ok },
|
||||
new object[] { 5f, -112d, HitResult.Ok },
|
||||
new object[] { 5f, -113d, HitResult.Meh },
|
||||
new object[] { 5f, -114d, HitResult.Meh },
|
||||
new object[] { 5f, -135d, HitResult.Meh },
|
||||
new object[] { 5f, -136d, HitResult.Meh },
|
||||
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 },
|
||||
|
||||
// OD = 9.3 test cases.
|
||||
// PERFECT hit window is [ -16ms, 16ms]
|
||||
// GREAT hit window is [ -36ms, 36ms]
|
||||
// GOOD hit window is [ -69ms, 69ms]
|
||||
// OK hit window is [ -99ms, 99ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-123ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 9.3f, 15d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 16d, HitResult.Perfect },
|
||||
new object[] { 9.3f, 17d, HitResult.Great },
|
||||
new object[] { 9.3f, 18d, HitResult.Great },
|
||||
new object[] { 9.3f, 35d, HitResult.Great },
|
||||
new object[] { 9.3f, 36d, HitResult.Great },
|
||||
new object[] { 9.3f, 37d, HitResult.Good },
|
||||
new object[] { 9.3f, 38d, HitResult.Good },
|
||||
new object[] { 9.3f, 68d, HitResult.Good },
|
||||
new object[] { 9.3f, 69d, HitResult.Good },
|
||||
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 },
|
||||
new object[] { 9.3f, -98d, HitResult.Ok },
|
||||
new object[] { 9.3f, -99d, HitResult.Ok },
|
||||
new object[] { 9.3f, -100d, HitResult.Meh },
|
||||
new object[] { 9.3f, -101d, HitResult.Meh },
|
||||
new object[] { 9.3f, -122d, HitResult.Meh },
|
||||
new object[] { 9.3f, -123d, HitResult.Meh },
|
||||
new object[] { 9.3f, -124d, HitResult.Miss },
|
||||
new object[] { 9.3f, -125d, HitResult.Miss },
|
||||
|
||||
// OD = 3.1 test cases.
|
||||
// PERFECT hit window is [ -16ms, 16ms]
|
||||
// GREAT hit window is [ -54ms, 54ms]
|
||||
// GOOD hit window is [ -87ms, 87ms]
|
||||
// OK hit window is [-117ms, 117ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-141ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 3.1f, 15d, HitResult.Perfect },
|
||||
new object[] { 3.1f, 16d, HitResult.Perfect },
|
||||
new object[] { 3.1f, 17d, HitResult.Great },
|
||||
new object[] { 3.1f, 18d, HitResult.Great },
|
||||
new object[] { 3.1f, 53d, HitResult.Great },
|
||||
new object[] { 3.1f, 54d, HitResult.Great },
|
||||
new object[] { 3.1f, 55d, HitResult.Good },
|
||||
new object[] { 3.1f, 56d, HitResult.Good },
|
||||
new object[] { 3.1f, 86d, HitResult.Good },
|
||||
new object[] { 3.1f, 87d, HitResult.Good },
|
||||
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 },
|
||||
new object[] { 3.1f, -116d, HitResult.Ok },
|
||||
new object[] { 3.1f, -117d, HitResult.Ok },
|
||||
new object[] { 3.1f, -118d, HitResult.Meh },
|
||||
new object[] { 3.1f, -119d, HitResult.Meh },
|
||||
new object[] { 3.1f, -140d, HitResult.Meh },
|
||||
new object[] { 3.1f, -141d, HitResult.Meh },
|
||||
new object[] { 3.1f, -142d, HitResult.Miss },
|
||||
new object[] { 3.1f, -143d, HitResult.Miss },
|
||||
};
|
||||
|
||||
private static readonly object[][] score_v1_convert_test_cases =
|
||||
{
|
||||
// OD = 5 test cases.
|
||||
// PERFECT hit window is [ -16ms, 16ms]
|
||||
// GREAT hit window is [ -34ms, 34ms]
|
||||
// GOOD hit window is [ -67ms, 67ms]
|
||||
// OK hit window is [ -97ms, 97ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-121ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 5f, -15d, HitResult.Perfect },
|
||||
new object[] { 5f, -16d, HitResult.Perfect },
|
||||
new object[] { 5f, -17d, HitResult.Great },
|
||||
new object[] { 5f, -18d, HitResult.Great },
|
||||
new object[] { 5f, -33d, HitResult.Great },
|
||||
new object[] { 5f, -34d, HitResult.Great },
|
||||
new object[] { 5f, -35d, HitResult.Good },
|
||||
new object[] { 5f, -36d, HitResult.Good },
|
||||
new object[] { 5f, -66d, HitResult.Good },
|
||||
new object[] { 5f, -67d, HitResult.Good },
|
||||
new object[] { 5f, -68d, HitResult.Ok },
|
||||
new object[] { 5f, -69d, HitResult.Ok },
|
||||
new object[] { 5f, -96d, HitResult.Ok },
|
||||
new object[] { 5f, -97d, HitResult.Ok },
|
||||
new object[] { 5f, -98d, HitResult.Meh },
|
||||
new object[] { 5f, -99d, HitResult.Meh },
|
||||
new object[] { 5f, -120d, HitResult.Meh },
|
||||
new object[] { 5f, -121d, HitResult.Meh },
|
||||
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 },
|
||||
|
||||
// OD = 3.1 test cases.
|
||||
// PERFECT hit window is [ -16ms, 16ms]
|
||||
// GREAT hit window is [ -47ms, 47ms]
|
||||
// GOOD hit window is [ -77ms, 77ms]
|
||||
// OK hit window is [ -97ms, 97ms) <- not a typo, this side of the interval is OPEN!
|
||||
// MEH hit window is [-121ms, -----) <- it is NOT POSSIBLE to get a MEH result on a late hit!
|
||||
new object[] { 3.1f, 15d, HitResult.Perfect },
|
||||
new object[] { 3.1f, 16d, HitResult.Perfect },
|
||||
new object[] { 3.1f, 17d, HitResult.Great },
|
||||
new object[] { 3.1f, 18d, HitResult.Great },
|
||||
new object[] { 3.1f, 46d, HitResult.Great },
|
||||
new object[] { 3.1f, 47d, HitResult.Great },
|
||||
new object[] { 3.1f, 48d, HitResult.Good },
|
||||
new object[] { 3.1f, 49d, HitResult.Good },
|
||||
new object[] { 3.1f, 76d, HitResult.Good },
|
||||
new object[] { 3.1f, 77d, HitResult.Good },
|
||||
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 },
|
||||
new object[] { 3.1f, -96d, HitResult.Ok },
|
||||
new object[] { 3.1f, -97d, HitResult.Ok },
|
||||
new object[] { 3.1f, -98d, HitResult.Meh },
|
||||
new object[] { 3.1f, -99d, HitResult.Meh },
|
||||
new object[] { 3.1f, -120d, HitResult.Meh },
|
||||
new object[] { 3.1f, -121d, HitResult.Meh },
|
||||
new object[] { 3.1f, -122d, HitResult.Miss },
|
||||
new object[] { 3.1f, -123d, HitResult.Miss },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(score_v2_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV2(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 300;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = note_time,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
CircleSize = 1,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new ModScoreV2()]
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"SV2 single note @ OD{overallDifficulty}", beatmap, $@"SV2 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(score_v1_non_convert_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1NonConvert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 300;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = note_time,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
CircleSize = 1,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"SV1 single note @ OD{overallDifficulty}", beatmap, $@"SV1 {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(score_v1_convert_test_cases))]
|
||||
public void TestHitWindowTreatmentWithScoreV1Convert(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 300;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new Beatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new FakeCircle
|
||||
{
|
||||
StartTime = note_time,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new RulesetInfo { OnlineID = 0 }
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
Mods = [new ManiaModKey1()],
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"SV1 convert single note @ OD{overallDifficulty}", beatmap, $@"SV1 convert {hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
|
||||
private class FakeCircle : HitObject, IHasPosition
|
||||
{
|
||||
public float X
|
||||
{
|
||||
get => Position.X;
|
||||
set => Position = new Vector2(value, Position.Y);
|
||||
}
|
||||
|
||||
public float Y
|
||||
{
|
||||
get => Position.Y;
|
||||
set => Position = new Vector2(Position.X, value);
|
||||
}
|
||||
|
||||
public Vector2 Position { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Objects;
|
||||
using osu.Game.Rulesets.Mania.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
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 =
|
||||
{
|
||||
// With respect to notation,
|
||||
// square brackets `[]` represent *closed* or *inclusive* bounds,
|
||||
// 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]
|
||||
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.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.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.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.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]
|
||||
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.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.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.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.95d, HitResult.Miss },
|
||||
new object[] { 9.3f, 124d, HitResult.Miss },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestHitWindowStability(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double note_time = 100;
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(1))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = note_time,
|
||||
Column = 0,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = overallDifficulty,
|
||||
CircleSize = 1,
|
||||
},
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new ManiaRuleset().RulesetInfo,
|
||||
},
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(0),
|
||||
new ManiaReplayFrame(note_time + hitOffset, ManiaAction.Key1),
|
||||
new ManiaReplayFrame(note_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
RunTest(beatmap, replay, [expectedResult]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,20 +36,23 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
|
||||
{
|
||||
int notes = HitObjects.Count(s => s is Note);
|
||||
int holdNotes = HitObjects.Count(s => s is HoldNote);
|
||||
int sum = Math.Max(1, notes + holdNotes);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Note Count",
|
||||
Name = @"Notes",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = notes.ToString(),
|
||||
BarDisplayLength = notes / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hold Note Count",
|
||||
Name = @"Hold Notes",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = holdNotes.ToString(),
|
||||
BarDisplayLength = holdNotes / (float)sum,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Configuration.Tracking;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Localisation;
|
||||
@@ -25,17 +24,6 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
SetDefault(ManiaRulesetSetting.ScrollDirection, ManiaScrollingDirection.Down);
|
||||
SetDefault(ManiaRulesetSetting.TimingBasedNoteColouring, false);
|
||||
SetDefault(ManiaRulesetSetting.MobileLayout, ManiaMobileLayout.Portrait);
|
||||
|
||||
#pragma warning disable CS0618
|
||||
// Although obsolete, this is still required to populate the bindable from the database in case migration is required.
|
||||
SetDefault<double?>(ManiaRulesetSetting.ScrollTime, null);
|
||||
|
||||
if (Get<double?>(ManiaRulesetSetting.ScrollTime) is double scrollTime)
|
||||
{
|
||||
SetValue(ManiaRulesetSetting.ScrollSpeed, Math.Round(DrawableManiaRuleset.MAX_TIME_RANGE / scrollTime));
|
||||
SetValue<double?>(ManiaRulesetSetting.ScrollTime, null);
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
|
||||
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
|
||||
@@ -52,8 +40,6 @@ namespace osu.Game.Rulesets.Mania.Configuration
|
||||
|
||||
public enum ManiaRulesetSetting
|
||||
{
|
||||
[Obsolete("Use ScrollSpeed instead.")] // Can be removed 2023-11-30
|
||||
ScrollTime,
|
||||
ScrollSpeed,
|
||||
ScrollDirection,
|
||||
TimingBasedNoteColouring,
|
||||
|
||||
@@ -89,6 +89,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.BaseVelocity,
|
||||
HintText = EditorSetupStrings.BaseVelocityDescription,
|
||||
KeyboardStep = 0.1f,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||
{
|
||||
Default = 1.4,
|
||||
@@ -103,6 +104,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.TickRate,
|
||||
HintText = EditorSetupStrings.TickRateDescription,
|
||||
KeyboardStep = 1,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||
{
|
||||
Default = 1,
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Rulesets.Filter;
|
||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||
using osu.Game.Rulesets.Mania.Mods;
|
||||
@@ -17,20 +18,72 @@ namespace osu.Game.Rulesets.Mania
|
||||
{
|
||||
public class ManiaFilterCriteria : IRulesetFilterCriteria
|
||||
{
|
||||
private FilterCriteria.OptionalRange<float> keys;
|
||||
private readonly HashSet<int> includedKeyCounts = Enumerable.Range(1, LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT).ToHashSet();
|
||||
|
||||
public bool Matches(BeatmapInfo beatmapInfo, FilterCriteria criteria)
|
||||
{
|
||||
return !keys.HasFilter || keys.IsInRange(ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods));
|
||||
int keyCount = ManiaBeatmapConverter.GetColumnCount(LegacyBeatmapConversionDifficultyInfo.FromBeatmapInfo(beatmapInfo), criteria.Mods);
|
||||
|
||||
return includedKeyCounts.Contains(keyCount);
|
||||
}
|
||||
|
||||
public bool TryParseCustomKeywordCriteria(string key, Operator op, string value)
|
||||
public bool TryParseCustomKeywordCriteria(string key, Operator op, string strValues)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case "key":
|
||||
case "keys":
|
||||
return FilterQueryParser.TryUpdateCriteriaRange(ref keys, op, value);
|
||||
{
|
||||
var keyCounts = new HashSet<int>();
|
||||
|
||||
foreach (string strValue in strValues.Split(','))
|
||||
{
|
||||
if (!int.TryParse(strValue, out int keyCount))
|
||||
return false;
|
||||
|
||||
keyCounts.Add(keyCount);
|
||||
}
|
||||
|
||||
int? singleKeyCount = keyCounts.Count == 1 ? keyCounts.Single() : null;
|
||||
|
||||
switch (op)
|
||||
{
|
||||
case Operator.Equal:
|
||||
includedKeyCounts.IntersectWith(keyCounts);
|
||||
return true;
|
||||
|
||||
case Operator.NotEqual:
|
||||
includedKeyCounts.ExceptWith(keyCounts);
|
||||
return true;
|
||||
|
||||
case Operator.Less:
|
||||
if (singleKeyCount == null) return false;
|
||||
|
||||
includedKeyCounts.RemoveWhere(k => k >= singleKeyCount.Value);
|
||||
return true;
|
||||
|
||||
case Operator.LessOrEqual:
|
||||
if (singleKeyCount == null) return false;
|
||||
|
||||
includedKeyCounts.RemoveWhere(k => k > singleKeyCount.Value);
|
||||
return true;
|
||||
|
||||
case Operator.Greater:
|
||||
if (singleKeyCount == null) return false;
|
||||
|
||||
includedKeyCounts.RemoveWhere(k => k <= singleKeyCount.Value);
|
||||
return true;
|
||||
|
||||
case Operator.GreaterOrEqual:
|
||||
if (singleKeyCount == null) return false;
|
||||
|
||||
includedKeyCounts.RemoveWhere(k => k < singleKeyCount.Value);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -38,7 +91,7 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
public bool FilterMayChangeFromMods(ValueChangedEvent<IReadOnlyList<Mod>> mods)
|
||||
{
|
||||
if (keys.HasFilter)
|
||||
if (includedKeyCounts.Count != LegacyBeatmapDecoder.MAX_MANIA_KEY_COUNT)
|
||||
{
|
||||
// Interpreting as the Mod type is required for equality comparison.
|
||||
HashSet<Mod> oldSet = mods.OldValue.OfType<ManiaKeyMod>().AsEnumerable<Mod>().ToHashSet();
|
||||
|
||||
@@ -60,8 +60,9 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
private readonly BindableDouble configScrollSpeed = new BindableDouble();
|
||||
private readonly Bindable<ManiaMobileLayout> mobileLayout = new Bindable<ManiaMobileLayout>();
|
||||
|
||||
public double TargetTimeRange { get; protected set; }
|
||||
|
||||
private double currentTimeRange;
|
||||
protected double TargetTimeRange;
|
||||
|
||||
// Stores the current speed adjustment active in gameplay.
|
||||
private readonly Track speedAdjustmentTrack = new TrackVirtual(0);
|
||||
@@ -109,7 +110,13 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
configDirection.BindValueChanged(direction => Direction.Value = (ScrollingDirection)direction.NewValue, true);
|
||||
|
||||
Config.BindWith(ManiaRulesetSetting.ScrollSpeed, configScrollSpeed);
|
||||
configScrollSpeed.BindValueChanged(speed => TargetTimeRange = ComputeScrollTime(speed.NewValue));
|
||||
configScrollSpeed.BindValueChanged(speed =>
|
||||
{
|
||||
if (!AllowScrollSpeedAdjustment)
|
||||
return;
|
||||
|
||||
TargetTimeRange = ComputeScrollTime(speed.NewValue);
|
||||
});
|
||||
|
||||
TimeRange.Value = TargetTimeRange = currentTimeRange = ComputeScrollTime(configScrollSpeed.Value);
|
||||
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual;
|
||||
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();
|
||||
|
||||
protected override string? ExportLocation => null;
|
||||
|
||||
private static readonly object[][] test_cases =
|
||||
{
|
||||
// With respect to notation,
|
||||
// square brackets `[]` represent *closed* or *inclusive* bounds,
|
||||
// while round brackets `()` represent *open* or *exclusive* bounds.
|
||||
// Additionally, note that offsets provided in double will be rounded to the nearest integer.
|
||||
|
||||
// OD = 5 test cases.
|
||||
// GREAT hit window is ( -50ms, 50ms)
|
||||
// OK hit window is (-100ms, 100ms)
|
||||
// MEH hit window is (-150ms, 150ms)
|
||||
new object[] { 5f, 48d, HitResult.Great },
|
||||
new object[] { 5f, 49d, HitResult.Great },
|
||||
new object[] { 5f, 50d, HitResult.Ok },
|
||||
new object[] { 5f, 51d, HitResult.Ok },
|
||||
new object[] { 5f, 98d, HitResult.Ok },
|
||||
new object[] { 5f, 99d, HitResult.Ok },
|
||||
new object[] { 5f, 100d, HitResult.Meh },
|
||||
new object[] { 5f, 101d, HitResult.Meh },
|
||||
new object[] { 5f, 148d, HitResult.Meh },
|
||||
new object[] { 5f, 149d, HitResult.Meh },
|
||||
new object[] { 5f, 150d, HitResult.Miss },
|
||||
new object[] { 5f, 151d, HitResult.Miss },
|
||||
|
||||
// OD = 5.7 test cases.
|
||||
// GREAT hit window is ( -45ms, 45ms)
|
||||
// OK hit window is ( -94ms, 94ms)
|
||||
// MEH hit window is (-143ms, 143ms)
|
||||
new object[] { 5.7f, 43d, HitResult.Great },
|
||||
new object[] { 5.7f, 44d, HitResult.Great },
|
||||
new object[] { 5.7f, 45d, HitResult.Ok },
|
||||
new object[] { 5.7f, 46d, HitResult.Ok },
|
||||
new object[] { 5.7f, 92d, HitResult.Ok },
|
||||
new object[] { 5.7f, 93d, HitResult.Ok },
|
||||
new object[] { 5.7f, 94d, HitResult.Meh },
|
||||
new object[] { 5.7f, 95d, HitResult.Meh },
|
||||
new object[] { 5.7f, 141d, HitResult.Meh },
|
||||
new object[] { 5.7f, 142d, HitResult.Meh },
|
||||
new object[] { 5.7f, 143d, HitResult.Miss },
|
||||
new object[] { 5.7f, 144d, HitResult.Miss },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestHitWindowTreatment(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double hit_circle_time = 100;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new OsuBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = hit_circle_time,
|
||||
Position = OsuPlayfield.BASE_SIZE / 2
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty { OverallDifficulty = overallDifficulty },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
// required for correct playback in stable
|
||||
new OsuReplayFrame(0, new Vector2(256, -500)),
|
||||
new OsuReplayFrame(0, new Vector2(256, -500)),
|
||||
new OsuReplayFrame(0, OsuPlayfield.BASE_SIZE / 2),
|
||||
new OsuReplayFrame(hit_circle_time + hitOffset, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(hit_circle_time + hitOffset + 20, OsuPlayfield.BASE_SIZE / 2),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"single circle @ OD{overallDifficulty}", beatmap, $@"{hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
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 =
|
||||
{
|
||||
// With respect to notation,
|
||||
// square brackets `[]` represent *closed* or *inclusive* bounds,
|
||||
// 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]
|
||||
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, 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, 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, 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 },
|
||||
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, 143.4d, HitResult.Miss },
|
||||
new object[] { 5.7f, 143.9d, HitResult.Miss },
|
||||
new object[] { 5.7f, 144d, HitResult.Miss },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestHitWindowStability(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double hit_circle_time = 100;
|
||||
|
||||
var beatmap = new OsuBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle
|
||||
{
|
||||
StartTime = hit_circle_time,
|
||||
Position = OsuPlayfield.BASE_SIZE / 2
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty { OverallDifficulty = overallDifficulty },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
},
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new OsuReplayFrame(0, OsuPlayfield.BASE_SIZE / 2),
|
||||
new OsuReplayFrame(hit_circle_time + hitOffset, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton),
|
||||
new OsuReplayFrame(hit_circle_time + hitOffset + 20, OsuPlayfield.BASE_SIZE / 2),
|
||||
}
|
||||
};
|
||||
|
||||
RunTest(beatmap, replay, [expectedResult]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,9 +86,12 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestSpinningSamplePitchShift()
|
||||
{
|
||||
PausableSkinnableSound spinSample = null;
|
||||
|
||||
AddStep("Add spinner", () => SetContents(_ => testSingle(5, true, 4000)));
|
||||
AddUntilStep("Pitch starts low", () => getSpinningSample().Frequency.Value < 0.8);
|
||||
AddUntilStep("Pitch increases", () => getSpinningSample().Frequency.Value > 0.8);
|
||||
AddUntilStep("wait for spin sample", () => (spinSample = getSpinningSample()) != null);
|
||||
AddUntilStep("Pitch starts low", () => spinSample.Frequency.Value < 0.8);
|
||||
AddUntilStep("Pitch increases", () => spinSample.Frequency.Value > 0.8);
|
||||
|
||||
PausableSkinnableSound getSpinningSample() =>
|
||||
drawableSpinner.ChildrenOfType<PausableSkinnableSound>().FirstOrDefault(s => s.Samples.Any(i => i.LookupNames.Any(l => l.Contains("spinnerspin"))));
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
@@ -16,26 +16,30 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
int circles = HitObjects.Count(c => c is HitCircle);
|
||||
int sliders = HitObjects.Count(s => s is Slider);
|
||||
int spinners = HitObjects.Count(s => s is Spinner);
|
||||
int sum = Math.Max(1, circles + sliders);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = BeatmapsetsStrings.ShowStatsCountCircles,
|
||||
Name = "Circles",
|
||||
Content = circles.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
BarDisplayLength = circles / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = BeatmapsetsStrings.ShowStatsCountSliders,
|
||||
Name = "Sliders",
|
||||
Content = sliders.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
BarDisplayLength = sliders / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Spinner Count",
|
||||
Name = @"Spinners",
|
||||
Content = spinners.ToString(),
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
BarDisplayLength = Math.Min(spinners / 10f, 1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Current = new BindableNumber<int>(3)
|
||||
{
|
||||
MinValue = 3,
|
||||
MaxValue = 10,
|
||||
MaxValue = 32,
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true
|
||||
|
||||
@@ -91,6 +91,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.BaseVelocity,
|
||||
HintText = EditorSetupStrings.BaseVelocityDescription,
|
||||
KeyboardStep = 0.1f,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||
{
|
||||
Default = 1.4,
|
||||
@@ -105,6 +106,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.TickRate,
|
||||
HintText = EditorSetupStrings.TickRateDescription,
|
||||
KeyboardStep = 1,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||
{
|
||||
Default = 1,
|
||||
@@ -119,6 +121,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup
|
||||
{
|
||||
Caption = "Stack Leniency",
|
||||
HintText = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.",
|
||||
KeyboardStep = 0.1f,
|
||||
Current = new BindableFloat(Beatmap.StackLeniency)
|
||||
{
|
||||
Default = 0.7f,
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
var spinner = (DrawableSpinner)drawable;
|
||||
|
||||
spinner.RotationTracker.Tracking = true;
|
||||
spinner.RotationTracker.Tracking = spinner.RotationTracker.IsSpinnableTime;
|
||||
|
||||
// early-return if we were paused to avoid division-by-zero in the subsequent calculations.
|
||||
if (Precision.AlmostEquals(spinner.Clock.Rate, 0))
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@@ -149,5 +150,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
protected float CalculateDrawableRelativePosition(Drawable drawable) => (drawable.ScreenSpaceDrawQuad.Centre.X - parentScreenSpaceRectangle.X) / parentScreenSpaceRectangle.Width;
|
||||
|
||||
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(HitObject, judgement);
|
||||
|
||||
protected void ApplyRepeatFadeIn(Drawable target, double fadeTime)
|
||||
{
|
||||
DrawableSlider slider = (DrawableSlider)ParentHitObject;
|
||||
int repeatIndex = ((SliderEndCircle)HitObject).RepeatIndex;
|
||||
|
||||
Debug.Assert(slider != null);
|
||||
|
||||
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||
bool delayFadeIn = slider.SliderBody?.SnakingIn.Value == true && repeatIndex == 0;
|
||||
|
||||
if (repeatIndex > 0)
|
||||
fadeTime = Math.Min(slider.HitObject.SpanDuration, fadeTime);
|
||||
|
||||
target
|
||||
.FadeOut()
|
||||
.Delay(delayFadeIn ? (slider.HitObject.TimePreempt) / 3 : 0)
|
||||
.FadeIn(fadeTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
public DrawableSlider DrawableSlider => (DrawableSlider)ParentHitObject;
|
||||
|
||||
private double animDuration;
|
||||
|
||||
public SkinnableDrawable CirclePiece { get; private set; }
|
||||
|
||||
public SkinnableDrawable Arrow { get; private set; }
|
||||
@@ -87,21 +85,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
|
||||
protected override void UpdateInitialTransforms()
|
||||
{
|
||||
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||
base.UpdateInitialTransforms();
|
||||
|
||||
animDuration = Math.Min(300, HitObject.SpanDuration);
|
||||
|
||||
this
|
||||
.FadeOut()
|
||||
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||
.FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration);
|
||||
ApplyRepeatFadeIn(CirclePiece, HitObject.TimeFadeIn);
|
||||
ApplyRepeatFadeIn(Arrow, 150);
|
||||
}
|
||||
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
{
|
||||
base.UpdateHitStateTransforms(state);
|
||||
|
||||
double animDuration = Math.Min(300, HitObject.SpanDuration);
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
@@ -181,10 +176,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
// More or less matches stable (see https://github.com/peppy/osu-stable-reference/blob/bb57924c1552adbed11ee3d96cdcde47cf96f2b6/osu!/GameplayElements/HitObjects/Osu/HitCircleOsu.cs#L336-L338)
|
||||
AccentColour.Value = Color4.White;
|
||||
Alpha = Interpolation.ValueAt(Time.Current, 1f, 0f, HitStateUpdateTime, HitStateUpdateTime + 700);
|
||||
Arrow.Alpha = 0;
|
||||
}
|
||||
|
||||
Arrow.Alpha = hit ? 0 : 1;
|
||||
|
||||
LifetimeEnd = HitStateUpdateTime + 700;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,13 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
{
|
||||
base.UpdateInitialTransforms();
|
||||
|
||||
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||
|
||||
CirclePiece
|
||||
.FadeOut()
|
||||
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||
.FadeIn(HitObject.TimeFadeIn);
|
||||
ApplyRepeatFadeIn(CirclePiece, HitObject.TimeFadeIn);
|
||||
}
|
||||
|
||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||
|
||||
@@ -277,13 +277,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||
base.Update();
|
||||
|
||||
if (HandleUserInput)
|
||||
{
|
||||
bool isValidSpinningTime = Time.Current >= HitObject.StartTime && Time.Current <= HitObject.EndTime;
|
||||
|
||||
RotationTracker.Tracking = !Result.HasResult
|
||||
&& correctButtonPressed()
|
||||
&& isValidSpinningTime;
|
||||
}
|
||||
RotationTracker.Tracking = RotationTracker.IsSpinnableTime && !Result.HasResult && correctButtonPressed();
|
||||
|
||||
if (spinningSample != null && spinnerFrequencyModulate)
|
||||
spinningSample.Frequency.Value = spinning_sample_modulated_base_frequency + Progress;
|
||||
|
||||
@@ -85,9 +85,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
|
||||
{
|
||||
double animDuration = Math.Min(300, drawableRepeat.HitObject.SpanDuration);
|
||||
Scale = new Vector2(Interpolation.ValueAt(Time.Current, 1, 1.5f, drawableRepeat.HitStateUpdateTime, drawableRepeat.HitStateUpdateTime + animDuration, Easing.Out));
|
||||
|
||||
// When hit, don't animate further. This avoids a scale being applied on a scale and looking very weird.
|
||||
return;
|
||||
}
|
||||
else
|
||||
Scale = Vector2.One;
|
||||
|
||||
Scale = Vector2.One;
|
||||
|
||||
const float move_distance = -12;
|
||||
const float scale_amount = 1.3f;
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
/// <summary>
|
||||
/// Whether currently in the correct time range to allow spinning.
|
||||
/// </summary>
|
||||
private bool isSpinnableTime => drawableSpinner.HitObject.StartTime <= Time.Current && drawableSpinner.HitObject.EndTime > Time.Current;
|
||||
public bool IsSpinnableTime => drawableSpinner.HitObject.StartTime <= Time.Current && drawableSpinner.HitObject.EndTime > Time.Current;
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
@@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
lastAngle = thisAngle;
|
||||
}
|
||||
|
||||
IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation - Rotation) > 10f;
|
||||
IsSpinning.Value = IsSpinnableTime && Math.Abs(currentRotation - Rotation) > 10f;
|
||||
Rotation = (float)Interpolation.Damp(Rotation, currentRotation, 0.99, Math.Abs(Time.Elapsed));
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default
|
||||
/// <param name="delta">The delta angle.</param>
|
||||
public void AddRotation(float delta)
|
||||
{
|
||||
if (!isSpinnableTime)
|
||||
if (!IsSpinnableTime)
|
||||
return;
|
||||
|
||||
if (!rotationTransferred)
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
using osu.Game.Scoring;
|
||||
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;
|
||||
|
||||
protected override Ruleset CreateRuleset() => new TaikoRuleset();
|
||||
|
||||
private static readonly object[][] test_cases =
|
||||
{
|
||||
// With respect to notation,
|
||||
// square brackets `[]` represent *closed* or *inclusive* bounds,
|
||||
// while round brackets `()` represent *open* or *exclusive* bounds.
|
||||
|
||||
// OD = 5 test cases.
|
||||
// GREAT hit window is (-35ms, 35ms)
|
||||
// OK hit window is (-80ms, 80ms)
|
||||
new object[] { 5f, -33d, HitResult.Great },
|
||||
new object[] { 5f, -34d, HitResult.Great },
|
||||
new object[] { 5f, -35d, HitResult.Ok },
|
||||
new object[] { 5f, -36d, HitResult.Ok },
|
||||
new object[] { 5f, -78d, HitResult.Ok },
|
||||
new object[] { 5f, -79d, HitResult.Ok },
|
||||
new object[] { 5f, -80d, HitResult.Miss },
|
||||
new object[] { 5f, -81d, HitResult.Miss },
|
||||
|
||||
// OD = 7.8 test cases.
|
||||
// GREAT hit window is (-26ms, 26ms)
|
||||
// OK hit window is (-63ms, 63ms)
|
||||
new object[] { 7.8f, -24d, HitResult.Great },
|
||||
new object[] { 7.8f, -25d, HitResult.Great },
|
||||
new object[] { 7.8f, -26d, HitResult.Ok },
|
||||
new object[] { 7.8f, -27d, HitResult.Ok },
|
||||
new object[] { 7.8f, -61d, HitResult.Ok },
|
||||
new object[] { 7.8f, -62d, HitResult.Ok },
|
||||
new object[] { 7.8f, -63d, HitResult.Miss },
|
||||
new object[] { 7.8f, -64d, HitResult.Miss },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestHitWindowTreatment(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double hit_time = 100;
|
||||
|
||||
var cpi = new ControlPointInfo();
|
||||
cpi.Add(0, new TimingControlPoint { BeatLength = 1000 });
|
||||
var beatmap = new TaikoBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = hit_time,
|
||||
Type = HitType.Centre,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty { OverallDifficulty = overallDifficulty },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
},
|
||||
ControlPointInfo = cpi,
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new TaikoReplayFrame(0),
|
||||
new TaikoReplayFrame(hit_time + hitOffset, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(hit_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
var score = new Score
|
||||
{
|
||||
Replay = replay,
|
||||
ScoreInfo = new ScoreInfo
|
||||
{
|
||||
Ruleset = CreateRuleset().RulesetInfo,
|
||||
}
|
||||
};
|
||||
|
||||
RunTest($@"single hit @ OD{overallDifficulty}", beatmap, $@"{hitOffset}ms @ OD{overallDifficulty} = {expectedResult}", score, [expectedResult]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Beatmaps;
|
||||
using osu.Game.Rulesets.Taiko.Objects;
|
||||
using osu.Game.Rulesets.Taiko.Replays;
|
||||
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 =
|
||||
{
|
||||
// With respect to notation,
|
||||
// square brackets `[]` represent *closed* or *inclusive* bounds,
|
||||
// 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]
|
||||
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, -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, -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 },
|
||||
};
|
||||
|
||||
[TestCaseSource(nameof(test_cases))]
|
||||
public void TestHitWindowStability(float overallDifficulty, double hitOffset, HitResult expectedResult)
|
||||
{
|
||||
const double hit_time = 100;
|
||||
|
||||
var beatmap = new TaikoBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Hit
|
||||
{
|
||||
StartTime = hit_time,
|
||||
Type = HitType.Centre,
|
||||
}
|
||||
},
|
||||
Difficulty = new BeatmapDifficulty { OverallDifficulty = overallDifficulty },
|
||||
BeatmapInfo =
|
||||
{
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
},
|
||||
};
|
||||
|
||||
var replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new TaikoReplayFrame(0),
|
||||
new TaikoReplayFrame(hit_time + hitOffset, TaikoAction.LeftCentre),
|
||||
new TaikoReplayFrame(hit_time + hitOffset + 20),
|
||||
}
|
||||
};
|
||||
|
||||
RunTest(beatmap, replay, [expectedResult]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -15,26 +16,30 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
|
||||
int hits = HitObjects.Count(s => s is Hit);
|
||||
int drumRolls = HitObjects.Count(s => s is DrumRoll);
|
||||
int swells = HitObjects.Count(s => s is Swell);
|
||||
int sum = Math.Max(1, hits + drumRolls);
|
||||
|
||||
return new[]
|
||||
{
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Hit Count",
|
||||
Name = @"Hits",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles),
|
||||
Content = hits.ToString(),
|
||||
BarDisplayLength = hits / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Drumroll Count",
|
||||
Name = @"Drumrolls",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders),
|
||||
Content = drumRolls.ToString(),
|
||||
BarDisplayLength = drumRolls / (float)sum,
|
||||
},
|
||||
new BeatmapStatistic
|
||||
{
|
||||
Name = @"Swell Count",
|
||||
Name = @"Swells",
|
||||
CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners),
|
||||
Content = swells.ToString(),
|
||||
BarDisplayLength = Math.Min(swells / 10f, 1),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.BaseVelocity,
|
||||
HintText = EditorSetupStrings.BaseVelocityDescription,
|
||||
KeyboardStep = 0.1f,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderMultiplier)
|
||||
{
|
||||
Default = 1.4,
|
||||
@@ -74,6 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Setup
|
||||
{
|
||||
Caption = EditorSetupStrings.TickRate,
|
||||
HintText = EditorSetupStrings.TickRateDescription,
|
||||
KeyboardStep = 1,
|
||||
Current = new BindableDouble(Beatmap.Difficulty.SliderTickRate)
|
||||
{
|
||||
Default = 1,
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@@ -112,5 +113,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
.FadeOut(timingPoint.BeatLength * 0.75, Easing.OutSine);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableHitObject.IsNotNull())
|
||||
drawableHitObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
@@ -202,5 +203,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Default
|
||||
.Then()
|
||||
.FadeEdgeEffectTo(edge_alpha_kiai, duration, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (drawableHitObject.IsNotNull())
|
||||
drawableHitObject.ApplyCustomUpdateState -= updateStateTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
{
|
||||
internal partial class LegacyKiaiGlow : BeatSyncedContainer
|
||||
{
|
||||
private bool isKiaiActive;
|
||||
[Resolved]
|
||||
private HealthProcessor? healthProcessor { get; set; }
|
||||
|
||||
private bool isKiaiActive;
|
||||
private Sprite sprite = null!;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(ISkinSource skin, HealthProcessor? healthProcessor)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(ISkinSource skin)
|
||||
{
|
||||
Child = sprite = new Sprite
|
||||
{
|
||||
@@ -33,6 +35,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
Scale = new Vector2(TaikoLegacyHitTarget.SCALE),
|
||||
Colour = new Colour4(255, 228, 0, 255),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (healthProcessor != null)
|
||||
healthProcessor.NewJudgement += onNewJudgement;
|
||||
@@ -61,5 +68,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
||||
sprite.ScaleTo(TaikoLegacyHitTarget.SCALE + 0.15f).Then()
|
||||
.ScaleTo(TaikoLegacyHitTarget.SCALE, 80, Easing.OutQuad);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (healthProcessor != null)
|
||||
healthProcessor.NewJudgement -= onNewJudgement;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
<ProjectReference Include="..\osu.Game\osu.Game.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Bogus" Version="35.6.2" />
|
||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<ProjectReference Include="..\osu.Game.Rulesets.Taiko\osu.Game.Rulesets.Taiko.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="Bogus" Version="35.6.2" />
|
||||
<PackageReference Include="DeepEqual" Version="2.0.0" />
|
||||
<PackageReference Include="Moq" Version="4.17.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -135,6 +135,24 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoopFadeTransformIsIgnoredForLifetime()
|
||||
{
|
||||
var decoder = new LegacyStoryboardDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("noop-fade-transform-is-ignored-for-lifetime.osb"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3);
|
||||
Assert.AreEqual(2, background.Elements.Count);
|
||||
|
||||
Assert.AreEqual(1500, background.Elements[0].StartTime);
|
||||
Assert.AreEqual(1500, background.Elements[1].StartTime);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOutOfOrderStartTimes()
|
||||
{
|
||||
@@ -288,6 +306,29 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVideoWithCustomFadeIn()
|
||||
{
|
||||
var decoder = new LegacyStoryboardDecoder();
|
||||
|
||||
using var resStream = TestResources.OpenResource("video-custom-alpha-transform.osb");
|
||||
using var stream = new LineBufferedReader(resStream);
|
||||
|
||||
var storyboard = decoder.Decode(stream);
|
||||
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
Assert.That(storyboard.GetLayer(@"Video").Elements, Has.Count.EqualTo(1));
|
||||
Assert.That(storyboard.GetLayer(@"Video").Elements.Single(), Is.InstanceOf<StoryboardVideo>());
|
||||
Assert.That(storyboard.GetLayer(@"Video").Elements.Single().StartTime, Is.EqualTo(-5678));
|
||||
Assert.That(((StoryboardVideo)storyboard.GetLayer(@"Video").Elements.Single()).Commands.Alpha.Single().StartTime, Is.EqualTo(1500));
|
||||
Assert.That(((StoryboardVideo)storyboard.GetLayer(@"Video").Elements.Single()).Commands.Alpha.Single().EndTime, Is.EqualTo(1600));
|
||||
|
||||
Assert.That(storyboard.EarliestEventTime, Is.Null);
|
||||
Assert.That(storyboard.LatestEventTime, Is.Null);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVideoAndBackgroundEventsDoNotAffectStoryboardBounds()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Extensions;
|
||||
|
||||
namespace osu.Game.Tests.Extensions
|
||||
{
|
||||
[TestFixture]
|
||||
public class NumberFormattingExtensionsTest
|
||||
{
|
||||
[TestCase(-1, false, 0, ExpectedResult = "-1")]
|
||||
[TestCase(0, false, 0, ExpectedResult = "0")]
|
||||
[TestCase(1, false, 0, ExpectedResult = "1")]
|
||||
[TestCase(500, false, 10, ExpectedResult = "500")]
|
||||
[TestCase(-1, true, 0, ExpectedResult = "-1%")]
|
||||
[TestCase(0, true, 0, ExpectedResult = "0%")]
|
||||
[TestCase(1, true, 0, ExpectedResult = "1%")]
|
||||
[TestCase(50, true, 0, ExpectedResult = "50%")]
|
||||
public string TestInteger(int input, bool percent, int decimalDigits)
|
||||
{
|
||||
return input.ToStandardFormattedString(decimalDigits, percent);
|
||||
}
|
||||
|
||||
[TestCase(-1, false, 0, ExpectedResult = "-1")]
|
||||
[TestCase(-1e-6, false, 0, ExpectedResult = "0")]
|
||||
[TestCase(-1e-6, false, 6, ExpectedResult = "-0.000001")]
|
||||
[TestCase(0, false, 10, ExpectedResult = "0")]
|
||||
[TestCase(0, false, 0, ExpectedResult = "0")]
|
||||
[TestCase(double.NegativeZero, false, 0, ExpectedResult = "0")]
|
||||
[TestCase(1e-6, false, 0, ExpectedResult = "0")]
|
||||
[TestCase(1e-6, false, 6, ExpectedResult = "0.000001")]
|
||||
[TestCase(1, false, 0, ExpectedResult = "1")]
|
||||
[TestCase(1.528, false, 2, ExpectedResult = "1.53")]
|
||||
[TestCase(500, false, 10, ExpectedResult = "500")]
|
||||
[TestCase(-0.1, true, 0, ExpectedResult = "-10%")]
|
||||
[TestCase(0, true, 0, ExpectedResult = "0%")]
|
||||
[TestCase(0.4, true, 0, ExpectedResult = "40%")]
|
||||
[TestCase(0.48333, true, 2, ExpectedResult = "48%")]
|
||||
[TestCase(0.48333, true, 4, ExpectedResult = "48.33%")]
|
||||
[TestCase(1, true, 0, ExpectedResult = "100%")]
|
||||
public string TestDouble(double input, bool percent, int decimalDigits)
|
||||
{
|
||||
return input.ToStandardFormattedString(decimalDigits, percent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[SetCulture("fr-FR")]
|
||||
public void TestCultureInsensitivity()
|
||||
{
|
||||
Assert.That(0.4.ToStandardFormattedString(maxDecimalDigits: 2, asPercentage: true), Is.EqualTo("40%"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
// 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 Bogus;
|
||||
using MessagePack;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Tests.OnlinePlay
|
||||
{
|
||||
[TestFixture]
|
||||
public class MultiplayerPlaylistItemTest
|
||||
{
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Randomizer.Seed = new Random(1337);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloneMultiplayerPlaylistItem()
|
||||
{
|
||||
var faker = new Faker<MultiplayerPlaylistItem>()
|
||||
.StrictMode(true)
|
||||
.RuleFor(o => o.ID, f => f.Random.Long())
|
||||
.RuleFor(o => o.OwnerID, f => f.Random.Int())
|
||||
.RuleFor(o => o.BeatmapID, f => f.Random.Int())
|
||||
.RuleFor(o => o.BeatmapChecksum, f => f.Random.Hash())
|
||||
.RuleFor(o => o.RulesetID, f => f.Random.Int())
|
||||
.RuleFor(o => o.RequiredMods, f => f.Make(5, _ => new APIMod { Acronym = f.Random.String2(3) }))
|
||||
.RuleFor(o => o.AllowedMods, f => f.Make(5, _ => new APIMod { Acronym = f.Random.String2(3) }))
|
||||
.RuleFor(o => o.Expired, f => f.Random.Bool())
|
||||
.RuleFor(o => o.PlaylistOrder, f => f.Random.UShort())
|
||||
.RuleFor(o => o.PlayedAt, f => f.Date.RecentOffset())
|
||||
.RuleFor(o => o.StarRating, f => f.Random.Double())
|
||||
.RuleFor(o => o.Freestyle, f => f.Random.Bool());
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
MultiplayerPlaylistItem item = faker.Generate();
|
||||
Assert.That(MessagePackSerializer.SerializeToJson(item.Clone()), Is.EqualTo(MessagePackSerializer.SerializeToJson(item)));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestConstructFromAPIModel()
|
||||
{
|
||||
var faker = new Faker<MultiplayerPlaylistItem>()
|
||||
.StrictMode(true)
|
||||
.RuleFor(o => o.ID, f => f.Random.Long())
|
||||
.RuleFor(o => o.OwnerID, f => f.Random.Int())
|
||||
.RuleFor(o => o.BeatmapID, f => f.Random.Int())
|
||||
.RuleFor(o => o.BeatmapChecksum, f => f.Random.Hash())
|
||||
.RuleFor(o => o.RulesetID, f => f.Random.Int())
|
||||
.RuleFor(o => o.RequiredMods, f => f.Make(5, _ => new APIMod { Acronym = f.Random.String2(3) }))
|
||||
.RuleFor(o => o.AllowedMods, f => f.Make(5, _ => new APIMod { Acronym = f.Random.String2(3) }))
|
||||
.RuleFor(o => o.Expired, f => f.Random.Bool())
|
||||
.RuleFor(o => o.PlaylistOrder, f => f.Random.UShort())
|
||||
.RuleFor(o => o.PlayedAt, f => f.Date.RecentOffset())
|
||||
.RuleFor(o => o.StarRating, f => f.Random.Double())
|
||||
.RuleFor(o => o.Freestyle, f => f.Random.Bool());
|
||||
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
MultiplayerPlaylistItem initialItem = faker.Generate();
|
||||
MultiplayerPlaylistItem copiedItem = new MultiplayerPlaylistItem(new PlaylistItem(initialItem));
|
||||
Assert.That(MessagePackSerializer.SerializeToJson(copiedItem), Is.EqualTo(MessagePackSerializer.SerializeToJson(initialItem)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[Events]
|
||||
//Storyboard Layer 0 (Background)
|
||||
Sprite,Background,TopCentre,"img.jpg",320,240
|
||||
F,0,1000,1000,0,0 // should be ignored
|
||||
F,0,1500,1600,0,1
|
||||
Sprite,Background,TopCentre,"img.jpg",320,240
|
||||
F,0,1000,1000,0,0 // should be ignored
|
||||
F,0,1500,1600,1,1
|
||||
@@ -0,0 +1,5 @@
|
||||
osu file format v14
|
||||
|
||||
[Events]
|
||||
Video,-5678,"Video.avi",0,0
|
||||
F,0,1500,1600,0,1
|
||||
@@ -215,6 +215,35 @@ namespace osu.Game.Tests.Scores.IO
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScoreWithInvalidModCombinationsWillNotImport()
|
||||
{
|
||||
using (HeadlessGameHost host = new CleanRunHeadlessGameHost())
|
||||
{
|
||||
try
|
||||
{
|
||||
var osu = LoadOsuIntoHost(host, true);
|
||||
|
||||
var beatmap = BeatmapImportHelper.LoadOszIntoOsu(osu, TestResources.GetQuickTestBeatmapForImport()).GetResultSafely();
|
||||
|
||||
var toImport = new ScoreInfo
|
||||
{
|
||||
User = new APIUser { Username = "Test user" },
|
||||
BeatmapInfo = beatmap.Beatmaps.First(),
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
ClientVersion = "12345",
|
||||
Mods = new Mod[] { new OsuModHalfTime(), new OsuModDoubleTime() },
|
||||
};
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => LoadScoreIntoOsu(osu, toImport));
|
||||
}
|
||||
finally
|
||||
{
|
||||
host.Exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestImportStatistics()
|
||||
{
|
||||
|
||||
@@ -91,6 +91,6 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
}
|
||||
|
||||
private void assertCorrectIcon(bool favourited) => AddAssert("icon correct",
|
||||
() => this.ChildrenOfType<SpriteIcon>().Single().Icon.Equals(favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart));
|
||||
() => this.ChildrenOfType<SpriteIcon>().First().Icon.Equals(favourited ? FontAwesome.Solid.Heart : FontAwesome.Regular.Heart));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
{
|
||||
public partial class TestSceneBeatmapSetOnlineStatusPill : ThemeComparisonTestScene
|
||||
{
|
||||
private bool showUnknownStatus;
|
||||
|
||||
protected override Drawable CreateContent() => new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@@ -26,12 +28,20 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
Origin = Anchor.Centre,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
ChildrenEnumerable = Enum.GetValues(typeof(BeatmapOnlineStatus)).Cast<BeatmapOnlineStatus>().Select(status => new BeatmapSetOnlineStatusPill
|
||||
ChildrenEnumerable = Enum.GetValues(typeof(BeatmapOnlineStatus)).Cast<BeatmapOnlineStatus>().Select(status => new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Status = status
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 20,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
ShowUnknownStatus = showUnknownStatus,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Status = status
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
@@ -48,6 +58,12 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
pill.Width = 90;
|
||||
}));
|
||||
|
||||
AddStep("toggle show unknown", () =>
|
||||
{
|
||||
showUnknownStatus = !showUnknownStatus;
|
||||
CreateThemedContent(OverlayColourScheme.Red);
|
||||
});
|
||||
|
||||
AddStep("unset fixed width", () => statusPills.ForEach(pill => pill.AutoSizeAxes = Axes.Both));
|
||||
}
|
||||
|
||||
@@ -65,11 +81,6 @@ namespace osu.Game.Tests.Visual.Beatmaps
|
||||
pill.Status = BeatmapOnlineStatus.LocallyModified;
|
||||
break;
|
||||
|
||||
// skip none
|
||||
case BeatmapOnlineStatus.LocallyModified:
|
||||
pill.Status = BeatmapOnlineStatus.Graveyard;
|
||||
break;
|
||||
|
||||
default:
|
||||
pill.Status = (pill.Status + 1);
|
||||
break;
|
||||
|
||||
@@ -15,7 +15,7 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.SelectV2.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.Metadata;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
@@ -43,7 +43,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -66,7 +65,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -87,7 +85,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
AddStep("force transforms to finish", () => FinishTransforms(true));
|
||||
AddStep("right click second score", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<LeaderboardScoreV2>().ElementAt(1));
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<BeatmapLeaderboardScore>().ElementAt(1));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("use these mods not present",
|
||||
@@ -99,7 +97,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -114,7 +111,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
};
|
||||
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = room.RoomID!.Value });
|
||||
|
||||
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
|
||||
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||
@@ -128,7 +125,6 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = 1234,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -143,7 +139,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
};
|
||||
|
||||
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
|
||||
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = room.RoomID!.Value });
|
||||
|
||||
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
|
||||
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
|
||||
|
||||
@@ -44,17 +44,17 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
[Test]
|
||||
public void TestDailyChallenge()
|
||||
{
|
||||
startChallenge(1234);
|
||||
startChallenge();
|
||||
AddStep("push screen", () => LoadScreen(new DailyChallengeIntro(room)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPlayIntroOnceFlag()
|
||||
{
|
||||
startChallenge(1234);
|
||||
startChallenge();
|
||||
AddStep("set intro played flag", () => Dependencies.Get<SessionStatics>().SetValue(Static.DailyChallengeIntroPlayed, true));
|
||||
|
||||
startChallenge(1235);
|
||||
startChallenge();
|
||||
|
||||
AddAssert("intro played flag reset", () => Dependencies.Get<SessionStatics>().Get<bool>(Static.DailyChallengeIntroPlayed), () => Is.False);
|
||||
|
||||
@@ -62,13 +62,12 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
AddUntilStep("intro played flag set", () => Dependencies.Get<SessionStatics>().Get<bool>(Static.DailyChallengeIntroPlayed), () => Is.True);
|
||||
}
|
||||
|
||||
private void startChallenge(int roomId)
|
||||
private void startChallenge()
|
||||
{
|
||||
AddStep("add room", () =>
|
||||
{
|
||||
API.Perform(new CreateRoomRequest(room = new Room
|
||||
{
|
||||
RoomID = roomId,
|
||||
Name = "Daily Challenge: June 4, 2024",
|
||||
Playlist =
|
||||
[
|
||||
@@ -83,7 +82,7 @@ namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
Category = RoomCategory.DailyChallenge
|
||||
}));
|
||||
});
|
||||
AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = roomId }));
|
||||
AddStep("signal client", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo { RoomID = room.RoomID!.Value }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,12 +24,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
PoolableSkinnableSample[] loopingSamples = null;
|
||||
PoolableSkinnableSample[] onceOffSamples = null;
|
||||
|
||||
AddStep("get first slider", () =>
|
||||
{
|
||||
slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First();
|
||||
onceOffSamples = slider.ChildrenOfType<PoolableSkinnableSample>().Where(s => !s.Looping).ToArray();
|
||||
loopingSamples = slider.ChildrenOfType<PoolableSkinnableSample>().Where(s => s.Looping).ToArray();
|
||||
});
|
||||
AddStep("get first slider", () => slider = Editor.ChildrenOfType<DrawableSlider>().OrderBy(s => s.HitObject.StartTime).First());
|
||||
|
||||
AddStep("start playback", () => EditorClock.Start());
|
||||
|
||||
@@ -38,6 +33,9 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
if (!slider.Tracking.Value)
|
||||
return false;
|
||||
|
||||
onceOffSamples = slider.ChildrenOfType<PoolableSkinnableSample>().Where(s => !s.Looping).ToArray();
|
||||
loopingSamples = slider.ChildrenOfType<PoolableSkinnableSample>().Where(s => s.Looping).ToArray();
|
||||
|
||||
if (!loopingSamples.Any(s => s.Playing))
|
||||
return false;
|
||||
|
||||
|
||||
@@ -65,10 +65,10 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
InputManager.Keys(PlatformAction.Paste);
|
||||
});
|
||||
|
||||
assertArtistMetadata("Example Artist");
|
||||
assertArtistMetadata("Example ArtistExample Artist");
|
||||
|
||||
// It's important values are committed immediately on focus loss so the editor exit sequence detects them.
|
||||
AddAssert("value immediately changed on focus loss", () =>
|
||||
AddAssert("value still changed after focus loss", () =>
|
||||
{
|
||||
((IFocusManager)InputManager).TriggerFocusContention(metadataSection);
|
||||
return editorBeatmap.Metadata.Artist;
|
||||
@@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
InputManager.Keys(PlatformAction.Paste);
|
||||
});
|
||||
|
||||
assertArtistMetadata("Example Artist");
|
||||
assertArtistMetadata("Example ArtistExample Artist");
|
||||
|
||||
AddStep("commit", () => InputManager.Key(Key.Enter));
|
||||
|
||||
|
||||
@@ -50,21 +50,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("Set short reference score", () =>
|
||||
{
|
||||
// 50 events total. one of them (head circle) being timed / having hitwindows, rest having no hitwindows
|
||||
List<HitEvent> hitEvents =
|
||||
[
|
||||
// 10 events total. one of them (head circle) being timed / having hitwindows, rest having no hitwindows
|
||||
new HitEvent(30, 1, HitResult.LargeTickHit, new SliderHeadCircle { ClassicSliderBehaviour = true }, null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null),
|
||||
];
|
||||
|
||||
for (int i = 0; i < 49; i++)
|
||||
{
|
||||
hitEvents.Add(new HitEvent(0, 1, HitResult.LargeTickHit, new SliderTick(), null, null));
|
||||
}
|
||||
|
||||
foreach (var ev in hitEvents)
|
||||
ev.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Extensions.PolygonExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@@ -16,6 +16,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
@@ -23,7 +24,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[TestFixture]
|
||||
public partial class TestSceneGameplayLeaderboard : OsuTestScene
|
||||
{
|
||||
private TestGameplayLeaderboard leaderboard;
|
||||
private TestDrawableGameplayLeaderboard leaderboard = null!;
|
||||
|
||||
[Cached(typeof(IGameplayLeaderboardProvider))]
|
||||
private TestGameplayLeaderboardProvider leaderboardProvider = new TestGameplayLeaderboardProvider();
|
||||
|
||||
private readonly BindableLong playerScore = new BindableLong();
|
||||
|
||||
@@ -31,7 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("toggle expanded", () =>
|
||||
{
|
||||
if (leaderboard != null)
|
||||
if (leaderboard.IsNotNull())
|
||||
leaderboard.Expanded.Value = !leaderboard.Expanded.Value;
|
||||
});
|
||||
|
||||
@@ -57,10 +61,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
// has caused layout to not work in the past.
|
||||
|
||||
AddUntilStep("wait for fill flow layout",
|
||||
() => leaderboard.ChildrenOfType<FillFlowContainer<GameplayLeaderboardScore>>().First().ScreenSpaceDrawQuad.Intersects(leaderboard.ScreenSpaceDrawQuad));
|
||||
() => leaderboard.ChildrenOfType<FillFlowContainer<DrawableGameplayLeaderboardScore>>().First().ScreenSpaceDrawQuad.Intersects(leaderboard.ScreenSpaceDrawQuad));
|
||||
|
||||
AddUntilStep("wait for some scores not masked away",
|
||||
() => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre)));
|
||||
() => leaderboard.ChildrenOfType<DrawableGameplayLeaderboardScore>().Any(s => leaderboard.ScreenSpaceDrawQuad.Contains(s.ScreenSpaceDrawQuad.Centre)));
|
||||
|
||||
AddUntilStep("wait for tracked score fully visible", () => leaderboard.ScreenSpaceDrawQuad.Intersects(leaderboard.TrackedScore!.ScreenSpaceDrawQuad));
|
||||
|
||||
@@ -139,7 +143,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
checkHeight(8);
|
||||
|
||||
void checkHeight(int panelCount)
|
||||
=> AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (GameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
|
||||
=> AddAssert($"leaderboard height is {panelCount} panels high", () => leaderboard.DrawHeight == (DrawableGameplayLeaderboardScore.PANEL_HEIGHT + leaderboard.Spacing) * panelCount);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -179,6 +183,30 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
() => Does.Contain("#FF549A"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTrackedScorePosition([Values] bool partial)
|
||||
{
|
||||
createLeaderboard(partial);
|
||||
|
||||
AddStep("add many scores in one go", () =>
|
||||
{
|
||||
for (int i = 0; i < 49; i++)
|
||||
createRandomScore(new APIUser { Username = $"Player {i + 1}" });
|
||||
|
||||
// Add player at end to force an animation down the whole list.
|
||||
playerScore.Value = 0;
|
||||
createLeaderboardScore(playerScore, new APIUser { Username = "You", Id = 3 }, true);
|
||||
});
|
||||
|
||||
if (partial)
|
||||
AddUntilStep("tracked player has null position", () => leaderboard.TrackedScore?.ScorePosition, () => Is.Null);
|
||||
else
|
||||
AddUntilStep("tracked player is #50", () => leaderboard.TrackedScore?.ScorePosition, () => Is.EqualTo(50));
|
||||
|
||||
AddStep("move tracked player to top", () => leaderboard.TrackedScore!.TotalScore.Value = 8_000_000);
|
||||
AddUntilStep("all players have non-null position", () => leaderboard.AllScores.Select(s => s.ScorePosition), () => Does.Not.Contain(null));
|
||||
}
|
||||
|
||||
private void addLocalPlayer()
|
||||
{
|
||||
AddStep("add local player", () =>
|
||||
@@ -188,11 +216,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
}
|
||||
|
||||
private void createLeaderboard()
|
||||
private void createLeaderboard(bool partial = false)
|
||||
{
|
||||
AddStep("create leaderboard", () =>
|
||||
{
|
||||
Child = leaderboard = new TestGameplayLeaderboard
|
||||
leaderboardProvider.Scores.Clear();
|
||||
leaderboardProvider.IsPartial = partial;
|
||||
Child = leaderboard = new TestDrawableGameplayLeaderboard
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -205,11 +235,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
private void createLeaderboardScore(BindableLong score, APIUser user, bool isTracked = false)
|
||||
{
|
||||
var leaderboardScore = leaderboard.Add(user, isTracked);
|
||||
leaderboardScore.TotalScore.BindTo(score);
|
||||
var leaderboardScore = new GameplayLeaderboardScore(user, isTracked, score);
|
||||
leaderboardProvider.Scores.Add(leaderboardScore);
|
||||
}
|
||||
|
||||
private partial class TestGameplayLeaderboard : GameplayLeaderboard
|
||||
private partial class TestDrawableGameplayLeaderboard : DrawableGameplayLeaderboard
|
||||
{
|
||||
public float Spacing => Flow.Spacing.Y;
|
||||
|
||||
@@ -220,8 +250,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
return scoreItem != null && scoreItem.ScorePosition == expectedPosition;
|
||||
}
|
||||
|
||||
public IEnumerable<GameplayLeaderboardScore> GetAllScoresForUsername(string username)
|
||||
public IEnumerable<DrawableGameplayLeaderboardScore> GetAllScoresForUsername(string username)
|
||||
=> Flow.Where(i => i.User?.Username == username);
|
||||
|
||||
public IEnumerable<DrawableGameplayLeaderboardScore> AllScores => Flow;
|
||||
}
|
||||
|
||||
private class TestGameplayLeaderboardProvider : IGameplayLeaderboardProvider
|
||||
{
|
||||
IBindableList<GameplayLeaderboardScore> IGameplayLeaderboardProvider.Scores => Scores;
|
||||
public BindableList<GameplayLeaderboardScore> Scores { get; } = new BindableList<GameplayLeaderboardScore>();
|
||||
public bool IsPartial { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
@@ -458,6 +459,62 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyDefaultComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAnchorRadioButtonBehavior()
|
||||
{
|
||||
ISerialisableDrawable? selectedComponent = null;
|
||||
|
||||
AddStep("Select first component", () =>
|
||||
{
|
||||
var blueprint = skinEditor.ChildrenOfType<SkinBlueprint>().First();
|
||||
skinEditor.SelectedComponents.Clear();
|
||||
skinEditor.SelectedComponents.Add(blueprint.Item);
|
||||
selectedComponent = blueprint.Item;
|
||||
});
|
||||
|
||||
AddStep("Right-click to open context menu", () =>
|
||||
{
|
||||
if (selectedComponent != null)
|
||||
InputManager.MoveMouseTo(((Drawable)selectedComponent).ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddStep("Click on Anchor menu", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("Anchor"));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("Right-click TopLeft anchor", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("TopLeft"));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddAssert("TopLeft item checked", () => (getMenuItemByText("TopLeft").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
|
||||
|
||||
AddStep("Right-click Centre anchor", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("Centre"));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddAssert("Centre item checked", () => (getMenuItemByText("Centre").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
|
||||
AddAssert("TopLeft item unchecked", () => (getMenuItemByText("TopLeft").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.False);
|
||||
|
||||
AddStep("Right-click Closest anchor", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getMenuItemByText("Closest"));
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
|
||||
AddAssert("Closest item checked", () => (getMenuItemByText("Closest").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.True);
|
||||
AddAssert("Centre item unchecked", () => (getMenuItemByText("Centre").Item as TernaryStateRadioMenuItem)?.State.Value == TernaryState.False);
|
||||
|
||||
Menu.DrawableMenuItem getMenuItemByText(string text)
|
||||
=> this.ChildrenOfType<Menu.DrawableMenuItem>().First(m => m.Item.Text.ToString() == text);
|
||||
}
|
||||
|
||||
private Skin importSkinFromArchives(string filename)
|
||||
{
|
||||
var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Gameplay;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneSoloGameplayLeaderboard : OsuTestScene
|
||||
{
|
||||
[Cached(typeof(ScoreProcessor))]
|
||||
private readonly ScoreProcessor scoreProcessor = TestGameplayState.Create(new OsuRuleset()).ScoreProcessor;
|
||||
|
||||
private readonly BindableList<ScoreInfo> scores = new BindableList<ScoreInfo>();
|
||||
|
||||
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
|
||||
private readonly Bindable<PlayBeatmapDetailArea.TabType> beatmapTabType = new Bindable<PlayBeatmapDetailArea.TabType>();
|
||||
|
||||
private SoloGameplayLeaderboard leaderboard = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
|
||||
config.BindWith(OsuSetting.BeatmapDetailTab, beatmapTabType);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("clear scores", () => scores.Clear());
|
||||
|
||||
AddStep("create component", () =>
|
||||
{
|
||||
var trackingUser = new APIUser
|
||||
{
|
||||
Username = "local user",
|
||||
Id = 2,
|
||||
};
|
||||
|
||||
Child = leaderboard = new SoloGameplayLeaderboard(trackingUser)
|
||||
{
|
||||
Scores = { BindTarget = scores },
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AlwaysVisible = { Value = false },
|
||||
Expanded = { Value = true },
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("add scores", () => scores.AddRange(createSampleScores()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalUser()
|
||||
{
|
||||
AddSliderStep("score", 0, 1000000, 500000, v => scoreProcessor.TotalScore.Value = v);
|
||||
AddSliderStep("accuracy", 0f, 1f, 0.5f, v => scoreProcessor.Accuracy.Value = v);
|
||||
AddSliderStep("combo", 0, 10000, 0, v => scoreProcessor.HighestCombo.Value = v);
|
||||
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
|
||||
}
|
||||
|
||||
[TestCase(PlayBeatmapDetailArea.TabType.Local, 51)]
|
||||
[TestCase(PlayBeatmapDetailArea.TabType.Global, null)]
|
||||
[TestCase(PlayBeatmapDetailArea.TabType.Country, null)]
|
||||
[TestCase(PlayBeatmapDetailArea.TabType.Friends, null)]
|
||||
public void TestTrackedScorePosition(PlayBeatmapDetailArea.TabType tabType, int? expectedOverflowIndex)
|
||||
{
|
||||
AddStep($"change TabType to {tabType}", () => beatmapTabType.Value = tabType);
|
||||
AddUntilStep("tracked player is #50", () => leaderboard.TrackedScore?.ScorePosition, () => Is.EqualTo(50));
|
||||
|
||||
AddStep("add one more score", () => scores.Add(new ScoreInfo { User = new APIUser { Username = "New player 1" }, TotalScore = RNG.Next(600000, 1000000) }));
|
||||
|
||||
AddUntilStep("wait for sort", () => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().First().ScorePosition != null);
|
||||
|
||||
if (expectedOverflowIndex == null)
|
||||
AddUntilStep("tracked player has null position", () => leaderboard.TrackedScore?.ScorePosition, () => Is.Null);
|
||||
else
|
||||
AddUntilStep($"tracked player is #{expectedOverflowIndex}", () => leaderboard.TrackedScore?.ScorePosition, () => Is.EqualTo(expectedOverflowIndex));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVisibility()
|
||||
{
|
||||
AddStep("set config visible true", () => configVisibility.Value = true);
|
||||
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
|
||||
|
||||
AddStep("set config visible false", () => configVisibility.Value = false);
|
||||
AddUntilStep("leaderboard not visible", () => leaderboard.Alpha == 0);
|
||||
|
||||
AddStep("set always visible", () => leaderboard.AlwaysVisible.Value = true);
|
||||
AddUntilStep("leaderboard visible", () => leaderboard.Alpha == 1);
|
||||
|
||||
AddStep("set config visible true", () => configVisibility.Value = true);
|
||||
AddAssert("leaderboard still visible", () => leaderboard.Alpha == 1);
|
||||
}
|
||||
|
||||
private static List<ScoreInfo> createSampleScores()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new ScoreInfo { User = new APIUser { Username = @"peppy" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||
new ScoreInfo { User = new APIUser { Username = @"smoogipoo" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||
new ScoreInfo { User = new APIUser { Username = @"spaceman_atlas" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||
new ScoreInfo { User = new APIUser { Username = @"frenzibyte" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||
new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) },
|
||||
}.Concat(Enumerable.Range(0, 44).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,14 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -48,7 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestVideoSize()
|
||||
public void TestVideo()
|
||||
{
|
||||
AddStep("load storyboard with only video", () =>
|
||||
{
|
||||
@@ -56,6 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
loadStoryboard("storyboard_only_video.osu", s => s.Beatmap.WidescreenStoryboard = false);
|
||||
});
|
||||
|
||||
AddAssert("storyboard video present in hierarchy", () => this.ChildrenOfType<DrawableStoryboardVideo>().Any());
|
||||
AddAssert("storyboard is correct width", () => Precision.AlmostEquals(storyboard?.Width ?? 0f, 480 * 16 / 9f));
|
||||
}
|
||||
|
||||
|
||||
@@ -185,8 +185,12 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddUntilStep("track changed", () => trackChangeQueue.Count == 1);
|
||||
|
||||
AddStep("press previous", () => globalActionContainer.TriggerPressed(GlobalAction.MusicPrev));
|
||||
AddUntilStep("track changed", () =>
|
||||
AddUntilStep("new track selected", () =>
|
||||
trackChangeQueue.Count == 2 && !trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo));
|
||||
|
||||
AddStep("press next", () => globalActionContainer.TriggerPressed(GlobalAction.MusicNext));
|
||||
AddUntilStep("first track selected",
|
||||
() => trackChangeQueue.Count == 3 && trackChangeQueue.First().working.BeatmapInfo.Equals(trackChangeQueue.Last().working.BeatmapInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Moq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
@@ -20,6 +21,7 @@ using osu.Game.Online.Spectator;
|
||||
using osu.Game.Replays.Legacy;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@@ -29,11 +31,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
protected readonly BindableList<MultiplayerRoomUser> MultiplayerUsers = new BindableList<MultiplayerRoomUser>();
|
||||
|
||||
protected MultiplayerGameplayLeaderboard? Leaderboard { get; private set; }
|
||||
protected MultiplayerLeaderboardProvider? LeaderboardProvider { get; private set; }
|
||||
|
||||
protected DrawableGameplayLeaderboard? Leaderboard { get; private set; }
|
||||
|
||||
protected virtual MultiplayerRoomUser CreateUser(int userId) => new MultiplayerRoomUser(userId);
|
||||
|
||||
protected abstract MultiplayerGameplayLeaderboard CreateLeaderboard();
|
||||
protected abstract MultiplayerLeaderboardProvider CreateLeaderboardProvider();
|
||||
|
||||
private readonly BindableList<int> multiplayerUserIds = new BindableList<int>();
|
||||
private readonly BindableDictionary<int, SpectatorState> watchedUserStates = new BindableDictionary<int, SpectatorState>();
|
||||
@@ -124,19 +128,38 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create leaderboard", () =>
|
||||
{
|
||||
Leaderboard?.Expire();
|
||||
Clear(true);
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||
|
||||
LoadComponentAsync(Leaderboard = CreateLeaderboard(), Add);
|
||||
LoadComponentAsync(LeaderboardProvider = CreateLeaderboardProvider(), Add);
|
||||
Add(new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = [(typeof(IGameplayLeaderboardProvider), LeaderboardProvider)],
|
||||
Child = Leaderboard = new DrawableGameplayLeaderboard
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => Leaderboard!.IsLoaded);
|
||||
|
||||
AddStep("check watch requests were sent", () =>
|
||||
AddUntilStep("check watch requests were sent", () =>
|
||||
{
|
||||
foreach (var user in MultiplayerUsers)
|
||||
spectatorClient.Verify(s => s.WatchUser(user.UserID), Times.Once);
|
||||
try
|
||||
{
|
||||
foreach (var user in MultiplayerUsers)
|
||||
spectatorClient.Verify(s => s.WatchUser(user.UserID), Times.Once);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (MockException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -159,10 +182,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return false;
|
||||
});
|
||||
|
||||
AddStep("check stop watching requests were sent", () =>
|
||||
AddUntilStep("check stop watching requests were sent", () =>
|
||||
{
|
||||
foreach (var user in MultiplayerUsers)
|
||||
spectatorClient.Verify(s => s.StopWatchingUser(user.UserID), Times.Once);
|
||||
try
|
||||
{
|
||||
foreach (var user in MultiplayerUsers)
|
||||
spectatorClient.Verify(s => s.StopWatchingUser(user.UserID), Times.Once);
|
||||
return true;
|
||||
}
|
||||
catch (MockException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -204,12 +235,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
header.Combo++;
|
||||
header.MaxCombo = Math.Max(header.MaxCombo, header.Combo);
|
||||
header.Statistics[HitResult.Meh]++;
|
||||
header.TotalScore += 50;
|
||||
break;
|
||||
|
||||
default:
|
||||
header.Combo++;
|
||||
header.MaxCombo = Math.Max(header.MaxCombo, header.Combo);
|
||||
header.Statistics[HitResult.Great]++;
|
||||
header.TotalScore += 300;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -218,3 +251,4 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||
});
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
|
||||
OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0);
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
@@ -30,7 +31,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||
|
||||
private DrawableLoungeRoom drawableRoom = null!;
|
||||
private LoungeRoomPanel panel = null!;
|
||||
private SearchTextBox searchTextBox = null!;
|
||||
|
||||
private readonly ManualResetEventSlim allowResponseCallback = new ManualResetEventSlim();
|
||||
@@ -40,14 +41,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
var mockLounge = new Mock<IOnlinePlayLounge>();
|
||||
mockLounge
|
||||
.Setup(l => l.Join(It.IsAny<Room>(), It.IsAny<string>(), It.IsAny<Action<Room>>(), It.IsAny<Action<string>>()))
|
||||
.Callback<Room, string, Action<Room>, Action<string>>((_, _, _, d) =>
|
||||
.Setup(l => l.Join(It.IsAny<Room>(), It.IsAny<string>(), It.IsAny<Action<Room>>(), It.IsAny<Action<string, Exception?>>()))
|
||||
.Callback<Room, string, Action<Room>, Action<string, Exception?>>((_, _, _, d) =>
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
allowResponseCallback.Wait(10000);
|
||||
allowResponseCallback.Reset();
|
||||
Schedule(() => d?.Invoke("Incorrect password"));
|
||||
Schedule(() => d?.Invoke("Incorrect password", new InvalidPasswordException()));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -73,7 +74,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Width = 500,
|
||||
Depth = float.MaxValue
|
||||
},
|
||||
drawableRoom = new DrawableLoungeRoom(room)
|
||||
panel = new LoungeRoomPanel(room)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -87,16 +88,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestFocusViaKeyboardCommit()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? popover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? popover = null;
|
||||
|
||||
AddAssert("search textbox has focus", () => checkFocus(searchTextBox));
|
||||
AddStep("click room twice", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(drawableRoom);
|
||||
InputManager.MoveMouseTo(panel);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
|
||||
AddAssert("textbox has focus", () => checkFocus(popover.ChildrenOfType<OsuPasswordTextBox>().Single()));
|
||||
|
||||
@@ -122,16 +123,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestFocusViaMouseCommit()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? popover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? popover = null;
|
||||
|
||||
AddAssert("search textbox has focus", () => checkFocus(searchTextBox));
|
||||
AddStep("click room twice", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(drawableRoom);
|
||||
InputManager.MoveMouseTo(panel);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
AddUntilStep("wait for popover", () => (popover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().SingleOrDefault()) != null);
|
||||
|
||||
AddAssert("textbox has focus", () => checkFocus(popover.ChildrenOfType<OsuPasswordTextBox>().Single()));
|
||||
|
||||
|
||||
@@ -105,6 +105,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("no item selected", () => playlist.SelectedItem.Value == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMarkCompleted()
|
||||
{
|
||||
createPlaylist();
|
||||
AddStep("mark some items as complete", () =>
|
||||
{
|
||||
playlist.Items[0].MarkCompleted();
|
||||
playlist.Items[2].MarkCompleted();
|
||||
playlist.Items[3].MarkCompleted();
|
||||
playlist.Items[5].MarkCompleted();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSelectable()
|
||||
{
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
|
||||
|
||||
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
|
||||
AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.Beatmap.OnlineID == otherBeatmap.OnlineID);
|
||||
AddUntilStep("selected item is new beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == otherBeatmap.OnlineID);
|
||||
}
|
||||
|
||||
private void addItem(Func<BeatmapInfo> beatmap)
|
||||
|
||||
@@ -9,15 +9,16 @@ using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiSpectatorLeaderboard : MultiplayerTestScene
|
||||
{
|
||||
private Dictionary<int, ManualClock> clocks = null!;
|
||||
private MultiSpectatorLeaderboard? leaderboard;
|
||||
private MultiSpectatorLeaderboardProvider? leaderboardProvider;
|
||||
private DrawableGameplayLeaderboard leaderboard = null!;
|
||||
|
||||
[SetUpSteps]
|
||||
public override void SetUpSteps()
|
||||
@@ -29,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("reset", () =>
|
||||
{
|
||||
leaderboard?.RemoveAndDisposeImmediately();
|
||||
Clear(true);
|
||||
|
||||
clocks = new Dictionary<int, ManualClock>
|
||||
{
|
||||
@@ -48,21 +49,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray())
|
||||
LoadComponentAsync(leaderboardProvider = new MultiSpectatorLeaderboardProvider(clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray()), Add);
|
||||
Add(new DependencyProvidingContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Expanded = { Value = true }
|
||||
}, Add);
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = [(typeof(IGameplayLeaderboardProvider), leaderboardProvider)],
|
||||
Child = leaderboard = new DrawableGameplayLeaderboard
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Expanded = { Value = true }
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for load", () => leaderboard!.IsLoaded);
|
||||
AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().Count() == 2);
|
||||
AddUntilStep("wait for load", () => leaderboard.IsLoaded);
|
||||
AddUntilStep("wait for user population", () => leaderboard.ChildrenOfType<DrawableGameplayLeaderboardScore>().Count() == 2);
|
||||
|
||||
AddStep("add clock sources", () =>
|
||||
{
|
||||
foreach ((int userId, var clock) in clocks)
|
||||
leaderboard!.AddClock(userId, clock);
|
||||
leaderboardProvider!.AddClock(userId, clock);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -123,6 +130,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
=> AddStep($"set user {userId} time {time}", () => clocks[userId].CurrentTime = time);
|
||||
|
||||
private void assertCombo(int userId, int expectedCombo)
|
||||
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId).Combo.Value == expectedCombo);
|
||||
=> AddUntilStep($"player {userId} has {expectedCombo} combo", () => this.ChildrenOfType<DrawableGameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId).Combo.Value == expectedCombo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,7 +560,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private PlayerArea getInstance(int userId) => spectatorScreen.ChildrenOfType<PlayerArea>().Single(p => p.UserId == userId);
|
||||
|
||||
private GameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<GameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId);
|
||||
private DrawableGameplayLeaderboardScore getLeaderboardScore(int userId) => spectatorScreen.ChildrenOfType<DrawableGameplayLeaderboardScore>().Single(s => s.User?.OnlineID == userId);
|
||||
|
||||
private int[] getPlayerIds(int count) => Enumerable.Range(PLAYER_1_ID, count).ToArray();
|
||||
}
|
||||
|
||||
@@ -81,6 +81,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("import beatmap", () =>
|
||||
{
|
||||
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||
});
|
||||
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
|
||||
});
|
||||
|
||||
@@ -269,7 +274,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room and immediately exit select", () =>
|
||||
@@ -298,7 +303,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
@@ -349,13 +354,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||
|
||||
@@ -438,7 +443,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("Enter song select", () =>
|
||||
{
|
||||
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
@@ -479,7 +484,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("Enter song select", () =>
|
||||
{
|
||||
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
@@ -520,7 +525,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("Enter song select", () =>
|
||||
{
|
||||
var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen;
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item);
|
||||
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
|
||||
@@ -652,7 +657,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
|
||||
|
||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<RoomSubScreen>().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden);
|
||||
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
|
||||
|
||||
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);
|
||||
|
||||
@@ -802,7 +807,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => this.ChildrenOfType<LoungeSubScreen>().Single().UpdateFilter());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<DrawableRoom>().Any());
|
||||
AddUntilStep("wait for room", () => this.ChildrenOfType<RoomPanel>().Any());
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
|
||||
AddStep("disable polling", () => this.ChildrenOfType<LoungeListingPoller>().Single().TimeBetweenPolls.Value = 0);
|
||||
@@ -823,11 +828,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddUntilStep("wait for join", () => multiplayerClient.RoomJoined);
|
||||
|
||||
AddAssert("local room has correct settings", () =>
|
||||
{
|
||||
var localRoom = this.ChildrenOfType<MultiplayerMatchSubScreen>().Single().Room;
|
||||
return localRoom.Name == multiplayerClient.ServerSideRooms[0].Name && localRoom.Playlist.Single().ID == 2;
|
||||
});
|
||||
AddAssert("local room has correct name", () => this.ChildrenOfType<MultiplayerRoomPanel>().Single().Room.Name, () => Is.EqualTo(multiplayerClient.ServerSideRooms[0].Name));
|
||||
AddAssert("local room has correct playlist", () => this.ChildrenOfType<MultiplayerQueueList>().Single().Items.Single().ID, () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -1054,6 +1056,45 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("hidden is selected", () => SelectedMods.Value, () => Has.One.TypeOf(typeof(OsuModHidden)));
|
||||
}
|
||||
|
||||
[FlakyTest]
|
||||
[Test]
|
||||
public void TestGlobalBeatmapDoesNotChangeAtResults()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = "Test Room",
|
||||
QueueMode = QueueMode.AllPlayers,
|
||||
Playlist =
|
||||
[
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
AllowedMods = new[] { new APIMod { Acronym = "HD" } },
|
||||
},
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 1)).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new TaikoRuleset().RulesetInfo.OnlineID,
|
||||
AllowedMods = new[] { new APIMod { Acronym = "HD" } },
|
||||
},
|
||||
]
|
||||
});
|
||||
|
||||
enterGameplay();
|
||||
|
||||
// Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out.
|
||||
for (double i = 1000; i < TestResources.QUICK_BEATMAP_LENGTH; i += 1000)
|
||||
{
|
||||
double time = i;
|
||||
AddUntilStep($"wait for time > {i}", () => this.ChildrenOfType<GameplayClockContainer>().SingleOrDefault()?.CurrentTime > time);
|
||||
}
|
||||
|
||||
AddUntilStep("wait for results", () => multiplayerComponents.CurrentScreen is ResultsScreen);
|
||||
|
||||
AddAssert("global beatmap still matches first playlist item", () => Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(multiplayerClient.ClientRoom!.Playlist[0].BeatmapID));
|
||||
AddStep("return to match", () => multiplayerComponents.Exit());
|
||||
AddAssert("global beatmap matches second playlist item", () => Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(multiplayerClient.ClientRoom!.Playlist[1].BeatmapID));
|
||||
}
|
||||
|
||||
private void enterGameplay()
|
||||
{
|
||||
pressReadyButton();
|
||||
|
||||
@@ -9,7 +9,7 @@ using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@@ -25,27 +25,25 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return user;
|
||||
}
|
||||
|
||||
protected override MultiplayerGameplayLeaderboard CreateLeaderboard()
|
||||
{
|
||||
return new TestLeaderboard(MultiplayerUsers.ToArray())
|
||||
protected override MultiplayerLeaderboardProvider CreateLeaderboardProvider() =>
|
||||
new TestLeaderboard(MultiplayerUsers.ToArray())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPerUserMods()
|
||||
{
|
||||
AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)Leaderboard!).UserMods[0], Is.Empty));
|
||||
AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)LeaderboardProvider!).UserMods[0], Is.Empty));
|
||||
AddStep("last user has NF mod", () =>
|
||||
{
|
||||
Assert.That(((TestLeaderboard)Leaderboard!).UserMods[TOTAL_USERS - 1], Has.One.Items);
|
||||
Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1].Single(), Is.TypeOf<OsuModNoFail>());
|
||||
Assert.That(((TestLeaderboard)LeaderboardProvider!).UserMods[TOTAL_USERS - 1], Has.One.Items);
|
||||
Assert.That(((TestLeaderboard)LeaderboardProvider).UserMods[TOTAL_USERS - 1].Single(), Is.TypeOf<OsuModNoFail>());
|
||||
});
|
||||
}
|
||||
|
||||
private partial class TestLeaderboard : MultiplayerGameplayLeaderboard
|
||||
private partial class TestLeaderboard : MultiplayerLeaderboardProvider
|
||||
{
|
||||
public Dictionary<int, IReadOnlyList<Mod>> UserMods => UserScores.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ScoreProcessor.Mods);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Select.Leaderboards;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
@@ -24,8 +25,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
return user;
|
||||
}
|
||||
|
||||
protected override MultiplayerGameplayLeaderboard CreateLeaderboard() =>
|
||||
new MultiplayerGameplayLeaderboard(MultiplayerUsers.ToArray())
|
||||
protected override MultiplayerLeaderboardProvider CreateLeaderboardProvider() =>
|
||||
new MultiplayerLeaderboardProvider(MultiplayerUsers.ToArray())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -39,17 +40,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
LoadComponentAsync(new MatchScoreDisplay
|
||||
{
|
||||
Team1Score = { BindTarget = Leaderboard!.TeamScores[0] },
|
||||
Team2Score = { BindTarget = Leaderboard.TeamScores[1] }
|
||||
Team1Score = { BindTarget = LeaderboardProvider!.TeamScores[0] },
|
||||
Team2Score = { BindTarget = LeaderboardProvider.TeamScores[1] }
|
||||
}, Add);
|
||||
|
||||
LoadComponentAsync(new GameplayMatchScoreDisplay
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Team1Score = { BindTarget = Leaderboard.TeamScores[0] },
|
||||
Team2Score = { BindTarget = Leaderboard.TeamScores[1] },
|
||||
Expanded = { BindTarget = Leaderboard.Expanded },
|
||||
Team1Score = { BindTarget = LeaderboardProvider.TeamScores[0] },
|
||||
Team2Score = { BindTarget = LeaderboardProvider.TeamScores[1] },
|
||||
Expanded = { BindTarget = Leaderboard!.Expanded },
|
||||
}, Add);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox);
|
||||
|
||||
AddStep("hit escape", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
|
||||
}
|
||||
@@ -65,9 +65,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
AddStep("exit screen", () => Stack.Exit());
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().Any());
|
||||
|
||||
AddAssert("room not joined", () => !MultiplayerClient.RoomJoined);
|
||||
}
|
||||
@@ -75,12 +75,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithIncorrectPasswordViaButton()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||
|
||||
@@ -94,12 +94,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithIncorrectPasswordViaEnter()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "wrong");
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
@@ -113,12 +113,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithCorrectPassword()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().TriggerClick());
|
||||
|
||||
@@ -128,12 +128,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestJoinRoomWithPasswordViaKeyboardOnly()
|
||||
{
|
||||
DrawableLoungeRoom.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
LoungeRoomPanel.PasswordEntryPopover? passwordEntryPopover = null;
|
||||
|
||||
createRooms(GenerateRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<LoungeRoomPanel.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
@@ -185,7 +187,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("mod select contents loaded",
|
||||
() => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
|
||||
AddUntilStep("mod select contains only double time mod",
|
||||
() => this.ChildrenOfType<RoomSubScreen>().Single().UserModsSelectOverlay
|
||||
() => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single()
|
||||
.ChildrenOfType<ModPanel>()
|
||||
.SingleOrDefault(panel => panel.Visible)?.Mod is OsuModDoubleTime);
|
||||
}
|
||||
@@ -211,7 +213,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("press toggle mod select key", () => InputManager.Key(Key.F1));
|
||||
|
||||
AddUntilStep("mod select shown", () => this.ChildrenOfType<RoomSubScreen>().Single().UserModsSelectOverlay.State.Value == Visibility.Visible);
|
||||
AddUntilStep("mod select shown", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -234,7 +236,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("press toggle mod select key", () => InputManager.Key(Key.F1));
|
||||
|
||||
AddWaitStep("wait some", 3);
|
||||
AddAssert("mod select not shown", () => this.ChildrenOfType<RoomSubScreen>().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden);
|
||||
AddAssert("mod select not shown", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -306,10 +308,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("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", () => screen.UserModsSelectOverlay.ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
|
||||
AddStep("select flashlight", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().ChildrenOfType<ModPanel>().Single(m => m.Mod is ModFlashlight).TriggerClick());
|
||||
AddAssert("score multiplier = 1.35", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.35).Within(0.01));
|
||||
|
||||
AddStep("change flashlight setting", () => ((OsuModFlashlight)screen.UserModsSelectOverlay.SelectedMods.Value.Single()).FollowDelay.Value = 1200);
|
||||
AddStep("change flashlight setting", () => ((OsuModFlashlight)this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().SelectedMods.Value.Single()).FollowDelay.Value = 1200);
|
||||
AddAssert("score multiplier = 1.20", () => this.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
|
||||
}
|
||||
|
||||
@@ -330,10 +332,98 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddUntilStep("wait for join", () => RoomJoined);
|
||||
|
||||
AddUntilStep("button visible", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.GreaterThan(0));
|
||||
AddUntilStep("button visible", () => this.ChildrenOfType<MultiplayerRoomPanel>().Single().ChangeSettingsButton.Alpha, () => Is.GreaterThan(0));
|
||||
AddStep("join other user", void () => MultiplayerClient.AddUser(new APIUser { Id = PLAYER_1_ID }));
|
||||
AddStep("make other user host", () => MultiplayerClient.TransferHost(PLAYER_1_ID));
|
||||
AddAssert("button hidden", () => this.ChildrenOfType<DrawableMatchRoom>().Single().ChangeSettingsButton?.Alpha, () => Is.EqualTo(0));
|
||||
AddAssert("button hidden", () => this.ChildrenOfType<MultiplayerRoomPanel>().Single().ChangeSettingsButton.Alpha, () => Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserModSelectUpdatesWhenNotVisible()
|
||||
{
|
||||
AddStep("add playlist item", () =>
|
||||
{
|
||||
room.Playlist =
|
||||
[
|
||||
new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
|
||||
AllowedMods = [new APIMod(new OsuModFlashlight())]
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
|
||||
AddUntilStep("wait for join", () => RoomJoined);
|
||||
|
||||
// 1. Open the mod select overlay and enable flashlight
|
||||
|
||||
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||
AddUntilStep("mod select contents loaded", () => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
|
||||
AddStep("click flashlight panel", () =>
|
||||
{
|
||||
ModPanel panel = this.ChildrenOfType<ModPanel>().Single(p => p.Mod is OsuModFlashlight);
|
||||
InputManager.MoveMouseTo(panel);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddUntilStep("flashlight mod enabled", () => MultiplayerClient.ClientRoom!.Users[0].Mods.Any());
|
||||
|
||||
// 2. Close the mod select overlay, edit the playlist to disable allowed mods, and then edit it again to re-enable allowed mods.
|
||||
|
||||
AddStep("close mod select overlay", () => this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().Hide());
|
||||
AddUntilStep("mod select overlay not present", () => !this.ChildrenOfType<MultiplayerUserModSelectOverlay>().Single().IsPresent);
|
||||
AddStep("disable allowed mods", () => MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem(MultiplayerClient.ServerRoom!.Playlist[0])
|
||||
{
|
||||
AllowedMods = []
|
||||
})));
|
||||
// This would normally be done as part of the above operation with an actual server.
|
||||
AddStep("disable user mods", () => MultiplayerClient.ChangeUserMods(API.LocalUser.Value.OnlineID, Array.Empty<APIMod>()));
|
||||
AddUntilStep("flashlight mod disabled", () => !MultiplayerClient.ClientRoom!.Users[0].Mods.Any());
|
||||
AddStep("re-enable allowed mods", () => MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem(new PlaylistItem(MultiplayerClient.ServerRoom!.Playlist[0])
|
||||
{
|
||||
AllowedMods = [new APIMod(new OsuModFlashlight())]
|
||||
})));
|
||||
AddAssert("flashlight mod still disabled", () => !MultiplayerClient.ClientRoom!.Users[0].Mods.Any());
|
||||
|
||||
// 3. Open the mod select overlay, check that the flashlight mod panel is deactivated.
|
||||
|
||||
ClickButtonWhenEnabled<UserModSelectButton>();
|
||||
AddUntilStep("mod select contents loaded", () => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
|
||||
AddAssert("flashlight mod still disabled", () => !MultiplayerClient.ClientRoom!.Users[0].Mods.Any());
|
||||
AddAssert("flashlight mod panel not activated", () => !this.ChildrenOfType<ModPanel>().Single(p => p.Mod is OsuModFlashlight).Active.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStartCountdown()
|
||||
{
|
||||
AddStep("set playlist", () =>
|
||||
{
|
||||
room.Playlist =
|
||||
[
|
||||
new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()).BeatmapInfo)
|
||||
{
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||
}
|
||||
];
|
||||
});
|
||||
|
||||
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
|
||||
|
||||
AddUntilStep("wait for room join", () => RoomJoined);
|
||||
|
||||
AddStep("click countdown button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerCountdownButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("start a countdown", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<Popover>().Single().ChildrenOfType<Button>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("countdown started", () => MultiplayerClient.ServerRoom!.ActiveCountdowns.Any());
|
||||
}
|
||||
|
||||
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Platform;
|
||||
@@ -32,7 +31,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private BeatmapManager beatmaps = null!;
|
||||
private BeatmapSetInfo importedSet = null!;
|
||||
private BeatmapInfo importedBeatmap = null!;
|
||||
private Room room = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host, AudioManager audio)
|
||||
@@ -47,19 +45,17 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("create room", () => room = CreateDefaultRoom());
|
||||
AddStep("join room", () => JoinRoom(room));
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("create list", () =>
|
||||
{
|
||||
Child = list = new MultiplayerPlaylist(room)
|
||||
Child = list = new MultiplayerPlaylist
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.4f, 0.8f),
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
Size = new Vector2(0.4f, 0.8f)
|
||||
};
|
||||
});
|
||||
|
||||
@@ -158,37 +154,36 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
assertQueueTabCount(0);
|
||||
}
|
||||
|
||||
[Ignore("Expired items are initially removed from the room.")]
|
||||
[Test]
|
||||
public void TestJoinRoomWithMixedItemsAddedInCorrectLists()
|
||||
{
|
||||
AddStep("leave room", () => MultiplayerClient.LeaveRoom());
|
||||
AddUntilStep("wait for room part", () => !RoomJoined);
|
||||
|
||||
AddStep("join room with items", () =>
|
||||
AddStep("join room with expired items", () =>
|
||||
{
|
||||
API.Queue(new CreateRoomRequest(new Room
|
||||
{
|
||||
Name = "test name",
|
||||
Playlist =
|
||||
[
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID,
|
||||
Expired = true
|
||||
}
|
||||
]
|
||||
}));
|
||||
Room room = CreateDefaultRoom();
|
||||
room.Playlist =
|
||||
[
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID
|
||||
},
|
||||
new PlaylistItem(new TestBeatmap(Ruleset.Value).BeatmapInfo)
|
||||
{
|
||||
RulesetID = Ruleset.Value.OnlineID,
|
||||
Expired = true
|
||||
}
|
||||
];
|
||||
|
||||
JoinRoom(room);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for room join", () => RoomJoined);
|
||||
WaitForJoined();
|
||||
|
||||
assertItemInQueueListStep(1, 0);
|
||||
assertItemInHistoryListStep(2, 0);
|
||||
// IDs are offset by 1 because we've joined two rooms in this test.
|
||||
assertItemInQueueListStep(2, 0);
|
||||
assertItemInHistoryListStep(3, 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -49,13 +49,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("create playlist", () =>
|
||||
{
|
||||
Child = playlist = new MultiplayerQueueList(room)
|
||||
Child = playlist = new MultiplayerQueueList
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(500, 300),
|
||||
Size = new Vector2(500, 300)
|
||||
};
|
||||
|
||||
playlist.Items.ReplaceRange(0, playlist.Items.Count, MultiplayerClient.ClientAPIRoom!.Playlist);
|
||||
|
||||
MultiplayerClient.ClientAPIRoom!.PropertyChanged += (_, e) =>
|
||||
{
|
||||
if (e.PropertyName == nameof(Room.Playlist))
|
||||
@@ -132,6 +134,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
assertDeleteButtonVisibility(1, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeExistingItem()
|
||||
{
|
||||
AddStep("change beatmap", () => MultiplayerClient.EditPlaylistItem(new MultiplayerPlaylistItem
|
||||
{
|
||||
ID = playlist.Items[0].ID,
|
||||
BeatmapID = 1337
|
||||
}).WaitSafely());
|
||||
|
||||
AddUntilStep("first playlist item has new beatmap", () => playlist.Items[0].Beatmap.OnlineID, () => Is.EqualTo(1337));
|
||||
}
|
||||
|
||||
private void addPlaylistItem(Func<int> userId)
|
||||
{
|
||||
long itemId = -1;
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneMultiplayerUserModDisplay : MultiplayerTestScene
|
||||
{
|
||||
private MultiplayerUserModDisplay modDisplay = null!;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("join room", () => JoinRoom(CreateDefaultRoom()));
|
||||
WaitForJoined();
|
||||
|
||||
AddStep("add display", () => Child = modDisplay = new MultiplayerUserModDisplay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChangeMods()
|
||||
{
|
||||
AddStep("set DT", () => MultiplayerClient.ChangeUserMods([new OsuModDoubleTime()]).WaitSafely());
|
||||
AddUntilStep("mod displayed", () => modDisplay.ChildrenOfType<ModIcon>().Count() == 1);
|
||||
|
||||
AddStep("set DT, HR", () => MultiplayerClient.ChangeUserMods([new OsuModDoubleTime(), new OsuModHardRock()]).WaitSafely());
|
||||
AddUntilStep("mods displayed", () => modDisplay.ChildrenOfType<ModIcon>().Count() == 2);
|
||||
|
||||
AddStep("set no mods", () => MultiplayerClient.ChangeUserMods(Enumerable.Empty<APIMod>()).WaitSafely());
|
||||
AddUntilStep("no mods displayed", () => !modDisplay.ChildrenOfType<ModIcon>().Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -201,6 +201,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
private bool checkRoomSelected(Room? room) => selectedRoom.Value == room;
|
||||
|
||||
private Room? getRoomInFlow(int index) =>
|
||||
(container.ChildrenOfType<FillFlowContainer<DrawableLoungeRoom>>().First().FlowingChildren.ElementAt(index) as DrawableRoom)?.Room;
|
||||
(container.ChildrenOfType<FillFlowContainer<LoungeRoomPanel>>().First().FlowingChildren.ElementAt(index) as RoomPanel)?.Room;
|
||||
}
|
||||
}
|
||||
|
||||
+16
-25
@@ -18,13 +18,13 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Tests.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestSceneDrawableRoom : OsuTestScene
|
||||
public partial class TestSceneRoomPanel : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
@@ -129,24 +129,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestEnableAndDisablePassword()
|
||||
{
|
||||
DrawableRoom drawableRoom = null!;
|
||||
RoomPanel panel = null!;
|
||||
Room room = null!;
|
||||
|
||||
AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room
|
||||
AddStep("create room", () => Child = panel = createLoungeRoom(room = new Room
|
||||
{
|
||||
Name = "Room with password",
|
||||
Type = MatchType.HeadToHead,
|
||||
}));
|
||||
|
||||
AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType<DrawableRoomParticipantsList>().Any());
|
||||
AddUntilStep("wait for panel load", () => panel.ChildrenOfType<DrawableRoomParticipantsList>().Any());
|
||||
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("set password", () => room.Password = "password");
|
||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("unset password", () => room.Password = string.Empty);
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, panel.ChildrenOfType<RoomPanel.PasswordProtectedIcon>().Single().Alpha));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -160,38 +160,29 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Spacing = new Vector2(5),
|
||||
Children = new[]
|
||||
{
|
||||
new DrawableMatchRoom(new Room
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "A host-only room",
|
||||
QueueMode = QueueMode.HostOnly,
|
||||
Type = MatchType.HeadToHead,
|
||||
})
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
},
|
||||
new DrawableMatchRoom(new Room
|
||||
}),
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "An all-players, team-versus room",
|
||||
QueueMode = QueueMode.AllPlayers,
|
||||
Type = MatchType.TeamVersus
|
||||
})
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
},
|
||||
new DrawableMatchRoom(new Room
|
||||
}),
|
||||
new MultiplayerRoomPanel(new Room
|
||||
{
|
||||
Name = "A round-robin room",
|
||||
QueueMode = QueueMode.AllPlayersRoundRobin,
|
||||
Type = MatchType.HeadToHead
|
||||
})
|
||||
{
|
||||
SelectedItem = new Bindable<PlaylistItem?>()
|
||||
},
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private DrawableRoom createLoungeRoom(Room room)
|
||||
private RoomPanel createLoungeRoom(Room room)
|
||||
{
|
||||
room.Host ??= new APIUser { Username = "peppy", Id = 2 };
|
||||
|
||||
@@ -204,7 +195,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
return new DrawableLoungeRoom(room)
|
||||
return new LoungeRoomPanel(room)
|
||||
{
|
||||
MatchingFilter = true,
|
||||
SelectedRoom = selectedRoom
|
||||
@@ -33,6 +33,10 @@ using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Overlays.Toolbar;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
using osu.Game.Rulesets.Mania.UI;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Scoring;
|
||||
@@ -394,6 +398,60 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestScrollSpeedAdjustDuringGameplay()
|
||||
{
|
||||
Player player = null;
|
||||
|
||||
Screens.Select.SongSelect songSelect = null;
|
||||
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
|
||||
AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded);
|
||||
|
||||
AddStep("import beatmap", () => BeatmapImportHelper.LoadOszIntoOsu(Game).WaitSafely());
|
||||
|
||||
AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("switch to mania ruleset", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.LControl);
|
||||
InputManager.Key(Key.Number4);
|
||||
InputManager.ReleaseKey(Key.LControl);
|
||||
});
|
||||
|
||||
AddStep("set mods", () => Game.SelectedMods.Value = new Mod[] { new OsuModNoFail() });
|
||||
AddStep("press enter", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for player", () =>
|
||||
{
|
||||
DismissAnyNotifications();
|
||||
player = Game.ScreenStack.CurrentScreen as Player;
|
||||
return player?.IsLoaded == true;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for track playing", () => Game.Beatmap.Value.Track.IsRunning);
|
||||
checkScrollSpeed(8, 8);
|
||||
|
||||
AddStep("adjust scroll speed via keyboard", () => InputManager.Key(Key.F4));
|
||||
checkScrollSpeed(9, 9);
|
||||
|
||||
AddStep("seek beyond 10 seconds", () => player.ChildrenOfType<GameplayClockContainer>().First().Seek(10500));
|
||||
AddUntilStep("wait for seek", () => player.ChildrenOfType<GameplayClockContainer>().First().CurrentTime, () => Is.GreaterThan(10600));
|
||||
AddStep("attempt adjust offset via keyboard", () => InputManager.Key(Key.F4));
|
||||
checkScrollSpeed(9, 9);
|
||||
|
||||
AddStep("attempt adjust offset via config change", () => getConfigManager().SetValue(ManiaRulesetSetting.ScrollSpeed, 10.0));
|
||||
checkScrollSpeed(10, 9);
|
||||
|
||||
void checkScrollSpeed(double configValue, double gameplayValue)
|
||||
{
|
||||
AddUntilStep($"config value is {configValue}", () => getConfigManager().Get<double>(ManiaRulesetSetting.ScrollSpeed), () => Is.EqualTo(configValue));
|
||||
AddUntilStep($"gameplay value is {gameplayValue}", () => this.ChildrenOfType<DrawableManiaRuleset>().Single().TargetTimeRange,
|
||||
() => Is.EqualTo(DrawableManiaRuleset.ComputeScrollTime(gameplayValue)));
|
||||
}
|
||||
|
||||
ManiaRulesetConfigManager getConfigManager() => ((ManiaRulesetConfigManager)Game.Dependencies.Get<IRulesetConfigCache>().GetConfigFor(new ManiaRuleset())!);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOffsetAdjustDuringGameplay()
|
||||
{
|
||||
|
||||
@@ -99,8 +99,35 @@ namespace osu.Game.Tests.Visual.Online
|
||||
Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(),
|
||||
Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(),
|
||||
},
|
||||
TopTags =
|
||||
[
|
||||
new APIBeatmapTag { TagId = 4, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 2, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 23, VoteCount = 5 },
|
||||
],
|
||||
},
|
||||
},
|
||||
RelatedTags =
|
||||
[
|
||||
new APITag
|
||||
{
|
||||
Id = 2,
|
||||
Name = "song representation/simple",
|
||||
Description = "Accessible and straightforward map design."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 4,
|
||||
Name = "style/clean",
|
||||
Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 23,
|
||||
Name = "aim/aim control",
|
||||
Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern."
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -215,6 +215,32 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChannelCloseViaMiddleClick()
|
||||
{
|
||||
var testPMChannel = new Channel(testUser);
|
||||
|
||||
AddStep("Show overlay", () => chatOverlay.Show());
|
||||
joinTestChannel(0);
|
||||
joinChannel(testPMChannel);
|
||||
AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
|
||||
AddStep("Middle click", () =>
|
||||
{
|
||||
var item = getChannelListItem(testPMChannel);
|
||||
InputManager.MoveMouseTo(item);
|
||||
InputManager.Click(MouseButton.Middle);
|
||||
});
|
||||
AddAssert("PM channel closed", () => !channelManager.JoinedChannels.Contains(testPMChannel));
|
||||
AddStep("Select normal channel", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||
AddStep("Click close button", () =>
|
||||
{
|
||||
var item = getChannelListItem(testChannel1);
|
||||
InputManager.MoveMouseTo(item);
|
||||
InputManager.Click(MouseButton.Middle);
|
||||
});
|
||||
AddAssert("Normal channel closed", () => !channelManager.JoinedChannels.Contains(testChannel1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestChannelCloseButton()
|
||||
{
|
||||
|
||||
@@ -61,9 +61,9 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddUntilStep("last room is not masked", () => checkRoomVisible(roomListing.DrawableRooms[^1]));
|
||||
}
|
||||
|
||||
private bool checkRoomVisible(DrawableRoom room) =>
|
||||
private bool checkRoomVisible(RoomPanel panel) =>
|
||||
loungeScreen.ChildrenOfType<OsuScrollContainer>().First().ScreenSpaceDrawQuad
|
||||
.Contains(room.ScreenSpaceDrawQuad.Centre);
|
||||
.Contains(panel.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
private void createRooms(params Room[] rooms)
|
||||
{
|
||||
|
||||
@@ -176,6 +176,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
RulesetID = new OsuRuleset().RulesetInfo.OnlineID
|
||||
}
|
||||
];
|
||||
room.EndDate = DateTimeOffset.Now.AddHours(1);
|
||||
});
|
||||
|
||||
AddAssert("match has default beatmap", () => match.Beatmap.IsDefault);
|
||||
@@ -212,6 +213,11 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null);
|
||||
importedBeatmap = manager.Import(beatmap.BeatmapInfo.BeatmapSet)!.Value.Detach();
|
||||
Realm.Write(r =>
|
||||
{
|
||||
foreach (var beatmapInfo in r.All<BeatmapInfo>())
|
||||
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
|
||||
});
|
||||
});
|
||||
|
||||
private partial class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen
|
||||
|
||||
@@ -65,7 +65,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 1,
|
||||
DifficultyName = "Osu 1",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "11111111",
|
||||
OnlineMD5Hash = "11111111",
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
@@ -79,7 +80,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 2,
|
||||
DifficultyName = "Osu 2",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "22222222",
|
||||
OnlineMD5Hash = "22222222",
|
||||
Ruleset = new OsuRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
@@ -93,7 +95,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 3,
|
||||
DifficultyName = "Taiko 1",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "33333333",
|
||||
OnlineMD5Hash = "33333333",
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
@@ -107,7 +110,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
OnlineID = 4,
|
||||
DifficultyName = "Taiko 2",
|
||||
Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = Guid.NewGuid().ToString().ComputeMD5Hash(),
|
||||
MD5Hash = "44444444",
|
||||
OnlineMD5Hash = "44444444",
|
||||
Ruleset = new TaikoRuleset().RulesetInfo,
|
||||
Metadata =
|
||||
{
|
||||
|
||||
@@ -168,6 +168,19 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
};
|
||||
});
|
||||
|
||||
public static List<HitEvent> CreateHitEvents(double offset = 0, int count = 50)
|
||||
{
|
||||
var hitEvents = new List<HitEvent>();
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
for (int j = 0; j < count; j++)
|
||||
hitEvents.Add(new HitEvent(offset, 1.0, HitResult.Perfect, placeholder_object, placeholder_object, null));
|
||||
}
|
||||
|
||||
return hitEvents;
|
||||
}
|
||||
|
||||
public static List<HitEvent> CreateDistributedHitEvents(double centre = 0, double range = 25)
|
||||
{
|
||||
var hitEvents = new List<HitEvent>();
|
||||
|
||||
@@ -26,6 +26,7 @@ using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
@@ -52,6 +53,9 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
private RulesetStore rulesetStore = null!;
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
@@ -214,14 +218,10 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
Tags =
|
||||
[
|
||||
new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", },
|
||||
new APITag
|
||||
{
|
||||
Id = 2, Name = "alt",
|
||||
Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.",
|
||||
},
|
||||
new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", },
|
||||
new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", },
|
||||
new APITag { Id = 1, Name = "song representation/simple", Description = "Accessible and straightforward map design.", },
|
||||
new APITag { Id = 2, Name = "style/clean", Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.", },
|
||||
new APITag { Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.", },
|
||||
new APITag { Id = 4, Name = "tap/bursts", Description = "Patterns requiring continuous movement and alternating, typically 9 notes or less.", },
|
||||
]
|
||||
}), 500);
|
||||
return true;
|
||||
@@ -368,12 +368,16 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
|
||||
private void loadPanel(ScoreInfo score) => AddStep("load panel", () =>
|
||||
{
|
||||
Child = new StatisticsPanel
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
AchievedScore = score,
|
||||
Child = new StatisticsPanel
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
State = { Value = Visibility.Visible },
|
||||
Score = { Value = score },
|
||||
AchievedScore = score,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Testing;
|
||||
@@ -9,21 +10,23 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Screens.Ranking;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
public partial class TestSceneUserTagControl : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("set up working beatmap", () =>
|
||||
{
|
||||
Beatmap.Value.BeatmapInfo.OnlineID = 42;
|
||||
});
|
||||
AddStep("set up network requests", () =>
|
||||
{
|
||||
dummyAPI.HandleRequest = request =>
|
||||
@@ -36,10 +39,19 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
{
|
||||
Tags =
|
||||
[
|
||||
new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", },
|
||||
new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", },
|
||||
new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", },
|
||||
new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", },
|
||||
new APITag { Id = 0, Name = "uncategorised tag", Description = "This probably isn't real but could be and should be handled.", },
|
||||
new APITag { Id = 1, Name = "song representation/simple", Description = "Accessible and straightforward map design.", },
|
||||
new APITag
|
||||
{
|
||||
Id = 2, Name = "style/clean",
|
||||
Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.",
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.",
|
||||
},
|
||||
new APITag { Id = 4, Name = "tap/bursts", Description = "Patterns requiring continuous movement and alternating, typically 9 notes or less.", },
|
||||
new APITag { Id = 5, Name = "style/mono-heavy", Description = "Features monos used in large amounts.", RulesetId = 1, },
|
||||
]
|
||||
}), 500);
|
||||
return true;
|
||||
@@ -67,19 +79,34 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
return false;
|
||||
};
|
||||
});
|
||||
AddStep("create control", () =>
|
||||
AddStep("show for osu! beatmap", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new UserTagControl(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
Width = 500,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
working.BeatmapInfo.OnlineID = 42;
|
||||
Beatmap.Value = working;
|
||||
recreateControl();
|
||||
});
|
||||
AddStep("show for taiko beatmap", () =>
|
||||
{
|
||||
var working = CreateWorkingBeatmap(new TaikoRuleset().RulesetInfo);
|
||||
working.BeatmapInfo.OnlineID = 44;
|
||||
Beatmap.Value = working;
|
||||
recreateControl();
|
||||
});
|
||||
}
|
||||
|
||||
private void recreateControl()
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new UserTagControl(Beatmap.Value.BeatmapInfo)
|
||||
{
|
||||
Width = 700,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays.Settings.Sections.Audio;
|
||||
@@ -70,16 +73,54 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("clear history", () => tracker.ClearHistory());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRounding()
|
||||
{
|
||||
AddStep("set new score", () => statics.SetValue(Static.LastLocalUserScore, new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateHitEvents(0.6),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
}));
|
||||
|
||||
checkButtonEnabled();
|
||||
AddStep("click button", () => adjustControl.ChildrenOfType<Button>().Single().TriggerClick());
|
||||
checkButtonDisabled();
|
||||
AddAssert("global offset set correctly", () => localConfig.Get<double>(OsuSetting.AudioOffset), () => Is.EqualTo(-1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNegligibleChangeNotApplicable()
|
||||
{
|
||||
AddStep("set new score", () => statics.SetValue(Static.LastLocalUserScore, new ScoreInfo
|
||||
{
|
||||
HitEvents = TestSceneHitEventTimingDistributionGraph.CreateHitEvents(0.5),
|
||||
BeatmapInfo = Beatmap.Value.BeatmapInfo,
|
||||
}));
|
||||
checkButtonDisabled();
|
||||
|
||||
AddStep("adjust global offset", () => localConfig.SetValue(OsuSetting.AudioOffset, 50.0));
|
||||
checkButtonEnabled();
|
||||
|
||||
AddStep("click button", () => adjustControl.ChildrenOfType<Button>().Single().TriggerClick());
|
||||
checkButtonDisabled();
|
||||
AddAssert("global offset set correctly", () => localConfig.Get<double>(OsuSetting.AudioOffset), () => Is.EqualTo(0));
|
||||
AddStep("clear history", () => tracker.ClearHistory());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBehaviour()
|
||||
{
|
||||
AddStep("set score with -20ms", () => setScore(-20));
|
||||
AddAssert("suggested global offset is 20ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(20));
|
||||
checkButtonEnabled();
|
||||
AddStep("clear history", () => tracker.ClearHistory());
|
||||
checkButtonDisabled();
|
||||
|
||||
AddStep("set score with 40ms", () => setScore(40));
|
||||
checkButtonEnabled();
|
||||
AddAssert("suggested global offset is -40ms", () => adjustControl.SuggestedOffset.Value, () => Is.EqualTo(-40));
|
||||
AddStep("clear history", () => tracker.ClearHistory());
|
||||
checkButtonDisabled();
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -111,6 +152,16 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("clear history", () => tracker.ClearHistory());
|
||||
}
|
||||
|
||||
private void checkButtonDisabled()
|
||||
{
|
||||
AddAssert("button is disabled", () => adjustControl.ChildrenOfType<Button>().Single().Enabled.Value, () => Is.False);
|
||||
}
|
||||
|
||||
private void checkButtonEnabled()
|
||||
{
|
||||
AddAssert("button is enabled", () => adjustControl.ChildrenOfType<Button>().Single().Enabled.Value, () => Is.True);
|
||||
}
|
||||
|
||||
private void setScore(double averageHitError)
|
||||
{
|
||||
statics.SetValue(Static.LastLocalUserScore, new ScoreInfo
|
||||
|
||||
@@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
foreach (var rulesetInfo in rulesets.AvailableRulesets)
|
||||
{
|
||||
var instance = rulesetInfo.CreateInstance();
|
||||
var testBeatmap = createTestBeatmap(rulesetInfo);
|
||||
var testBeatmap = CreateTestBeatmap(rulesetInfo);
|
||||
|
||||
beatmaps.Add(testBeatmap);
|
||||
|
||||
@@ -124,6 +124,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTruncation()
|
||||
{
|
||||
selectBeatmap(CreateLongMetadata());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullBeatmap()
|
||||
{
|
||||
@@ -135,17 +141,11 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTruncation()
|
||||
{
|
||||
selectBeatmap(createLongMetadata());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBPMUpdates()
|
||||
{
|
||||
const double bpm = 120;
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
IBeatmap beatmap = CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / bpm });
|
||||
|
||||
OsuModDoubleTime doubleTime = null!;
|
||||
@@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[TestCase(120, 120.4, "DT", "180")]
|
||||
public void TestVaryingBPM(double commonBpm, double otherBpm, string? mod, string expectedDisplay)
|
||||
{
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
IBeatmap beatmap = CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm });
|
||||
beatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 60 * 1000 / otherBpm });
|
||||
beatmap.ControlPointInfo.Add(200, new TimingControlPoint { BeatLength = 60 * 1000 / commonBpm });
|
||||
@@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
[TestCase]
|
||||
public void TestLengthUpdates()
|
||||
{
|
||||
IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
IBeatmap beatmap = CreateTestBeatmap(new OsuRuleset().RulesetInfo);
|
||||
double drain = beatmap.CalculateDrainLength();
|
||||
beatmap.BeatmapInfo.Length = drain;
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||
}
|
||||
|
||||
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
||||
public static IBeatmap CreateTestBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
List<HitObject> objects = new List<HitObject>();
|
||||
for (double i = 0; i < 50000; i += 1000)
|
||||
@@ -274,7 +274,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
};
|
||||
}
|
||||
|
||||
private IBeatmap createLongMetadata()
|
||||
public static IBeatmap CreateLongMetadata()
|
||||
{
|
||||
return new Beatmap
|
||||
{
|
||||
|
||||
@@ -43,6 +43,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private PlaySongSelect songSelect = null!;
|
||||
|
||||
private LeaderboardManager leaderboardManager = null!;
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||
@@ -51,6 +53,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get<AudioManager>(), Resources, dependencies.Get<GameHost>(), Beatmap.Default));
|
||||
dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, API));
|
||||
dependencies.CacheAs<Screens.Select.SongSelect>(songSelect = new PlaySongSelect());
|
||||
dependencies.Cache(leaderboardManager = new LeaderboardManager());
|
||||
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
return dependencies;
|
||||
@@ -60,6 +64,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void load()
|
||||
{
|
||||
LoadComponent(songSelect);
|
||||
LoadComponent(leaderboardManager);
|
||||
}
|
||||
|
||||
public TestSceneBeatmapLeaderboard()
|
||||
@@ -112,6 +117,27 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
checkDisplayedCount(0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalScoresDisplayWorksWhenStartingOffline()
|
||||
{
|
||||
BeatmapInfo beatmapInfo = null!;
|
||||
|
||||
AddStep("Log out", () => API.Logout());
|
||||
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Local);
|
||||
|
||||
AddStep(@"Set beatmap", () =>
|
||||
{
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First();
|
||||
|
||||
leaderboard.BeatmapInfo = beatmapInfo;
|
||||
});
|
||||
|
||||
clearScores();
|
||||
importMoreScores(() => beatmapInfo);
|
||||
checkDisplayedCount(10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalScoresDisplayOnBeatmapEdit()
|
||||
{
|
||||
@@ -180,8 +206,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
public void TestGlobalScoresDisplay()
|
||||
{
|
||||
AddStep(@"Set scope", () => leaderboard.Scope = BeatmapLeaderboardScope.Global);
|
||||
AddStep(@"New Scores", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo())));
|
||||
AddStep(@"New Scores with teams", () => leaderboard.SetScores(generateSampleScores(new BeatmapInfo()).Select(s =>
|
||||
AddStep(@"New Scores", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo())));
|
||||
AddStep(@"New Scores with teams", () => leaderboard.SetScores(GenerateSampleScores(new BeatmapInfo()).Select(s =>
|
||||
{
|
||||
s.User.Team = new APITeam();
|
||||
return s;
|
||||
@@ -286,7 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
AddStep(@"Import new scores", () =>
|
||||
{
|
||||
foreach (var score in generateSampleScores(beatmapInfo()))
|
||||
foreach (var score in GenerateSampleScores(beatmapInfo()))
|
||||
scoreManager.Import(score);
|
||||
});
|
||||
}
|
||||
@@ -302,7 +328,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
private void checkStoredCount(int expected) =>
|
||||
AddUntilStep($"Total scores stored is {expected}", () => Realm.Run(r => r.All<ScoreInfo>().Count(s => !s.DeletePending)), () => Is.EqualTo(expected));
|
||||
|
||||
private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo)
|
||||
public static ScoreInfo[] GenerateSampleScores(BeatmapInfo beatmapInfo)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
@@ -316,7 +342,6 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Mods = new Mod[]
|
||||
{
|
||||
new OsuModHidden(),
|
||||
new OsuModHardRock(),
|
||||
new OsuModFlashlight
|
||||
{
|
||||
FollowDelay = { Value = 200 },
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneCollectionDropdown : OsuManualInputManagerTestScene
|
||||
{
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private CollectionDropdown dropdown = null!;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
writeAndRefresh(r => r.RemoveAll<BeatmapCollection>());
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = dropdown = new CollectionDropdown
|
||||
{
|
||||
Width = 300,
|
||||
Y = 100,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEmptyCollectionFilterContainsAllBeatmaps()
|
||||
{
|
||||
assertCollectionDropdownContains("All beatmaps");
|
||||
assertCollectionHeaderDisplays("All beatmaps");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionAddedToDropdown()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionsCleared()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "3"))));
|
||||
|
||||
AddAssert("check count 5", () => dropdown.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(5));
|
||||
|
||||
AddStep("delete all collections", () => writeAndRefresh(r => r.RemoveAll<BeatmapCollection>()));
|
||||
|
||||
AddAssert("check count 2", () => dropdown.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRemovedFromDropdown()
|
||||
{
|
||||
BeatmapCollection first = null!;
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(first = new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
AddStep("remove collection", () => writeAndRefresh(r => r.Remove(first)));
|
||||
|
||||
assertCollectionDropdownContains("1", false);
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRenamed()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
AddStep("select collection", () => dropdown.Current.Value = dropdown.ItemSource.ElementAt(1));
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("change name", () => writeAndRefresh(_ => getFirstCollection().Name = "First"));
|
||||
|
||||
assertCollectionDropdownContains("First");
|
||||
assertCollectionHeaderDisplays("First");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllBeatmapFilterDoesNotHaveAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("hover all beatmaps", () => InputManager.MoveMouseTo(getAddOrRemoveButton(0)));
|
||||
AddAssert("'All beatmaps' filter does not have add button", () => !getAddOrRemoveButton(0).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionFilterHasAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1)));
|
||||
AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonDisabledAndEnabledWithBeatmapChanges()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value);
|
||||
|
||||
AddStep("set dummy beatmap", () => Beatmap.SetDefault());
|
||||
AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonChangesWhenAddedAndRemovedFromCollection()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
|
||||
AddStep("add beatmap to collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)));
|
||||
assertFirstButtonIs(FontAwesome.Solid.MinusSquare);
|
||||
|
||||
AddStep("remove beatmap from collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Clear()));
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonAddsAndRemovesBeatmap()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection contains beatmap", () => getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash));
|
||||
assertFirstButtonIs(FontAwesome.Solid.MinusSquare);
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection does not contain beatmap", () => !getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash));
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManageCollectionsFilterIsNotSelected()
|
||||
{
|
||||
bool received = false;
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1", new List<string> { "abc" }))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
AddStep("select collection", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("watch for filter requests", () =>
|
||||
{
|
||||
received = false;
|
||||
dropdown.ChildrenOfType<CollectionDropdown>().First().RequestFilter = () => received = true;
|
||||
});
|
||||
|
||||
AddStep("click manage collections filter", () =>
|
||||
{
|
||||
int lastItemIndex = dropdown.ChildrenOfType<CollectionDropdown>().Single().Items.Count() - 1;
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(lastItemIndex));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("collection filter still selected", () => dropdown.Current.Value.CollectionName == "1");
|
||||
|
||||
AddAssert("filter request not fired", () => !received);
|
||||
}
|
||||
|
||||
private void writeAndRefresh(Action<Realm> action) => Realm.Write(r =>
|
||||
{
|
||||
action(r);
|
||||
r.Refresh();
|
||||
});
|
||||
|
||||
private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All<BeatmapCollection>().First());
|
||||
|
||||
private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true)
|
||||
=> AddUntilStep($"collection dropdown header displays '{collectionName}'",
|
||||
() => shouldDisplay == dropdown.ChildrenOfType<CollectionDropdown.OsuDropdownHeader>().Any(h => h.ChildrenOfType<SpriteText>().Any(t => t.Text == collectionName)));
|
||||
|
||||
private void assertFirstButtonIs(IconUsage icon) => AddUntilStep($"button is {icon.Icon.ToString()}", () => getAddOrRemoveButton(1).Icon.Equals(icon));
|
||||
|
||||
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||
AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
|
||||
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
|
||||
() => shouldContain == dropdown.ChildrenOfType<Menu.DrawableMenuItem>().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName));
|
||||
|
||||
private IconButton getAddOrRemoveButton(int index)
|
||||
=> getCollectionDropdownItemAt(index).ChildrenOfType<IconButton>().Single();
|
||||
|
||||
private void addExpandHeaderStep() => AddStep("expand header", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dropdown.ChildrenOfType<CollectionDropdown.OsuDropdownHeader>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or remove button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getAddOrRemoveButton(index));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private Menu.DrawableMenuItem getCollectionDropdownItemAt(int index)
|
||||
{
|
||||
// todo: we should be able to use Items, but apparently that's not guaranteed to be ordered... see: https://github.com/ppy/osu-framework/pull/6079
|
||||
CollectionFilterMenuItem item = dropdown.ChildrenOfType<CollectionDropdown>().Single().ItemSource.ElementAt(index);
|
||||
return dropdown.ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value == item.CollectionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +1,18 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
using Realms;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneFilterControl : OsuManualInputManagerTestScene
|
||||
{
|
||||
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private FilterControl control = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
|
||||
base.Content.AddRange(new Drawable[]
|
||||
{
|
||||
Content
|
||||
});
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
writeAndRefresh(r => r.RemoveAll<BeatmapCollection>());
|
||||
|
||||
Child = control = new FilterControl
|
||||
Child = new FilterControl
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -59,216 +20,5 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Height = FilterControl.HEIGHT,
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEmptyCollectionFilterContainsAllBeatmaps()
|
||||
{
|
||||
assertCollectionDropdownContains("All beatmaps");
|
||||
assertCollectionHeaderDisplays("All beatmaps");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionAddedToDropdown()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionsCleared()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "3"))));
|
||||
|
||||
AddAssert("check count 5", () => control.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(5));
|
||||
|
||||
AddStep("delete all collections", () => writeAndRefresh(r => r.RemoveAll<BeatmapCollection>()));
|
||||
|
||||
AddAssert("check count 2", () => control.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRemovedFromDropdown()
|
||||
{
|
||||
BeatmapCollection first = null!;
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(first = new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
AddStep("remove collection", () => writeAndRefresh(r => r.Remove(first)));
|
||||
|
||||
assertCollectionDropdownContains("1", false);
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRenamed()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
AddStep("select collection", () =>
|
||||
{
|
||||
var dropdown = control.ChildrenOfType<CollectionDropdown>().Single();
|
||||
dropdown.Current.Value = dropdown.ItemSource.ElementAt(1);
|
||||
});
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("change name", () => writeAndRefresh(_ => getFirstCollection().Name = "First"));
|
||||
|
||||
assertCollectionDropdownContains("First");
|
||||
assertCollectionHeaderDisplays("First");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllBeatmapFilterDoesNotHaveAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("hover all beatmaps", () => InputManager.MoveMouseTo(getAddOrRemoveButton(0)));
|
||||
AddAssert("'All beatmaps' filter does not have add button", () => !getAddOrRemoveButton(0).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionFilterHasAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1)));
|
||||
AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonDisabledAndEnabledWithBeatmapChanges()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value);
|
||||
|
||||
AddStep("set dummy beatmap", () => Beatmap.SetDefault());
|
||||
AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonChangesWhenAddedAndRemovedFromCollection()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
|
||||
AddStep("add beatmap to collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)));
|
||||
assertFirstButtonIs(FontAwesome.Solid.MinusSquare);
|
||||
|
||||
AddStep("remove beatmap from collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Clear()));
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonAddsAndRemovesBeatmap()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection contains beatmap", () => getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash));
|
||||
assertFirstButtonIs(FontAwesome.Solid.MinusSquare);
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection does not contain beatmap", () => !getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash));
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManageCollectionsFilterIsNotSelected()
|
||||
{
|
||||
bool received = false;
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1", new List<string> { "abc" }))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
AddStep("select collection", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("watch for filter requests", () =>
|
||||
{
|
||||
received = false;
|
||||
control.ChildrenOfType<CollectionDropdown>().First().RequestFilter = () => received = true;
|
||||
});
|
||||
|
||||
AddStep("click manage collections filter", () =>
|
||||
{
|
||||
int lastItemIndex = control.ChildrenOfType<CollectionDropdown>().Single().Items.Count() - 1;
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(lastItemIndex));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes?.Any() == true);
|
||||
|
||||
AddAssert("filter request not fired", () => !received);
|
||||
}
|
||||
|
||||
private void writeAndRefresh(Action<Realm> action) => Realm.Write(r =>
|
||||
{
|
||||
action(r);
|
||||
r.Refresh();
|
||||
});
|
||||
|
||||
private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All<BeatmapCollection>().First());
|
||||
|
||||
private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true)
|
||||
=> AddUntilStep($"collection dropdown header displays '{collectionName}'",
|
||||
() => shouldDisplay == (control.ChildrenOfType<CollectionDropdown.CollectionDropdownHeader>().Single().ChildrenOfType<SpriteText>().First().Text == collectionName));
|
||||
|
||||
private void assertFirstButtonIs(IconUsage icon) => AddUntilStep($"button is {icon.Icon.ToString()}", () => getAddOrRemoveButton(1).Icon.Equals(icon));
|
||||
|
||||
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||
AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
|
||||
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
|
||||
() => shouldContain == control.ChildrenOfType<Menu.DrawableMenuItem>().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName));
|
||||
|
||||
private IconButton getAddOrRemoveButton(int index)
|
||||
=> getCollectionDropdownItemAt(index).ChildrenOfType<IconButton>().Single();
|
||||
|
||||
private void addExpandHeaderStep() => AddStep("expand header", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(control.ChildrenOfType<CollectionDropdown.CollectionDropdownHeader>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or remove button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getAddOrRemoveButton(index));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private Menu.DrawableMenuItem getCollectionDropdownItemAt(int index)
|
||||
{
|
||||
// todo: we should be able to use Items, but apparently that's not guaranteed to be ordered... see: https://github.com/ppy/osu-framework/pull/6079
|
||||
CollectionFilterMenuItem item = control.ChildrenOfType<CollectionDropdown>().Single().ItemSource.ElementAt(index);
|
||||
return control.ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value == item.CollectionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Collections
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
public partial class TestSceneManageCollectionsDialog : OsuManualInputManagerTestScene
|
||||
{
|
||||
+7
-4
@@ -16,6 +16,7 @@ using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Select;
|
||||
@@ -27,9 +28,9 @@ using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
using BeatmapCarousel = osu.Game.Screens.SelectV2.BeatmapCarousel;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public abstract partial class BeatmapCarouselV2TestScene : OsuManualInputManagerTestScene
|
||||
public abstract partial class BeatmapCarouselTestScene : OsuManualInputManagerTestScene
|
||||
{
|
||||
protected readonly BindableList<BeatmapSetInfo> BeatmapSets = new BindableList<BeatmapSetInfo>();
|
||||
|
||||
@@ -47,7 +48,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private int beatmapCount;
|
||||
|
||||
protected BeatmapCarouselV2TestScene()
|
||||
protected BeatmapCarouselTestScene()
|
||||
{
|
||||
store = new TestBeatmapStore
|
||||
{
|
||||
@@ -96,6 +97,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
{
|
||||
Carousel = new BeatmapCarousel
|
||||
{
|
||||
BleedTop = 50,
|
||||
BleedBottom = 50,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 800,
|
||||
@@ -189,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
.Where(p => ((ICarouselPanel)p).Item?.IsVisible == true)
|
||||
.OrderBy(p => p.Y)
|
||||
.ElementAt(index)
|
||||
.ChildrenOfType<PanelBase>().Single()
|
||||
.ChildrenOfType<Panel>().Single()
|
||||
.TriggerClick();
|
||||
});
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Overlays;
|
||||
@@ -20,33 +20,32 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
};
|
||||
|
||||
private Container? resizeContainer;
|
||||
private float relativeWidth;
|
||||
|
||||
protected virtual Anchor ComponentAnchor => Anchor.TopLeft;
|
||||
protected virtual float InitialRelativeWidth => 0.5f;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
base.Content.Child = resizeContainer = new Container
|
||||
base.Content.Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
Width = relativeWidth,
|
||||
Children = new Drawable[]
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = resizeContainer = new Container
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background5,
|
||||
},
|
||||
Content
|
||||
Anchor = ComponentAnchor,
|
||||
Origin = ComponentAnchor,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = relativeWidth,
|
||||
Child = Content
|
||||
}
|
||||
};
|
||||
|
||||
AddSliderStep("change relative width", 0, 1f, 1f, v =>
|
||||
AddSliderStep("change relative width", 0, 1f, InitialRelativeWidth, v =>
|
||||
{
|
||||
if (resizeContainer != null)
|
||||
resizeContainer.Width = v;
|
||||
@@ -55,6 +54,12 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
ChangeBackgroundColour(ColourProvider.Background6);
|
||||
}
|
||||
|
||||
[SetUpSteps]
|
||||
public virtual void SetUpSteps()
|
||||
{
|
||||
|
||||
+2
-2
@@ -10,13 +10,13 @@ using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
/// <summary>
|
||||
/// Covers common steps which can be used for manual testing.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2 : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarousel : BeatmapCarouselTestScene
|
||||
{
|
||||
[Test]
|
||||
[Explicit]
|
||||
+2
-2
@@ -9,10 +9,10 @@ using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2ArtistGrouping : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselArtistGrouping : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
+3
-2
@@ -5,15 +5,16 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2DifficultyGrouping : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselDifficultyGrouping : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
+3
-2
@@ -5,16 +5,17 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2NoGrouping : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselNoGrouping : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
+2
-2
@@ -8,10 +8,10 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelect
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneBeatmapCarouselV2Scrolling : BeatmapCarouselV2TestScene
|
||||
public partial class TestSceneBeatmapCarouselScrolling : BeatmapCarouselTestScene
|
||||
{
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
@@ -1,83 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2GroupPanel : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneBeatmapCarouselV2GroupPanel()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A"))
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(1, "1"))
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(3, "3")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(5, "5")),
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(7, "7")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(8, "8")),
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(9, "9")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapInfoWedge : SongSelectComponentsTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private TestBeatmapInfoWedgeV2 infoWedge = null!;
|
||||
private readonly List<IBeatmap> beatmaps = new List<IBeatmap>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(RulesetStore rulesets)
|
||||
{
|
||||
this.rulesets = rulesets;
|
||||
}
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
// This exists only to make the wedge more visible in the test scene
|
||||
new Box
|
||||
{
|
||||
Y = -20,
|
||||
Colour = Colour4.Cornsilk.Darken(0.2f),
|
||||
Height = BeatmapInfoWedgeV2.WEDGE_HEIGHT + 40,
|
||||
Width = 0.65f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Top = 20, Left = -10 }
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 20 },
|
||||
Child = infoWedge = new TestBeatmapInfoWedgeV2
|
||||
{
|
||||
Width = 0.6f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
AddSliderStep("change star difficulty", 0, 11.9, 5.55, v =>
|
||||
{
|
||||
foreach (var hasCurrentValue in infoWedge.ChildrenOfType<IHasCurrentValue<StarDifficulty>>())
|
||||
hasCurrentValue.Current.Value = new StarDifficulty(v, 0);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRulesetChange()
|
||||
{
|
||||
selectBeatmap(Beatmap.Value.Beatmap);
|
||||
|
||||
AddWaitStep("wait for select", 3);
|
||||
|
||||
foreach (var rulesetInfo in rulesets.AvailableRulesets)
|
||||
{
|
||||
var instance = rulesetInfo.CreateInstance();
|
||||
var testBeatmap = createTestBeatmap(rulesetInfo);
|
||||
|
||||
beatmaps.Add(testBeatmap);
|
||||
|
||||
setRuleset(rulesetInfo);
|
||||
|
||||
selectBeatmap(testBeatmap);
|
||||
|
||||
testBeatmapLabels(instance);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestWedgeVisibility()
|
||||
{
|
||||
AddStep("hide", () => { infoWedge.Hide(); });
|
||||
AddWaitStep("wait for hide", 3);
|
||||
AddAssert("check visibility", () => infoWedge.Alpha == 0);
|
||||
AddStep("show", () => { infoWedge.Show(); });
|
||||
AddWaitStep("wait for show", 1);
|
||||
AddAssert("check visibility", () => infoWedge.Alpha > 0);
|
||||
}
|
||||
|
||||
private void testBeatmapLabels(Ruleset ruleset)
|
||||
{
|
||||
AddAssert("check title", () => infoWedge.Info!.TitleLabel.Current.Value == $"{ruleset.ShortName}Title");
|
||||
AddAssert("check artist", () => infoWedge.Info!.ArtistLabel.Current.Value == $"{ruleset.ShortName}Artist");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTruncation()
|
||||
{
|
||||
selectBeatmap(createLongMetadata());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNullBeatmapWithBackground()
|
||||
{
|
||||
selectBeatmap(null);
|
||||
AddAssert("check default title", () => infoWedge.Info!.TitleLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Title);
|
||||
AddAssert("check default artist", () => infoWedge.Info!.ArtistLabel.Current.Value == Beatmap.Default.BeatmapInfo.Metadata.Artist);
|
||||
AddAssert("check no info labels", () => !infoWedge.Info.ChildrenOfType<BeatmapInfoWedge.WedgeInfoText.InfoLabel>().Any());
|
||||
}
|
||||
|
||||
private void setRuleset(RulesetInfo rulesetInfo)
|
||||
{
|
||||
Container? containerBefore = null;
|
||||
|
||||
AddStep("set ruleset", () =>
|
||||
{
|
||||
// wedge content is only refreshed if the ruleset changes, so only wait for load in that case.
|
||||
if (!rulesetInfo.Equals(Ruleset.Value))
|
||||
containerBefore = infoWedge.DisplayedContent;
|
||||
|
||||
Ruleset.Value = rulesetInfo;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||
}
|
||||
|
||||
private void selectBeatmap(IBeatmap? b)
|
||||
{
|
||||
Container? containerBefore = null;
|
||||
|
||||
AddStep($"select {b?.Metadata.Title ?? "null"} beatmap", () =>
|
||||
{
|
||||
containerBefore = infoWedge.DisplayedContent;
|
||||
infoWedge.Beatmap = Beatmap.Value = b == null ? Beatmap.Default : CreateWorkingBeatmap(b);
|
||||
infoWedge.Show();
|
||||
});
|
||||
|
||||
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
|
||||
}
|
||||
|
||||
private IBeatmap createTestBeatmap(RulesetInfo ruleset)
|
||||
{
|
||||
List<HitObject> objects = new List<HitObject>();
|
||||
for (double i = 0; i < 50000; i += 1000)
|
||||
objects.Add(new TestHitObject { StartTime = i });
|
||||
|
||||
return new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Author = { Username = $"{ruleset.ShortName}Author" },
|
||||
Artist = $"{ruleset.ShortName}Artist",
|
||||
Source = $"{ruleset.ShortName}Source",
|
||||
Title = $"{ruleset.ShortName}Title"
|
||||
},
|
||||
Ruleset = ruleset,
|
||||
StarRating = 6,
|
||||
DifficultyName = $"{ruleset.ShortName}Version",
|
||||
Difficulty = new BeatmapDifficulty()
|
||||
},
|
||||
HitObjects = objects
|
||||
};
|
||||
}
|
||||
|
||||
private IBeatmap createLongMetadata()
|
||||
{
|
||||
return new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Author = { Username = "WWWWWWWWWWWWWWW" },
|
||||
Artist = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Artist",
|
||||
Source = "Verrrrry long Source",
|
||||
Title = "Verrrrry long Title"
|
||||
},
|
||||
DifficultyName = "Verrrrrrrrrrrrrrrrrrrrrrrrrrrrry long Version",
|
||||
Status = BeatmapOnlineStatus.Graveyard,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private partial class TestBeatmapInfoWedgeV2 : BeatmapInfoWedgeV2
|
||||
{
|
||||
public new Container? DisplayedContent => base.DisplayedContent;
|
||||
public new WedgeInfoText? Info => base.Info;
|
||||
}
|
||||
|
||||
private class TestHitObject : ConvertHitObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK.Input;
|
||||
using Realms;
|
||||
using CollectionDropdown = osu.Game.Screens.SelectV2.CollectionDropdown;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneCollectionDropdown : OsuManualInputManagerTestScene
|
||||
{
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
private CollectionDropdown dropdown = null!;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(GameHost host)
|
||||
{
|
||||
Dependencies.Cache(new RealmRulesetStore(Realm));
|
||||
Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default));
|
||||
Dependencies.Cache(Realm);
|
||||
|
||||
beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
writeAndRefresh(r => r.RemoveAll<BeatmapCollection>());
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = dropdown = new CollectionDropdown
|
||||
{
|
||||
Width = 300,
|
||||
Y = 100,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestEmptyCollectionFilterContainsAllBeatmaps()
|
||||
{
|
||||
assertCollectionDropdownContains("All beatmaps");
|
||||
assertCollectionHeaderDisplays("All beatmaps");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionAddedToDropdown()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionsCleared()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "3"))));
|
||||
|
||||
AddAssert("check count 5", () => dropdown.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(5));
|
||||
|
||||
AddStep("delete all collections", () => writeAndRefresh(r => r.RemoveAll<BeatmapCollection>()));
|
||||
|
||||
AddAssert("check count 2", () => dropdown.ChildrenOfType<CollectionDropdown>().Single().ChildrenOfType<Menu.DrawableMenuItem>().Count(), () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRemovedFromDropdown()
|
||||
{
|
||||
BeatmapCollection first = null!;
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(first = new BeatmapCollection(name: "1"))));
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2"))));
|
||||
AddStep("remove collection", () => writeAndRefresh(r => r.Remove(first)));
|
||||
|
||||
assertCollectionDropdownContains("1", false);
|
||||
assertCollectionDropdownContains("2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionRenamed()
|
||||
{
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
AddStep("select collection", () => dropdown.Current.Value = dropdown.ItemSource.ElementAt(1));
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("change name", () => writeAndRefresh(_ => getFirstCollection().Name = "First"));
|
||||
|
||||
assertCollectionDropdownContains("First");
|
||||
assertCollectionHeaderDisplays("First");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllBeatmapFilterDoesNotHaveAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("hover all beatmaps", () => InputManager.MoveMouseTo(getAddOrRemoveButton(0)));
|
||||
AddAssert("'All beatmaps' filter does not have add button", () => !getAddOrRemoveButton(0).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionFilterHasAddButton()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1)));
|
||||
AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonDisabledAndEnabledWithBeatmapChanges()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value);
|
||||
|
||||
AddStep("set dummy beatmap", () => Beatmap.SetDefault());
|
||||
AddAssert("button disabled", () => !getAddOrRemoveButton(1).Enabled.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonChangesWhenAddedAndRemovedFromCollection()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
|
||||
AddStep("add beatmap to collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)));
|
||||
assertFirstButtonIs(FontAwesome.Solid.MinusSquare);
|
||||
|
||||
AddStep("remove beatmap from collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Clear()));
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestButtonAddsAndRemovesBeatmap()
|
||||
{
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0]));
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1"))));
|
||||
assertCollectionDropdownContains("1");
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection contains beatmap", () => getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash));
|
||||
assertFirstButtonIs(FontAwesome.Solid.MinusSquare);
|
||||
|
||||
addClickAddOrRemoveButtonStep(1);
|
||||
AddAssert("collection does not contain beatmap", () => !getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash));
|
||||
assertFirstButtonIs(FontAwesome.Solid.PlusSquare);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManageCollectionsFilterIsNotSelected()
|
||||
{
|
||||
bool received = false;
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1", new List<string> { "abc" }))));
|
||||
assertCollectionDropdownContains("1");
|
||||
|
||||
AddStep("select collection", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(1));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
addExpandHeaderStep();
|
||||
|
||||
AddStep("watch for filter requests", () =>
|
||||
{
|
||||
received = false;
|
||||
dropdown.ChildrenOfType<CollectionDropdown>().First().RequestFilter = () => received = true;
|
||||
});
|
||||
|
||||
AddStep("click manage collections filter", () =>
|
||||
{
|
||||
int lastItemIndex = dropdown.ChildrenOfType<CollectionDropdown>().Single().Items.Count() - 1;
|
||||
InputManager.MoveMouseTo(getCollectionDropdownItemAt(lastItemIndex));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("collection filter still selected", () => dropdown.Current.Value.CollectionName == "1");
|
||||
|
||||
AddAssert("filter request not fired", () => !received);
|
||||
}
|
||||
|
||||
private void writeAndRefresh(Action<Realm> action) => Realm.Write(r =>
|
||||
{
|
||||
action(r);
|
||||
r.Refresh();
|
||||
});
|
||||
|
||||
private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All<BeatmapCollection>().First());
|
||||
|
||||
private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true)
|
||||
=> AddUntilStep($"collection dropdown header displays '{collectionName}'",
|
||||
() => shouldDisplay == dropdown.ChildrenOfType<CollectionDropdown.ShearedDropdownHeader>().Any(h => h.ChildrenOfType<SpriteText>().Any(t => t.Text == collectionName)));
|
||||
|
||||
private void assertFirstButtonIs(IconUsage icon) => AddUntilStep($"button is {icon.Icon.ToString()}", () => getAddOrRemoveButton(1).Icon.Equals(icon));
|
||||
|
||||
private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) =>
|
||||
AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'",
|
||||
// A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872
|
||||
() => shouldContain == dropdown.ChildrenOfType<Menu.DrawableMenuItem>().Any(i => i.ChildrenOfType<CompositeDrawable>().OfType<IHasText>().First().Text == collectionName));
|
||||
|
||||
private IconButton getAddOrRemoveButton(int index)
|
||||
=> getCollectionDropdownItemAt(index).ChildrenOfType<IconButton>().Single();
|
||||
|
||||
private void addExpandHeaderStep() => AddStep("expand header", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(dropdown.ChildrenOfType<CollectionDropdown.ShearedDropdownHeader>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private void addClickAddOrRemoveButtonStep(int index) => AddStep("click add or remove button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(getAddOrRemoveButton(index));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
private Menu.DrawableMenuItem getCollectionDropdownItemAt(int index)
|
||||
{
|
||||
// todo: we should be able to use Items, but apparently that's not guaranteed to be ordered... see: https://github.com/ppy/osu-framework/pull/6079
|
||||
CollectionFilterMenuItem item = dropdown.ChildrenOfType<CollectionDropdown>().Single().ItemSource.ElementAt(index);
|
||||
return dropdown.ChildrenOfType<Menu.DrawableMenuItem>().Single(i => i.Item.Text.Value == item.CollectionName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Screens.SelectV2.Wedge;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneDifficultyNameContent : SongSelectComponentsTestScene
|
||||
{
|
||||
private DifficultyNameContent? difficultyNameContent;
|
||||
|
||||
[Test]
|
||||
public void TestLocalBeatmap()
|
||||
{
|
||||
AddStep("set component", () => Child = difficultyNameContent = new LocalDifficultyNameContent());
|
||||
|
||||
AddAssert("difficulty name is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||
AddAssert("author is not set", () => LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||
|
||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||
{
|
||||
BeatmapInfo = new BeatmapInfo
|
||||
{
|
||||
DifficultyName = "really long difficulty name that gets truncated",
|
||||
Metadata = new BeatmapMetadata
|
||||
{
|
||||
Author = { Username = "really long username that is autosized" },
|
||||
},
|
||||
OnlineID = 1,
|
||||
}
|
||||
}));
|
||||
|
||||
AddAssert("difficulty name is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<TruncatingSpriteText>().Single().Text));
|
||||
AddAssert("author is set", () => !LocalisableString.IsNullOrEmpty(difficultyNameContent.ChildrenOfType<OsuHoverContainer>().Single().ChildrenOfType<OsuSpriteText>().Single().Text));
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -13,19 +13,19 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneScreenFooterButtonMods : OsuTestScene
|
||||
public partial class TestSceneFooterButtonMods : OsuTestScene
|
||||
{
|
||||
private readonly TestScreenFooterButtonMods footerButtonMods;
|
||||
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
public TestSceneScreenFooterButtonMods()
|
||||
public TestSceneFooterButtonMods()
|
||||
{
|
||||
Add(footerButtonMods = new TestScreenFooterButtonMods(new TestModSelectOverlay())
|
||||
{
|
||||
@@ -98,9 +98,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public void TestUnrankedBadge()
|
||||
{
|
||||
AddStep(@"Add unranked mod", () => changeMods(new[] { new OsuModDeflate() }));
|
||||
AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType<ScreenFooterButtonMods.UnrankedBadge>().Single().Alpha == 1);
|
||||
AddUntilStep("Unranked badge shown", () => footerButtonMods.ChildrenOfType<FooterButtonMods.UnrankedBadge>().Single().Alpha == 1);
|
||||
AddStep(@"Clear selected mod", () => changeMods(Array.Empty<Mod>()));
|
||||
AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType<ScreenFooterButtonMods.UnrankedBadge>().Single().Alpha == 0);
|
||||
AddUntilStep("Unranked badge not shown", () => footerButtonMods.ChildrenOfType<FooterButtonMods.UnrankedBadge>().Single().Alpha == 0);
|
||||
}
|
||||
|
||||
private void changeMods(IReadOnlyList<Mod> mods) => footerButtonMods.Current.Value = mods;
|
||||
@@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
}
|
||||
|
||||
private partial class TestScreenFooterButtonMods : ScreenFooterButtonMods
|
||||
private partial class TestScreenFooterButtonMods : FooterButtonMods
|
||||
{
|
||||
public new OsuSpriteText MultiplierText => base.MultiplierText;
|
||||
|
||||
@@ -20,7 +20,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.SelectV2.Leaderboards;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
@@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, 2f),
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0)
|
||||
Shear = OsuGame.SHEAR,
|
||||
},
|
||||
drawWidthText = new OsuSpriteText(),
|
||||
};
|
||||
|
||||
foreach (var scoreInfo in getTestScores())
|
||||
{
|
||||
fillFlow.Add(new LeaderboardScoreV2(scoreInfo)
|
||||
fillFlow.Add(new BeatmapLeaderboardScore(scoreInfo)
|
||||
{
|
||||
Rank = scoreInfo.Position,
|
||||
IsPersonalBest = scoreInfo.User.Id == 2,
|
||||
@@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
foreach (var scoreInfo in getTestScores())
|
||||
{
|
||||
fillFlow.Add(new LeaderboardScoreV2(scoreInfo)
|
||||
fillFlow.Add(new BeatmapLeaderboardScore(scoreInfo)
|
||||
{
|
||||
Rank = scoreInfo.Position,
|
||||
IsPersonalBest = scoreInfo.User.Id == 2,
|
||||
@@ -108,7 +108,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Test]
|
||||
public void TestUseTheseModsDoesNotCopySystemMods()
|
||||
{
|
||||
LeaderboardScoreV2 score = null!;
|
||||
BeatmapLeaderboardScore score = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
@@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, 2f),
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0)
|
||||
Shear = OsuGame.SHEAR,
|
||||
},
|
||||
drawWidthText = new OsuSpriteText(),
|
||||
};
|
||||
@@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Date = DateTimeOffset.Now.AddYears(-2),
|
||||
};
|
||||
|
||||
fillFlow.Add(score = new LeaderboardScoreV2(scoreInfo)
|
||||
fillFlow.Add(score = new BeatmapLeaderboardScore(scoreInfo)
|
||||
{
|
||||
Rank = scoreInfo.Position,
|
||||
Shear = Vector2.Zero,
|
||||
|
||||
+22
-2
@@ -1,16 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
@@ -18,14 +25,14 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2DifficultyPanel : ThemeComparisonTestScene
|
||||
public partial class TestScenePanelBeatmap : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapInfo beatmap = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2DifficultyPanel()
|
||||
public TestScenePanelBeatmap()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
@@ -65,6 +72,19 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddToggleStep("mania ruleset", v => Ruleset.Value = v ? new ManiaRuleset().RulesetInfo : new OsuRuleset().RulesetInfo);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalRank()
|
||||
{
|
||||
foreach (var rank in Enum.GetValues<ScoreRank>())
|
||||
{
|
||||
AddStep($"set {rank.GetDescription()} rank", () => this.ChildrenOfType<UpdateableRank>().ForEach(p =>
|
||||
{
|
||||
p.Show();
|
||||
p.Rank = rank;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
+22
-2
@@ -1,16 +1,23 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Online.Leaderboards;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
@@ -18,14 +25,14 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2StandalonePanel : ThemeComparisonTestScene
|
||||
public partial class TestScenePanelBeatmapStandalone : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapInfo beatmap = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2StandalonePanel()
|
||||
public TestScenePanelBeatmapStandalone()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
@@ -65,6 +72,19 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddToggleStep("mania ruleset", v => Ruleset.Value = v ? new ManiaRuleset().RulesetInfo : new OsuRuleset().RulesetInfo);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalRank()
|
||||
{
|
||||
foreach (var rank in Enum.GetValues<ScoreRank>())
|
||||
{
|
||||
AddStep($"set {rank.GetDescription()} rank", () => this.ChildrenOfType<UpdateableRank>().ForEach(p =>
|
||||
{
|
||||
p.Show();
|
||||
p.Rank = rank;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Visual.UserInterface;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestScenePanelGroup : ThemeComparisonTestScene
|
||||
{
|
||||
public TestScenePanelGroup()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGeneral()
|
||||
{
|
||||
AddStep("general", () => CreateThemedContent(OverlayColourScheme.Aquamarine));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStars()
|
||||
{
|
||||
for (int i = 0; i <= 10; i++)
|
||||
{
|
||||
int star = i;
|
||||
|
||||
AddStep($"display {i} star(s)", () =>
|
||||
{
|
||||
ContentContainer.Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[]
|
||||
{
|
||||
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Aquamarine))
|
||||
},
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new[]
|
||||
{
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString()))
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString())),
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString())),
|
||||
Expanded = { Value = true },
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(star, star.ToString())),
|
||||
Expanded = { Value = true },
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent()
|
||||
{
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A"))
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
new PanelGroup
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition('A', "Group A")),
|
||||
KeyboardSelected = { Value = true },
|
||||
Expanded = { Value = true }
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-2
@@ -8,6 +8,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
@@ -16,14 +17,14 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneBeatmapCarouselV2SetPanel : ThemeComparisonTestScene
|
||||
public partial class TestScenePanelSet : ThemeComparisonTestScene
|
||||
{
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; } = null!;
|
||||
|
||||
private BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
public TestSceneBeatmapCarouselV2SetPanel()
|
||||
public TestScenePanelSet()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user