mirror of
https://github.com/ppy/osu.git
synced 2026-05-19 00:30:19 +08:00
Compare commits
277 Commits
+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:
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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()
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
@@ -85,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",
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -443,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);
|
||||
@@ -484,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);
|
||||
@@ -525,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);
|
||||
@@ -657,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);
|
||||
|
||||
@@ -828,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]
|
||||
@@ -1059,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();
|
||||
|
||||
@@ -8,6 +8,7 @@ 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;
|
||||
@@ -186,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);
|
||||
}
|
||||
@@ -212,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]
|
||||
@@ -235,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]
|
||||
@@ -307,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));
|
||||
}
|
||||
|
||||
@@ -392,6 +393,39 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
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
|
||||
{
|
||||
[Resolved(canBeNull: true)]
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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 },
|
||||
|
||||
+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,6 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Overlays;
|
||||
@@ -20,7 +19,6 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
};
|
||||
|
||||
private Container? resizeContainer;
|
||||
@@ -33,15 +31,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(10),
|
||||
Width = relativeWidth,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background5,
|
||||
},
|
||||
Content
|
||||
}
|
||||
};
|
||||
@@ -55,6 +47,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;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
+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.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -18,14 +19,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)
|
||||
{
|
||||
}
|
||||
+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.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
@@ -18,14 +19,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)
|
||||
{
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
+3
-3
@@ -9,14 +9,14 @@ using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneUpdateBeatmapSetButtonV2 : OsuTestScene
|
||||
public partial class TestScenePanelUpdateBeatmapButton : OsuTestScene
|
||||
{
|
||||
private UpdateBeatmapSetButton button = null!;
|
||||
private PanelUpdateBeatmapButton button = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = button = new UpdateBeatmapSetButton
|
||||
Child = button = new PanelUpdateBeatmapButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
+5
-5
@@ -15,9 +15,9 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
|
||||
{
|
||||
@@ -51,9 +51,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
screenFooter.SetButtons(new ScreenFooterButton[]
|
||||
{
|
||||
new ScreenFooterButtonMods(modOverlay) { Current = SelectedMods },
|
||||
new ScreenFooterButtonRandom(),
|
||||
new ScreenFooterButtonOptions(),
|
||||
new FooterButtonMods(modOverlay) { Current = SelectedMods },
|
||||
new FooterButtonRandom(),
|
||||
new FooterButtonOptions(),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ using osu.Game.Rulesets.Taiko;
|
||||
using osu.Game.Screens;
|
||||
using osu.Game.Screens.Footer;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.SelectV2.Footer;
|
||||
using osu.Game.Screens.SelectV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
@@ -78,7 +78,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("load screen", () => Stack.Push(new Screens.SelectV2.SoloSongSelect()));
|
||||
AddStep("load screen", () => Stack.Push(new SoloSongSelect()));
|
||||
AddUntilStep("wait for load", () => Stack.CurrentScreen is Screens.SelectV2.SongSelect songSelect && songSelect.IsLoaded);
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
AddStep("Press F1", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ScreenFooterButtonMods>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonMods>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("Overlay visible", () => this.ChildrenOfType<ModSelectOverlay>().Single().State.Value == Visibility.Visible);
|
||||
|
||||
@@ -8,15 +8,12 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays.Music;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
@@ -24,8 +21,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
protected override bool UseFreshStoragePerRun => true;
|
||||
|
||||
private PlaylistOverlay playlistOverlay = null!;
|
||||
|
||||
private BeatmapManager beatmapManager = null!;
|
||||
|
||||
private const int item_count = 20;
|
||||
@@ -48,7 +43,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(300, 500),
|
||||
Child = playlistOverlay = new PlaylistOverlay
|
||||
Child = new PlaylistOverlay
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -67,116 +62,5 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
// Ensure all the initial imports are present before running any tests.
|
||||
Realm.Run(r => r.Refresh());
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestRearrangeItems()
|
||||
{
|
||||
AddUntilStep("wait for load complete", () =>
|
||||
{
|
||||
return this
|
||||
.ChildrenOfType<PlaylistItem>()
|
||||
.Count(i => i.ChildrenOfType<DelayedLoadWrapper>().First().DelayedLoadCompleted) > 6;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for animations to complete", () => !playlistOverlay.Transforms.Any());
|
||||
|
||||
PlaylistItem firstItem = null!;
|
||||
|
||||
AddStep("hold 1st item handle", () =>
|
||||
{
|
||||
firstItem = this.ChildrenOfType<PlaylistItem>().First();
|
||||
var handle = firstItem.ChildrenOfType<PlaylistItem.PlaylistItemHandle>().First();
|
||||
|
||||
InputManager.MoveMouseTo(handle.ScreenSpaceDrawQuad.Centre);
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddStep("drag to 5th", () =>
|
||||
{
|
||||
var item = this.ChildrenOfType<PlaylistItem>().ElementAt(4);
|
||||
InputManager.MoveMouseTo(item.ScreenSpaceDrawQuad.BottomLeft);
|
||||
});
|
||||
|
||||
AddAssert("first is moved", () => playlistOverlay.ChildrenOfType<Playlist>().Single().Items.ElementAt(4).Value.Equals(firstItem.Model.Value));
|
||||
|
||||
AddStep("release handle", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFiltering()
|
||||
{
|
||||
AddStep("set filter to \"10\"", () =>
|
||||
{
|
||||
var filterControl = playlistOverlay.ChildrenOfType<FilterControl>().Single();
|
||||
filterControl.Search.Current.Value = "10";
|
||||
});
|
||||
|
||||
AddAssert("results filtered correctly",
|
||||
() => playlistOverlay.ChildrenOfType<PlaylistItem>()
|
||||
.Where(item => item.MatchingFilter)
|
||||
.All(item => item.FilterTerms.Any(term => term.ToString().Contains("10"))));
|
||||
|
||||
AddStep("Import new non-matching beatmap", () =>
|
||||
{
|
||||
var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
testBeatmapSetInfo.Beatmaps.Single().Metadata.Title = "no guid";
|
||||
beatmapManager.Import(testBeatmapSetInfo);
|
||||
});
|
||||
|
||||
AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh()));
|
||||
|
||||
AddAssert("results filtered correctly",
|
||||
() => playlistOverlay.ChildrenOfType<PlaylistItem>()
|
||||
.Where(item => item.MatchingFilter)
|
||||
.All(item => item.FilterTerms.Any(term => term.ToString().Contains("10"))));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCollectionFiltering()
|
||||
{
|
||||
NowPlayingCollectionDropdown collectionDropdown() => playlistOverlay.ChildrenOfType<NowPlayingCollectionDropdown>().Single();
|
||||
|
||||
AddStep("Add collection", () =>
|
||||
{
|
||||
Realm.Write(r =>
|
||||
{
|
||||
r.RemoveAll<BeatmapCollection>();
|
||||
r.Add(new BeatmapCollection("wang"));
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for dropdown to have new collection", () => collectionDropdown().Items.Count() == 2);
|
||||
|
||||
AddStep("Filter to collection", () =>
|
||||
{
|
||||
collectionDropdown().Current.Value = collectionDropdown().Items.Last();
|
||||
});
|
||||
|
||||
AddUntilStep("No items present", () => !playlistOverlay.ChildrenOfType<PlaylistItem>().Any(i => i.MatchingFilter));
|
||||
|
||||
AddStep("Import new non-matching beatmap", () =>
|
||||
{
|
||||
beatmapManager.Import(TestResources.CreateTestBeatmapSetInfo(1));
|
||||
});
|
||||
|
||||
AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh()));
|
||||
|
||||
AddUntilStep("No items matching", () => !playlistOverlay.ChildrenOfType<PlaylistItem>().Any(i => i.MatchingFilter));
|
||||
|
||||
BeatmapSetInfo collectionAddedBeatmapSet = null!;
|
||||
|
||||
AddStep("Import new matching beatmap", () =>
|
||||
{
|
||||
collectionAddedBeatmapSet = TestResources.CreateTestBeatmapSetInfo(1);
|
||||
|
||||
beatmapManager.Import(collectionAddedBeatmapSet);
|
||||
Realm.Write(r => r.All<BeatmapCollection>().First().BeatmapMD5Hashes.Add(collectionAddedBeatmapSet.Beatmaps.First().MD5Hash));
|
||||
});
|
||||
|
||||
AddStep("Force realm refresh", () => Realm.Run(r => r.Refresh()));
|
||||
|
||||
AddUntilStep("Only matching item",
|
||||
() => playlistOverlay.ChildrenOfType<PlaylistItem>().Where(i => i.MatchingFilter).Select(i => i.Model.ID), () => Is.EquivalentTo(new[] { collectionAddedBeatmapSet.ID }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace osu.Game.Tournament.Models
|
||||
{
|
||||
public int ID;
|
||||
|
||||
[JsonIgnore]
|
||||
public List<string> Acronyms
|
||||
{
|
||||
get
|
||||
|
||||
@@ -53,6 +53,14 @@ namespace osu.Game.Tournament
|
||||
return new ProductionEndpointConfiguration();
|
||||
}
|
||||
|
||||
public override void SetHost(GameHost host)
|
||||
{
|
||||
base.SetHost(host);
|
||||
|
||||
if (host.Window != null)
|
||||
host.Window.Title = $"{Name} [tournament client]";
|
||||
}
|
||||
|
||||
private TournamentSpriteText initialisationText = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -14,10 +14,10 @@ namespace osu.Game.Beatmaps
|
||||
/// This is a special status given when local changes are made via the editor.
|
||||
/// Once in this state, online status changes should be ignored unless the beatmap is reverted or submitted.
|
||||
/// </summary>
|
||||
[Description("Local")]
|
||||
[LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.LocallyModified))]
|
||||
LocallyModified = -4,
|
||||
|
||||
[Description("Unknown")]
|
||||
None = -3,
|
||||
|
||||
[LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowStatusGraveyard))]
|
||||
|
||||
@@ -16,7 +16,19 @@ namespace osu.Game.Beatmaps
|
||||
/// </summary>
|
||||
public Func<Drawable> CreateIcon;
|
||||
|
||||
public string Content;
|
||||
/// <summary>
|
||||
/// The name of this statistic.
|
||||
/// </summary>
|
||||
public LocalisableString Name;
|
||||
|
||||
/// <summary>
|
||||
/// The text representing the value of this statistic.
|
||||
/// </summary>
|
||||
public string Content;
|
||||
|
||||
/// <summary>
|
||||
/// The length of a bar which visually represents this statistic's relevance in the beatmap.
|
||||
/// </summary>
|
||||
public float? BarDisplayLength;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,10 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
{
|
||||
public partial class BeatmapSetOnlineStatusPill : CircularContainer, IHasTooltip
|
||||
{
|
||||
private const double animation_duration = 400;
|
||||
|
||||
private BeatmapOnlineStatus status;
|
||||
/// <summary>
|
||||
/// Whether to show <see cref="BeatmapOnlineStatus.None"/> as "unknown" instead of fading out.
|
||||
/// </summary>
|
||||
public bool ShowUnknownStatus { get; init; }
|
||||
|
||||
public BeatmapOnlineStatus Status
|
||||
{
|
||||
@@ -34,30 +35,27 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
status = value;
|
||||
|
||||
if (IsLoaded)
|
||||
{
|
||||
AutoSizeDuration = (float)animation_duration;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapOnlineStatus status;
|
||||
|
||||
public float TextSize
|
||||
{
|
||||
get => statusText.Font.Size;
|
||||
set => statusText.Font = statusText.Font.With(size: value);
|
||||
init => statusText.Font = statusText.Font.With(size: value);
|
||||
}
|
||||
|
||||
public MarginPadding TextPadding
|
||||
{
|
||||
get => statusText.Padding;
|
||||
set => statusText.Padding = value;
|
||||
init => statusText.Padding = value;
|
||||
}
|
||||
|
||||
private readonly OsuSpriteText statusText;
|
||||
private readonly Box background;
|
||||
|
||||
private const double animation_duration = 400;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
@@ -66,6 +64,7 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
public BeatmapSetOnlineStatusPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Masking = true;
|
||||
|
||||
Alpha = 0;
|
||||
@@ -99,14 +98,27 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (Status == BeatmapOnlineStatus.None)
|
||||
if (Status == BeatmapOnlineStatus.None && !ShowUnknownStatus)
|
||||
{
|
||||
Hide();
|
||||
this.FadeOut(animation_duration, Easing.OutQuint);
|
||||
return;
|
||||
}
|
||||
|
||||
// The autosize animation on this component is intended to animate horizontal sizing only.
|
||||
// To avoid vertical autosize animating from zero to non-zero, only apply the duration
|
||||
// after we have a valid size.
|
||||
if (Height > 0)
|
||||
{
|
||||
AutoSizeDuration = (float)animation_duration;
|
||||
AutoSizeEasing = Easing.OutQuint;
|
||||
}
|
||||
|
||||
this.FadeIn(animation_duration, Easing.OutQuint);
|
||||
|
||||
// Handle the case where transition from hidden to non-hidden may cause
|
||||
// a fade from a colour that doesn't make sense (due to not being able to see the previous colour).
|
||||
double duration = Alpha > 0 ? animation_duration : 0;
|
||||
|
||||
Color4 statusTextColour;
|
||||
|
||||
if (colourProvider != null)
|
||||
@@ -114,8 +126,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
else
|
||||
statusTextColour = status == BeatmapOnlineStatus.Graveyard ? colours.GreySeaFoamLight : Color4.Black;
|
||||
|
||||
statusText.FadeColour(statusTextColour, animation_duration, Easing.OutQuint);
|
||||
background.FadeColour(OsuColour.ForBeatmapSetOnlineStatus(Status) ?? colourProvider?.Light1 ?? colours.GreySeaFoamLighter, animation_duration, Easing.OutQuint);
|
||||
statusText.FadeColour(statusTextColour, duration, Easing.OutQuint);
|
||||
background.FadeColour(OsuColour.ForBeatmapSetOnlineStatus(Status) ?? colourProvider?.Light1 ?? colours.GreySeaFoamLighter, duration, Easing.OutQuint);
|
||||
|
||||
statusText.Text = Status.GetLocalisableDescription().ToUpper();
|
||||
}
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Status = beatmapSet.Status,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
|
||||
@@ -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.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@@ -39,8 +38,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private readonly Bindable<double> displayedStars = new BindableDouble();
|
||||
|
||||
private readonly Container textContainer;
|
||||
|
||||
/// <summary>
|
||||
/// The currently displayed stars of this display wrapped in a bindable.
|
||||
/// This bindable gets transformed on change rather than instantaneous, if animation is enabled.
|
||||
@@ -119,19 +116,14 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
Size = new Vector2(8f),
|
||||
},
|
||||
Empty(),
|
||||
textContainer = new Container
|
||||
starsText = new OsuSpriteText
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Child = starsText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 1.5f },
|
||||
// todo: this should be size: 12f, but to match up with the design, it needs to be 14.4f
|
||||
// see https://github.com/ppy/osu-framework/issues/3271.
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold),
|
||||
Shadow = false,
|
||||
},
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Bottom = 1.5f },
|
||||
Spacing = new Vector2(-1.4f),
|
||||
Font = OsuFont.Torus.With(size: 14.4f, weight: FontWeight.Bold, fixedWidth: true),
|
||||
Shadow = false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -162,11 +154,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
starIcon.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
|
||||
starsText.Colour = s.NewValue >= 6.5 ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
|
||||
|
||||
// In order to avoid autosize throwing the width of these displays all over the place,
|
||||
// let's lock in some sane defaults for the text width based on how many digits we're
|
||||
// displaying.
|
||||
textContainer.Width = 24 + Math.Max(starsText.Text.ToString().Length - 4, 0) * 6;
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
/// </remarks>
|
||||
public const double CONTROL_POINT_LENIENCY = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum allowed number of keys in mania beatmaps.
|
||||
/// </summary>
|
||||
public const int MAX_MANIA_KEY_COUNT = 18;
|
||||
|
||||
internal static RulesetStore? RulesetStore;
|
||||
|
||||
private Beatmap beatmap = null!;
|
||||
@@ -116,7 +121,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
// mania uses "circle size" for key count, thus different allowable range
|
||||
difficulty.CircleSize = beatmap.BeatmapInfo.Ruleset.OnlineID != 3
|
||||
? Math.Clamp(difficulty.CircleSize, 0, 10)
|
||||
: Math.Clamp(difficulty.CircleSize, 1, 18);
|
||||
: Math.Clamp(difficulty.CircleSize, 1, MAX_MANIA_KEY_COUNT);
|
||||
|
||||
difficulty.OverallDifficulty = Math.Clamp(difficulty.OverallDifficulty, 0, 10);
|
||||
difficulty.ApproachRate = Math.Clamp(difficulty.ApproachRate, 0, 10);
|
||||
|
||||
@@ -213,12 +213,12 @@ namespace osu.Game.Beatmaps
|
||||
if (ae.InnerExceptions.FirstOrDefault() is TaskCanceledException)
|
||||
return null;
|
||||
|
||||
Logger.Error(ae, "Beatmap failed to load");
|
||||
Logger.Error(ae, $"Beatmap failed to load ({BeatmapInfo})");
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error(e, "Beatmap failed to load");
|
||||
Logger.Error(e, $"Beatmap failed to load ({BeatmapInfo})");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Configuration;
|
||||
@@ -263,10 +262,6 @@ namespace osu.Game.Configuration
|
||||
|
||||
public override TrackedSettings CreateTrackedSettings()
|
||||
{
|
||||
// these need to be assigned in normal game startup scenarios.
|
||||
Debug.Assert(LookupKeyBindings != null);
|
||||
Debug.Assert(LookupSkinName != null);
|
||||
|
||||
return new TrackedSettings
|
||||
{
|
||||
new TrackedSetting<bool>(OsuSetting.ShowFpsDisplay, state => new SettingDescription(
|
||||
@@ -330,8 +325,7 @@ namespace osu.Game.Configuration
|
||||
}
|
||||
|
||||
public Func<Guid, string> LookupSkinName { private get; set; } = _ => @"unknown";
|
||||
|
||||
public Func<GlobalAction, LocalisableString> LookupKeyBindings { get; set; } = _ => @"unknown";
|
||||
public Func<GlobalAction, LocalisableString> LookupKeyBindings { private get; set; } = _ => @"unknown";
|
||||
|
||||
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => GetOriginalBindable<float>(OsuSetting.ComboColourNormalisationAmount);
|
||||
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => GetOriginalBindable<float>(OsuSetting.PositionalHitsoundsLevel);
|
||||
|
||||
@@ -40,10 +40,10 @@ namespace osu.Game.Configuration
|
||||
if (newScore.Mods.Any(m => !m.UserPlayable || m is IHasNoTimedInputs))
|
||||
return;
|
||||
|
||||
if (newScore.HitEvents.Count < 10)
|
||||
if (newScore.HitEvents.Count < 50)
|
||||
return;
|
||||
|
||||
if (newScore.HitEvents.CalculateAverageHitError() is not double averageError)
|
||||
if (newScore.HitEvents.CalculateMedianHitError() is not double medianError)
|
||||
return;
|
||||
|
||||
// keep a sane maximum number of entries.
|
||||
@@ -51,7 +51,7 @@ namespace osu.Game.Configuration
|
||||
averageHitErrorHistory.RemoveAt(0);
|
||||
|
||||
double globalOffset = configManager.Get<double>(OsuSetting.AudioOffset);
|
||||
averageHitErrorHistory.Add(new DataPoint(averageError, globalOffset));
|
||||
averageHitErrorHistory.Add(new DataPoint(medianError, globalOffset));
|
||||
}
|
||||
|
||||
public void ClearHistory() => averageHitErrorHistory.Clear();
|
||||
|
||||
@@ -139,9 +139,14 @@ namespace osu.Game.Database
|
||||
notification.Progress = (float)current / tasks.Length;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
catch (OperationCanceledException cancelled)
|
||||
{
|
||||
throw;
|
||||
// We don't want to abort the full import process based off difficulty calculator's internal cancellation
|
||||
// see https://github.com/ppy/osu/blob/91f3be5feaab0c73c17e1a8c270516aa9bee1e14/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs#L65.
|
||||
if (cancelled.CancellationToken == notification.CancellationToken)
|
||||
throw;
|
||||
|
||||
Logger.Error(cancelled, $@"Timed out importing ({task})", LoggingTarget.Database);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace osu.Game.Database
|
||||
public override IBindableList<BeatmapSetInfo> GetBeatmapSets(CancellationToken? cancellationToken)
|
||||
{
|
||||
loaded.Wait(cancellationToken ?? CancellationToken.None);
|
||||
return detachedBeatmapSets.GetBoundCopy();
|
||||
lock (detachedBeatmapSets)
|
||||
return detachedBeatmapSets.GetBoundCopy();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -65,8 +66,11 @@ namespace osu.Game.Database
|
||||
{
|
||||
var detached = frozenSets.Detach();
|
||||
|
||||
detachedBeatmapSets.Clear();
|
||||
detachedBeatmapSets.AddRange(detached);
|
||||
lock (detachedBeatmapSets)
|
||||
{
|
||||
detachedBeatmapSets.Clear();
|
||||
detachedBeatmapSets.AddRange(detached);
|
||||
}
|
||||
});
|
||||
}
|
||||
finally
|
||||
@@ -116,22 +120,28 @@ namespace osu.Game.Database
|
||||
if (!loaded.IsSet)
|
||||
return;
|
||||
|
||||
// If this ever leads to performance issues, we could dequeue a limited number of operations per update frame.
|
||||
while (pendingOperations.TryDequeue(out var op))
|
||||
if (pendingOperations.Count == 0)
|
||||
return;
|
||||
|
||||
lock (detachedBeatmapSets)
|
||||
{
|
||||
switch (op.Type)
|
||||
// If this ever leads to performance issues, we could dequeue a limited number of operations per update frame.
|
||||
while (pendingOperations.TryDequeue(out var op))
|
||||
{
|
||||
case OperationType.Insert:
|
||||
detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!);
|
||||
break;
|
||||
switch (op.Type)
|
||||
{
|
||||
case OperationType.Insert:
|
||||
detachedBeatmapSets.Insert(op.Index, op.BeatmapSet!);
|
||||
break;
|
||||
|
||||
case OperationType.Update:
|
||||
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
|
||||
break;
|
||||
case OperationType.Update:
|
||||
detachedBeatmapSets.ReplaceRange(op.Index, 1, new[] { op.BeatmapSet! });
|
||||
break;
|
||||
|
||||
case OperationType.Remove:
|
||||
detachedBeatmapSets.RemoveAt(op.Index);
|
||||
break;
|
||||
case OperationType.Remove:
|
||||
detachedBeatmapSets.RemoveAt(op.Index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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.Globalization;
|
||||
using System.Numerics;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Extensions
|
||||
{
|
||||
public static class NumberFormattingExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// For a given numeric type, return a formatted string in the standard format we use for display everywhere.
|
||||
/// </summary>
|
||||
/// <param name="value">The numeric value.</param>
|
||||
/// <param name="maxDecimalDigits">The maximum number of decimals to be considered in the original value.</param>
|
||||
/// <param name="asPercentage">Whether the output should be a percentage. For integer types, 0-100 is mapped to 0-100%; for other types 0-1 is mapped to 0-100%.</param>
|
||||
/// <returns>The formatted output.</returns>
|
||||
public static string ToStandardFormattedString<T>(this T value, int maxDecimalDigits, bool asPercentage) where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
{
|
||||
double floatValue = double.CreateTruncating(value);
|
||||
|
||||
decimal decimalPrecision = normalise(decimal.CreateTruncating(value), maxDecimalDigits);
|
||||
|
||||
// Find the number of significant digits (we could have less than maxDecimalDigits after normalize())
|
||||
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
|
||||
|
||||
if (asPercentage)
|
||||
{
|
||||
if (value is int)
|
||||
floatValue /= 100;
|
||||
|
||||
return floatValue.ToString($@"0.{new string('0', Math.Max(0, significantDigits - 2))}%", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
string negativeSign = Math.Round(floatValue, significantDigits) < 0 ? "-" : string.Empty;
|
||||
|
||||
return FormattableString.Invariant($"{negativeSign}{Math.Abs(floatValue).ToString($"N{significantDigits}")}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all non-significant digits, keeping at most a requested number of decimal digits.
|
||||
/// </summary>
|
||||
/// <param name="d">The decimal to normalize.</param>
|
||||
/// <param name="sd">The maximum number of decimal digits to keep. The final result may have fewer decimal digits than this value.</param>
|
||||
/// <returns>The normalised decimal.</returns>
|
||||
private static decimal normalise(decimal d, int sd)
|
||||
=> decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ using osu.Game.Input.Bindings;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// A highly efficient vertical list display that is used primarily for the song select screen,
|
||||
@@ -38,12 +38,12 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// <summary>
|
||||
/// Height of the area above the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||
/// </summary>
|
||||
public float BleedTop { get; set; } = 0;
|
||||
public float BleedTop { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Height of the area below the carousel that should be treated as visible due to transparency of elements in front of it.
|
||||
/// </summary>
|
||||
public float BleedBottom { get; set; } = 0;
|
||||
public float BleedBottom { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The number of pixels outside the carousel's vertical bounds to manifest drawables.
|
||||
@@ -228,6 +228,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
InternalChild = Scroll = new CarouselScrollContainer
|
||||
{
|
||||
Masking = false,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
@@ -505,7 +506,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
private void scrollToSelection()
|
||||
{
|
||||
if (currentKeyboardSelection.CarouselItem != null)
|
||||
Scroll.ScrollTo(currentKeyboardSelection.CarouselItem.CarouselYPosition - visibleHalfHeight);
|
||||
Scroll.ScrollTo(currentKeyboardSelection.CarouselItem.CarouselYPosition - visibleHalfHeight + BleedTop);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -519,17 +520,17 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// <summary>
|
||||
/// The position of the lower visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleBottomBound => (float)(Scroll.Current + DrawHeight + BleedBottom);
|
||||
private float visibleBottomBound;
|
||||
|
||||
/// <summary>
|
||||
/// The position of the upper visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleUpperBound => (float)(Scroll.Current - BleedTop);
|
||||
private float visibleUpperBound;
|
||||
|
||||
/// <summary>
|
||||
/// Half the height of the visible content.
|
||||
/// </summary>
|
||||
private float visibleHalfHeight => (DrawHeight + BleedBottom + BleedTop) / 2;
|
||||
private float visibleHalfHeight;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@@ -538,6 +539,10 @@ namespace osu.Game.Screens.SelectV2
|
||||
if (carouselItems == null)
|
||||
return;
|
||||
|
||||
visibleBottomBound = (float)(Scroll.Current + DrawHeight + BleedBottom);
|
||||
visibleUpperBound = (float)(Scroll.Current - BleedTop);
|
||||
visibleHalfHeight = (DrawHeight + BleedBottom + BleedTop) / 2;
|
||||
|
||||
if (!selectionValid.IsValid)
|
||||
{
|
||||
refreshAfterSelection();
|
||||
@@ -582,7 +587,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
protected virtual float GetPanelXOffset(Drawable panel)
|
||||
{
|
||||
Vector2 posInScroll = Scroll.ToLocalSpace(panel.ScreenSpaceDrawQuad.Centre);
|
||||
float dist = Math.Abs(1f - posInScroll.Y / visibleHalfHeight);
|
||||
float dist = Math.Abs(1f - (posInScroll.Y + BleedTop) / visibleHalfHeight);
|
||||
|
||||
return offsetX(dist, visibleHalfHeight);
|
||||
}
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single display item for display in a <see cref="Carousel{T}"/>.
|
||||
+1
-1
@@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface representing a filter operation which can be run on a <see cref="Carousel{T}"/>.
|
||||
+1
-1
@@ -5,7 +5,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
namespace osu.Game.Graphics.Carousel
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface to be attached to any <see cref="Drawable"/>s which are used for display inside a <see cref="Carousel{T}"/>.
|
||||
@@ -134,7 +134,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
protected virtual DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new DrawableLinkCompiler(textPart);
|
||||
|
||||
protected override FillFlowContainer CreateFlow() => new LinkFlow();
|
||||
protected override InnerFlow CreateFlow() => new LinkFlow();
|
||||
|
||||
private partial class LinkFlow : InnerFlow
|
||||
{
|
||||
|
||||
@@ -20,10 +20,7 @@ namespace osu.Game.Graphics
|
||||
public static Color4 Gray(float amt) => new Color4(amt, amt, amt, 1f);
|
||||
public static Color4 Gray(byte amt) => new Color4(amt, amt, amt, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a given point in the star range.
|
||||
/// </summary>
|
||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[]
|
||||
public static readonly (float, Color4)[] STAR_DIFFICULTY_SPECTRUM =
|
||||
{
|
||||
(0.1f, Color4Extensions.FromHex("aaaaaa")),
|
||||
(0.1f, Color4Extensions.FromHex("4290fb")),
|
||||
@@ -37,7 +34,13 @@ namespace osu.Game.Graphics
|
||||
(6.7f, Color4Extensions.FromHex("6563de")),
|
||||
(7.7f, Color4Extensions.FromHex("18158e")),
|
||||
(9.0f, Color4.Black),
|
||||
}, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||
(10.0f, Color4.Black),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a given point in the star range.
|
||||
/// </summary>
|
||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(STAR_DIFFICULTY_SPECTRUM, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a <see cref="ScoreRank"/>.
|
||||
@@ -120,6 +123,9 @@ namespace osu.Game.Graphics
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case BeatmapOnlineStatus.None:
|
||||
return Color4.RosyBrown;
|
||||
|
||||
case BeatmapOnlineStatus.LocallyModified:
|
||||
return Color4.OrangeRed;
|
||||
|
||||
@@ -403,6 +409,12 @@ namespace osu.Game.Graphics
|
||||
public readonly Color4 Orange3 = Color4Extensions.FromHex(@"cca633");
|
||||
public readonly Color4 Orange4 = Color4Extensions.FromHex(@"6b5c2e");
|
||||
|
||||
public readonly Color4 DarkOrange0 = Color4Extensions.FromHex(@"ffbb99");
|
||||
public readonly Color4 DarkOrange1 = Color4Extensions.FromHex(@"ff9966");
|
||||
public readonly Color4 DarkOrange2 = Color4Extensions.FromHex(@"eb7e47");
|
||||
public readonly Color4 DarkOrange3 = Color4Extensions.FromHex(@"cc6633");
|
||||
public readonly Color4 DarkOrange4 = Color4Extensions.FromHex(@"6b422e");
|
||||
|
||||
public readonly Color4 Red0 = Color4Extensions.FromHex(@"ff9b9b");
|
||||
public readonly Color4 Red1 = Color4Extensions.FromHex(@"ff6666");
|
||||
public readonly Color4 Red2 = Color4Extensions.FromHex(@"eb4747");
|
||||
|
||||
@@ -15,15 +15,65 @@ namespace osu.Game.Graphics
|
||||
/// </summary>
|
||||
public const float DEFAULT_FONT_SIZE = 16;
|
||||
|
||||
/// <summary>
|
||||
/// Template font styles which should be preferred whenever possible for UI elements.
|
||||
/// </summary>
|
||||
public static class Style
|
||||
{
|
||||
/// <summary>
|
||||
/// Equivalent to Torus with 32px size and semi-bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Title => GetFont(Typeface.TorusAlternate, size: 32, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 28px size and semi-bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Subtitle => GetFont(size: 28, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 22px size and bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Heading1 => GetFont(size: 22, weight: FontWeight.Bold);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 18px size and semi-bold weight.
|
||||
/// </summary>
|
||||
public static FontUsage Heading2 => GetFont(size: 18, weight: FontWeight.SemiBold);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 16px size and regular weight.
|
||||
/// </summary>
|
||||
public static FontUsage Body => GetFont(size: DEFAULT_FONT_SIZE, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 14px size and regular weight.
|
||||
/// </summary>
|
||||
public static FontUsage Caption1 => GetFont(size: 14, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Torus with 12px size and regular weight.
|
||||
/// </summary>
|
||||
public static FontUsage Caption2 => GetFont(size: 12, weight: FontWeight.Regular);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The default font.
|
||||
/// </summary>
|
||||
public static FontUsage Default => GetFont();
|
||||
public static FontUsage Default => GetFont(weight: FontWeight.Medium);
|
||||
|
||||
/// <summary>
|
||||
/// Font face for numeric display.
|
||||
/// </summary>
|
||||
public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold);
|
||||
|
||||
/// <summary>
|
||||
/// Default font face for UI and game elements.
|
||||
/// </summary>
|
||||
public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular);
|
||||
|
||||
/// <summary>
|
||||
/// Default font face with alternate character set for headings and flair text.
|
||||
/// </summary>
|
||||
public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular);
|
||||
|
||||
public static FontUsage Inter => GetFont(Typeface.Inter, weight: FontWeight.Regular);
|
||||
|
||||
@@ -115,6 +115,7 @@ namespace osu.Game.Graphics
|
||||
public static IconUsage ChangelogB => get(OsuIconMapping.ChangelogB);
|
||||
public static IconUsage Chat => get(OsuIconMapping.Chat);
|
||||
public static IconUsage CheckCircle => get(OsuIconMapping.CheckCircle);
|
||||
public static IconUsage Clock => get(OsuIconMapping.Clock);
|
||||
public static IconUsage CollapseA => get(OsuIconMapping.CollapseA);
|
||||
public static IconUsage Collections => get(OsuIconMapping.Collections);
|
||||
public static IconUsage Cross => get(OsuIconMapping.Cross);
|
||||
@@ -141,6 +142,7 @@ namespace osu.Game.Graphics
|
||||
public static IconUsage Input => get(OsuIconMapping.Input);
|
||||
public static IconUsage Maintenance => get(OsuIconMapping.Maintenance);
|
||||
public static IconUsage Megaphone => get(OsuIconMapping.Megaphone);
|
||||
public static IconUsage Metronome => get(OsuIconMapping.Metronome);
|
||||
public static IconUsage Music => get(OsuIconMapping.Music);
|
||||
public static IconUsage News => get(OsuIconMapping.News);
|
||||
public static IconUsage Next => get(OsuIconMapping.Next);
|
||||
@@ -204,6 +206,9 @@ namespace osu.Game.Graphics
|
||||
[Description(@"check-circle")]
|
||||
CheckCircle,
|
||||
|
||||
[Description(@"clock")]
|
||||
Clock,
|
||||
|
||||
[Description(@"collapse-a")]
|
||||
CollapseA,
|
||||
|
||||
@@ -282,6 +287,9 @@ namespace osu.Game.Graphics
|
||||
[Description(@"megaphone")]
|
||||
Megaphone,
|
||||
|
||||
[Description(@"metronome")]
|
||||
Metronome,
|
||||
|
||||
[Description(@"music")]
|
||||
Music,
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Radius = 5,
|
||||
},
|
||||
Colour = ButtonColour,
|
||||
Shear = new Vector2(0.2f, 0),
|
||||
Shear = OsuGame.SHEAR,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@@ -149,7 +149,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
TriangleScale = 4,
|
||||
ColourDark = OsuColour.Gray(0.88f),
|
||||
Shear = new Vector2(-0.2f, 0),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
ClampAxes = Axes.Y
|
||||
},
|
||||
},
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private Color4 hoverColour = Color4.White.Opacity(0.1f);
|
||||
|
||||
protected float ScaleOnMouseDown { get; init; } = 0.75f;
|
||||
|
||||
/// <summary>
|
||||
/// The background colour of the <see cref="OsuAnimatedButton"/> while it is hovered.
|
||||
/// </summary>
|
||||
@@ -119,7 +121,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
Content.ScaleTo(0.75f, 2000, Easing.OutQuint);
|
||||
Content.ScaleTo(ScaleOnMouseDown, 2000, Easing.OutQuint);
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Globalization;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@@ -11,7 +9,7 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Utils;
|
||||
using osu.Game.Extensions;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
@@ -85,35 +83,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
channel.Play();
|
||||
}
|
||||
|
||||
public LocalisableString GetDisplayableValue(T value)
|
||||
{
|
||||
if (CurrentNumber.IsInteger)
|
||||
return int.CreateTruncating(value).ToString("N0");
|
||||
|
||||
double floatValue = double.CreateTruncating(value);
|
||||
|
||||
decimal decimalPrecision = normalise(decimal.CreateTruncating(CurrentNumber.Precision), max_decimal_digits);
|
||||
|
||||
// Find the number of significant digits (we could have less than 5 after normalize())
|
||||
int significantDigits = FormatUtils.FindPrecision(decimalPrecision);
|
||||
|
||||
if (DisplayAsPercentage)
|
||||
{
|
||||
return floatValue.ToString($@"P{Math.Max(0, significantDigits - 2)}");
|
||||
}
|
||||
|
||||
string negativeSign = Math.Round(floatValue, significantDigits) < 0 ? "-" : string.Empty;
|
||||
|
||||
return $"{negativeSign}{Math.Abs(floatValue).ToString($"N{significantDigits}")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all non-significant digits, keeping at most a requested number of decimal digits.
|
||||
/// </summary>
|
||||
/// <param name="d">The decimal to normalize.</param>
|
||||
/// <param name="sd">The maximum number of decimal digits to keep. The final result may have fewer decimal digits than this value.</param>
|
||||
/// <returns>The normalised decimal.</returns>
|
||||
private decimal normalise(decimal d, int sd)
|
||||
=> decimal.Parse(Math.Round(d, sd).ToString(string.Concat("0.", new string('#', sd)), CultureInfo.InvariantCulture), CultureInfo.InvariantCulture);
|
||||
public LocalisableString GetDisplayableValue(T value) => CurrentNumber.Value.ToStandardFormattedString(max_decimal_digits, DisplayAsPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user