mirror of
https://github.com/ppy/osu.git
synced 2026-05-21 05:01:20 +08:00
Merge branch 'master' into song-select-v2-context-menus
This commit is contained in:
@@ -65,5 +65,61 @@ namespace osu.Game.Rulesets.Mania.Tests
|
||||
|
||||
AddStep(@"exit player", () => currentPlayer.Exit());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCorrectComboAccountingForConcurrentObjects()
|
||||
{
|
||||
Score score = null!;
|
||||
|
||||
var beatmap = new ManiaBeatmap(new StageDefinition(4))
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Note
|
||||
{
|
||||
StartTime = 500,
|
||||
Column = 0,
|
||||
},
|
||||
new Note
|
||||
{
|
||||
StartTime = 500,
|
||||
Column = 2,
|
||||
},
|
||||
new HoldNote
|
||||
{
|
||||
StartTime = 1000,
|
||||
EndTime = 1500,
|
||||
Column = 1,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AddStep(@"create replay", () => score = new Score
|
||||
{
|
||||
Replay = new Replay
|
||||
{
|
||||
Frames =
|
||||
{
|
||||
new ManiaReplayFrame(500, ManiaAction.Key1, ManiaAction.Key3),
|
||||
new ManiaReplayFrame(520),
|
||||
new ManiaReplayFrame(1000, ManiaAction.Key2),
|
||||
new ManiaReplayFrame(1500),
|
||||
}
|
||||
},
|
||||
ScoreInfo = new ScoreInfo()
|
||||
});
|
||||
|
||||
AddStep(@"set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(beatmap));
|
||||
AddStep(@"set ruleset", () => Ruleset.Value = beatmap.BeatmapInfo.Ruleset);
|
||||
AddStep(@"push player", () => LoadScreen(currentPlayer = new ReplayPlayer(score)));
|
||||
|
||||
AddUntilStep(@"wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||
AddUntilStep(@"wait for objects to be judged", () => currentPlayer.ChildrenOfType<IFrameStableClock>().Single().CurrentTime, () => Is.GreaterThan(1600));
|
||||
AddStep(@"stop gameplay", () => currentPlayer.ChildrenOfType<GameplayClockContainer>().Single().Stop());
|
||||
AddStep(@"seek to start", () => currentPlayer.Seek(0));
|
||||
AddAssert(@"combo is 0", () => currentPlayer.GameplayState.ScoreProcessor.Combo.Value, () => Is.Zero);
|
||||
|
||||
AddStep(@"exit player", () => currentPlayer.Exit());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Replays;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
@@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Cached]
|
||||
private OsuRulesetConfigManager config = new OsuRulesetConfigManager(null, new OsuRuleset().RulesetInfo);
|
||||
|
||||
private readonly StopwatchClock clock = new StopwatchClock();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
@@ -35,7 +38,10 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
new OsuPlayfieldAdjustmentContainer
|
||||
{
|
||||
Child = analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay()),
|
||||
Child = analysisContainer = new TestReplayAnalysisOverlay(fabricateReplay())
|
||||
{
|
||||
Clock = new FramedClock(clock)
|
||||
},
|
||||
},
|
||||
settings = new ReplayAnalysisSettings(config),
|
||||
};
|
||||
@@ -55,11 +61,23 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
settings.ShowAimMarkers.Value = true;
|
||||
settings.ShowCursorPath.Value = true;
|
||||
});
|
||||
AddToggleStep("toggle pause", running =>
|
||||
{
|
||||
if (running)
|
||||
clock.Stop();
|
||||
else
|
||||
clock.Start();
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHitMarkers()
|
||||
{
|
||||
AddStep("stop at 2000", () =>
|
||||
{
|
||||
clock.Stop();
|
||||
clock.Seek(2000);
|
||||
});
|
||||
AddStep("enable hit markers", () => settings.ShowClickMarkers.Value = true);
|
||||
AddUntilStep("hit markers visible", () => analysisContainer.HitMarkersVisible);
|
||||
AddStep("disable hit markers", () => settings.ShowClickMarkers.Value = false);
|
||||
@@ -69,6 +87,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestAimMarker()
|
||||
{
|
||||
AddStep("stop at 2000", () =>
|
||||
{
|
||||
clock.Stop();
|
||||
clock.Seek(2000);
|
||||
});
|
||||
AddStep("enable aim markers", () => settings.ShowAimMarkers.Value = true);
|
||||
AddUntilStep("aim markers visible", () => analysisContainer.AimMarkersVisible);
|
||||
AddStep("disable aim markers", () => settings.ShowAimMarkers.Value = false);
|
||||
@@ -78,6 +101,11 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
[Test]
|
||||
public void TestAimLines()
|
||||
{
|
||||
AddStep("stop at 2000", () =>
|
||||
{
|
||||
clock.Stop();
|
||||
clock.Seek(2000);
|
||||
});
|
||||
AddStep("enable aim lines", () => settings.ShowCursorPath.Value = true);
|
||||
AddUntilStep("aim lines visible", () => analysisContainer.AimLinesVisible);
|
||||
AddStep("disable aim lines", () => settings.ShowCursorPath.Value = false);
|
||||
@@ -87,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
private Replay fabricateReplay()
|
||||
{
|
||||
var frames = new List<ReplayFrame>();
|
||||
var random = new Random();
|
||||
var random = new Random(20250522);
|
||||
int posX = 250;
|
||||
int posY = 250;
|
||||
|
||||
@@ -109,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
frames.Add(new OsuReplayFrame
|
||||
{
|
||||
Time = Time.Current + i * 15,
|
||||
Time = i * 15,
|
||||
Position = new Vector2(posX, posY),
|
||||
Actions = actions.ToList(),
|
||||
});
|
||||
|
||||
@@ -43,7 +43,10 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
||||
{
|
||||
if (obj.NewCombo) { lastNewComboTime = obj.StartTime; }
|
||||
if (obj.NewCombo)
|
||||
{
|
||||
lastNewComboTime = obj.StartTime;
|
||||
}
|
||||
|
||||
applyFadeInAdjustment(obj);
|
||||
}
|
||||
|
||||
@@ -421,6 +421,65 @@ namespace osu.Game.Tests.Rulesets.Scoring
|
||||
Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.SH));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestComboAccounting([Values] bool shuffleResults)
|
||||
{
|
||||
var testBeatmap = new Beatmap
|
||||
{
|
||||
HitObjects = Enumerable.Range(1, 40).Select(i => new TestHitObject(HitResult.Perfect, HitResult.Miss)).ToList<HitObject>(),
|
||||
};
|
||||
scoreProcessor.ApplyBeatmap(testBeatmap);
|
||||
|
||||
var results = new List<JudgementResult>();
|
||||
JudgementResult judgementResult;
|
||||
|
||||
for (int i = 0; i < 25; ++i)
|
||||
{
|
||||
judgementResult = new JudgementResult(testBeatmap.HitObjects[i], new TestJudgement(HitResult.Perfect, HitResult.Miss))
|
||||
{
|
||||
Type = HitResult.Perfect
|
||||
};
|
||||
results.Add(judgementResult);
|
||||
scoreProcessor.ApplyResult(judgementResult);
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(i + 1));
|
||||
}
|
||||
|
||||
judgementResult = new JudgementResult(testBeatmap.HitObjects[25], new TestJudgement(HitResult.Perfect, HitResult.Miss))
|
||||
{
|
||||
Type = HitResult.Miss
|
||||
};
|
||||
results.Add(judgementResult);
|
||||
scoreProcessor.ApplyResult(judgementResult);
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||
|
||||
for (int i = 26; i < 40; ++i)
|
||||
{
|
||||
judgementResult = new JudgementResult(testBeatmap.HitObjects[i], new TestJudgement(HitResult.Perfect, HitResult.Miss))
|
||||
{
|
||||
Type = HitResult.Perfect
|
||||
};
|
||||
results.Add(judgementResult);
|
||||
scoreProcessor.ApplyResult(judgementResult);
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(i - 25));
|
||||
}
|
||||
|
||||
Assert.That(scoreProcessor.MaximumStatistics[HitResult.Perfect], Is.EqualTo(40));
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(14));
|
||||
Assert.That(scoreProcessor.HighestCombo.Value, Is.EqualTo(25));
|
||||
|
||||
// random shuffle is VERY extreme and overkill.
|
||||
// it might not work correctly for any other `ScoreProcessor` property, and the intermediate results likely make no sense.
|
||||
// the goal is only to demonstrate idempotency to zero when reverting all results.
|
||||
var random = new Random(20250519);
|
||||
var toRevert = shuffleResults ? results.OrderBy(_ => random.Next()).ToList() : Enumerable.Reverse(results);
|
||||
|
||||
foreach (var result in toRevert)
|
||||
scoreProcessor.RevertResult(result);
|
||||
|
||||
Assert.That(scoreProcessor.Combo.Value, Is.Zero);
|
||||
Assert.That(scoreProcessor.HighestCombo.Value, Is.Zero);
|
||||
}
|
||||
|
||||
private class TestJudgement : Judgement
|
||||
{
|
||||
public override HitResult MaxResult { get; }
|
||||
|
||||
@@ -108,7 +108,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public bool IsRunning => true;
|
||||
|
||||
public double TrueGameplayRate { set => adjustableAudioComponent.Tempo.Value = value; }
|
||||
public double TrueGameplayRate
|
||||
{
|
||||
set => adjustableAudioComponent.Tempo.Value = value;
|
||||
}
|
||||
|
||||
private readonly AudioAdjustments adjustableAudioComponent = new AudioAdjustments();
|
||||
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
// 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 System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
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 osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class BeatmapCarouselFilterGroupingTest
|
||||
{
|
||||
#region No grouping
|
||||
|
||||
[Test]
|
||||
public async Task TestNoGrouping()
|
||||
{
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(applyTitle('E'), beatmapSets, out var beatmap1);
|
||||
addBeatmapSet(applyArtist('D'), beatmapSets, out var beatmap2);
|
||||
addBeatmapSet(applyAuthor('H'), beatmapSets, out var beatmap3);
|
||||
addBeatmapSet(applyLength(65_000), beatmapSets, out var beatmap4);
|
||||
|
||||
BeatmapInfo[] allBeatmaps =
|
||||
[
|
||||
..beatmap1.Beatmaps,
|
||||
..beatmap2.Beatmaps,
|
||||
..beatmap3.Beatmaps,
|
||||
..beatmap4.Beatmaps
|
||||
];
|
||||
|
||||
var results = await runGrouping(GroupMode.NoGrouping, beatmapSets);
|
||||
Assert.That(results.Select(r => r.Model).OfType<BeatmapSetInfo>(), Is.EquivalentTo(beatmapSets));
|
||||
Assert.That(results.Select(r => r.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(allBeatmaps));
|
||||
assertTotal(results, beatmapSets.Count + allBeatmaps.Length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Alphabetical grouping
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByArtist() => await testAlphabeticGroupingMode(GroupMode.Artist, applyArtist);
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByAuthor() => await testAlphabeticGroupingMode(GroupMode.Author, applyAuthor);
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByTitle() => await testAlphabeticGroupingMode(GroupMode.Title, applyTitle);
|
||||
|
||||
private async Task testAlphabeticGroupingMode(GroupMode mode, Func<char, Action<BeatmapSetInfo>> applyBeatmap)
|
||||
{
|
||||
int total = 0;
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
|
||||
addBeatmapSet(applyBeatmap('4'), beatmapSets, out var fourBeatmap);
|
||||
addBeatmapSet(applyBeatmap('5'), beatmapSets, out var fiveBeatmap);
|
||||
addBeatmapSet(applyBeatmap('A'), beatmapSets, out var aBeatmap);
|
||||
addBeatmapSet(applyBeatmap('F'), beatmapSets, out var fBeatmap);
|
||||
addBeatmapSet(applyBeatmap('Z'), beatmapSets, out var zBeatmap);
|
||||
addBeatmapSet(applyBeatmap('-'), beatmapSets, out var dashBeatmap);
|
||||
addBeatmapSet(applyBeatmap('_'), beatmapSets, out var underscoreBeatmap);
|
||||
|
||||
var results = await runGrouping(mode, beatmapSets);
|
||||
assertGroup(results, 0, "0-9", new[] { fiveBeatmap, fourBeatmap }, ref total);
|
||||
assertGroup(results, 1, "A", new[] { aBeatmap }, ref total);
|
||||
assertGroup(results, 2, "F", new[] { fBeatmap }, ref total);
|
||||
assertGroup(results, 3, "Z", new[] { zBeatmap }, ref total);
|
||||
assertGroup(results, 4, "Other", new[] { dashBeatmap, underscoreBeatmap }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
private Action<BeatmapSetInfo> applyArtist(char first)
|
||||
{
|
||||
return s => s.Beatmaps[0].Metadata.Artist = $"{first}-artist";
|
||||
}
|
||||
|
||||
private Action<BeatmapSetInfo> applyAuthor(char first)
|
||||
{
|
||||
return s => s.Beatmaps[0].Metadata.Author.Username = $"{first}-author";
|
||||
}
|
||||
|
||||
private Action<BeatmapSetInfo> applyTitle(char first)
|
||||
{
|
||||
return s => s.Beatmaps[0].Metadata.Title = $"{first}-title";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Date grouping
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByDateAdded()
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddHours(-5), beatmapSets, out var todayBeatmap);
|
||||
addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddDays(-1), beatmapSets, out var yesterdayBeatmap);
|
||||
addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddDays(-4), beatmapSets, out var lastWeekBeatmap);
|
||||
addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddDays(-21), beatmapSets, out var oneMonthBeatmap);
|
||||
addBeatmapSet(s => s.DateAdded = DateTimeOffset.Now.AddMonths(-2).AddDays(-3), beatmapSets, out var threeMonthBeatmap);
|
||||
|
||||
var results = await runGrouping(GroupMode.DateAdded, beatmapSets);
|
||||
assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
|
||||
assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
|
||||
assertGroup(results, 3, "1 month ago", new[] { oneMonthBeatmap }, ref total);
|
||||
assertGroup(results, 4, "3 months ago", new[] { threeMonthBeatmap }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByLastPlayed()
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(applyLastPlayed(DateTimeOffset.Now.AddHours(-5)), beatmapSets, out var todayBeatmap);
|
||||
addBeatmapSet(applyLastPlayed(DateTimeOffset.Now.AddDays(-1)), beatmapSets, out var yesterdayBeatmap);
|
||||
addBeatmapSet(applyLastPlayed(DateTimeOffset.Now.AddDays(-4)), beatmapSets, out var lastWeekBeatmap);
|
||||
addBeatmapSet(applyLastPlayed(DateTimeOffset.Now.AddDays(-21)), beatmapSets, out var oneMonthBeatmap);
|
||||
addBeatmapSet(applyLastPlayed(DateTimeOffset.Now.AddMonths(-2).AddDays(-3)), beatmapSets, out var threeMonthBeatmap);
|
||||
addBeatmapSet(applyLastPlayed(null), beatmapSets, out var neverBeatmap);
|
||||
|
||||
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
|
||||
assertGroup(results, 0, "Today", new[] { todayBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Yesterday", new[] { yesterdayBeatmap }, ref total);
|
||||
assertGroup(results, 2, "Last week", new[] { lastWeekBeatmap }, ref total);
|
||||
assertGroup(results, 3, "1 month ago", new[] { oneMonthBeatmap }, ref total);
|
||||
assertGroup(results, 4, "3 months ago", new[] { threeMonthBeatmap }, ref total);
|
||||
assertGroup(results, 5, "Never", new[] { neverBeatmap }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByLastPlayed_BeatmapPartiallyPlayed()
|
||||
{
|
||||
var set = TestResources.CreateTestBeatmapSetInfo(3);
|
||||
set.Beatmaps[0].LastPlayed = null;
|
||||
set.Beatmaps[1].LastPlayed = null;
|
||||
set.Beatmaps[2].LastPlayed = DateTimeOffset.Now;
|
||||
|
||||
List<BeatmapSetInfo> beatmapSets = new List<BeatmapSetInfo> { set };
|
||||
|
||||
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
|
||||
int total = 0;
|
||||
|
||||
assertGroup(results, 0, "Today", new[] { set }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByLastPlayed_NeverBelowOverFiveMonthsAgo()
|
||||
{
|
||||
List<BeatmapSetInfo> beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(applyLastPlayed(null), beatmapSets, out var neverBeatmap);
|
||||
addBeatmapSet(applyLastPlayed(DateTimeOffset.Now.AddMonths(-6)), beatmapSets, out var overFiveMonthsBeatmap);
|
||||
|
||||
var results = await runGrouping(GroupMode.LastPlayed, beatmapSets);
|
||||
int total = 0;
|
||||
|
||||
assertGroup(results, 0, "Over 5 months ago", new[] { overFiveMonthsBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Never", new[] { neverBeatmap }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
private Action<BeatmapSetInfo> applyLastPlayed(DateTimeOffset? lastPlayed)
|
||||
{
|
||||
return s => s.Beatmaps.ForEach(b => b.LastPlayed = lastPlayed);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ranked Status
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByRankedStatus()
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.Ranked, beatmapSets, out var rankedBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.Approved, beatmapSets, out var approvedBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.Qualified, beatmapSets, out var qualifiedBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.Loved, beatmapSets, out var lovedBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.WIP, beatmapSets, out var wipBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.Pending, beatmapSets, out var pendingBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.Graveyard, beatmapSets, out var graveyardBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.None, beatmapSets, out var noneBeatmap);
|
||||
addBeatmapSet(s => s.Status = BeatmapOnlineStatus.LocallyModified, beatmapSets, out var localBeatmap);
|
||||
|
||||
var results = await runGrouping(GroupMode.RankedStatus, beatmapSets);
|
||||
assertGroup(results, 0, "Ranked", new[] { rankedBeatmap, approvedBeatmap }, ref total);
|
||||
assertGroup(results, 1, "Qualified", new[] { qualifiedBeatmap }, ref total);
|
||||
assertGroup(results, 2, "WIP", new[] { wipBeatmap }, ref total);
|
||||
assertGroup(results, 3, "Pending", new[] { pendingBeatmap }, ref total);
|
||||
assertGroup(results, 4, "Graveyard", new[] { graveyardBeatmap }, ref total);
|
||||
assertGroup(results, 5, "Local", new[] { localBeatmap }, ref total);
|
||||
assertGroup(results, 6, "Unknown", new[] { noneBeatmap }, ref total);
|
||||
assertGroup(results, 7, "Loved", new[] { lovedBeatmap }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BPM grouping
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByBPM()
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(applyBPM(30), beatmapSets, out var beatmap30);
|
||||
addBeatmapSet(applyBPM(60), beatmapSets, out var beatmap60);
|
||||
addBeatmapSet(applyBPM(90), beatmapSets, out var beatmap90);
|
||||
addBeatmapSet(applyBPM(120), beatmapSets, out var beatmap120);
|
||||
addBeatmapSet(applyBPM(270), beatmapSets, out var beatmap270);
|
||||
addBeatmapSet(applyBPM(300), beatmapSets, out var beatmap300);
|
||||
addBeatmapSet(applyBPM(330), beatmapSets, out var beatmap330);
|
||||
|
||||
var results = await runGrouping(GroupMode.BPM, beatmapSets);
|
||||
assertGroup(results, 0, "Under 60 BPM", new[] { beatmap30 }, ref total);
|
||||
assertGroup(results, 1, "Under 120 BPM", new[] { beatmap60, beatmap90 }, ref total);
|
||||
assertGroup(results, 2, "Under 180 BPM", new[] { beatmap120 }, ref total);
|
||||
assertGroup(results, 3, "Under 300 BPM", new[] { beatmap270 }, ref total);
|
||||
assertGroup(results, 4, "Over 300 BPM", new[] { beatmap300, beatmap330 }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
private Action<BeatmapSetInfo> applyBPM(double bpm)
|
||||
{
|
||||
return s => s.Beatmaps.ForEach(b => b.BPM = bpm);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Difficulty grouping
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByDifficulty()
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(applyStars(0.5), beatmapSets, out var beatmapBelow1);
|
||||
addBeatmapSet(applyStars(1.9), beatmapSets, out var beatmapAbove1);
|
||||
addBeatmapSet(applyStars(1.995), beatmapSets, out var beatmapAlmost2);
|
||||
addBeatmapSet(applyStars(2), beatmapSets, out var beatmap2);
|
||||
addBeatmapSet(applyStars(2.1), beatmapSets, out var beatmapAbove2);
|
||||
addBeatmapSet(applyStars(7), beatmapSets, out var beatmap7);
|
||||
|
||||
var results = await runGrouping(GroupMode.Difficulty, beatmapSets);
|
||||
assertGroup(results, 0, "Below 1 Star", new[] { beatmapBelow1 }, ref total);
|
||||
assertGroup(results, 1, "1 Star", new[] { beatmapAbove1 }, ref total);
|
||||
assertGroup(results, 2, "2 Stars", new[] { beatmapAlmost2, beatmap2, beatmapAbove2 }, ref total);
|
||||
assertGroup(results, 3, "7 Stars", new[] { beatmap7 }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
private Action<BeatmapSetInfo> applyStars(double stars)
|
||||
{
|
||||
return s => s.Beatmaps.ForEach(b => b.StarRating = stars);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Length grouping
|
||||
|
||||
[Test]
|
||||
public async Task TestGroupingByLength()
|
||||
{
|
||||
int total = 0;
|
||||
|
||||
var beatmapSets = new List<BeatmapSetInfo>();
|
||||
addBeatmapSet(applyLength(30_000), beatmapSets, out var beatmap30Sec);
|
||||
addBeatmapSet(applyLength(60_000), beatmapSets, out var beatmap1Min);
|
||||
addBeatmapSet(applyLength(90_000), beatmapSets, out var beatmap1Min30Sec);
|
||||
addBeatmapSet(applyLength(120_000), beatmapSets, out var beatmap2Min);
|
||||
addBeatmapSet(applyLength(300_000), beatmapSets, out var beatmap5Min);
|
||||
addBeatmapSet(applyLength(360_000), beatmapSets, out var beatmap6Min);
|
||||
addBeatmapSet(applyLength(600_000), beatmapSets, out var beatmap10Min);
|
||||
addBeatmapSet(applyLength(630_000), beatmapSets, out var beatmap10Min30Sec);
|
||||
|
||||
var results = await runGrouping(GroupMode.Length, beatmapSets);
|
||||
assertGroup(results, 0, "1 minute or less", new[] { beatmap30Sec, beatmap1Min }, ref total);
|
||||
assertGroup(results, 1, "2 minutes or less", new[] { beatmap1Min30Sec, beatmap2Min }, ref total);
|
||||
assertGroup(results, 2, "5 minutes or less", new[] { beatmap5Min }, ref total);
|
||||
assertGroup(results, 3, "10 minutes or less", new[] { beatmap6Min, beatmap10Min }, ref total);
|
||||
assertGroup(results, 4, "Over 10 minutes", new[] { beatmap10Min30Sec }, ref total);
|
||||
assertTotal(results, total);
|
||||
}
|
||||
|
||||
private Action<BeatmapSetInfo> applyLength(double length)
|
||||
{
|
||||
return s => s.Beatmaps.ForEach(b => b.Length = length);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static async Task<List<CarouselItem>> runGrouping(GroupMode group, List<BeatmapSetInfo> beatmapSets)
|
||||
{
|
||||
var groupingFilter = new BeatmapCarouselFilterGrouping(() => new FilterCriteria { Group = group });
|
||||
var carouselItems = await groupingFilter.Run(beatmapSets.SelectMany(s => s.Beatmaps.Select(b => new CarouselItem(b))).ToList(), CancellationToken.None);
|
||||
|
||||
// sanity check to ensure no detection of two group items with equal order value.
|
||||
var groups = carouselItems.Select(i => i.Model).OfType<GroupDefinition>();
|
||||
|
||||
foreach (var header in groups)
|
||||
{
|
||||
var sameOrder = groups.FirstOrDefault(g => g != header && g.Order == header.Order);
|
||||
if (sameOrder != null)
|
||||
Assert.Fail($"Detected two groups with equal order number: \"{header.Title}\" vs. \"{sameOrder.Title}\"");
|
||||
}
|
||||
|
||||
return carouselItems;
|
||||
}
|
||||
|
||||
private static void assertGroup(List<CarouselItem> items, int index, string expectedTitle, IEnumerable<BeatmapSetInfo> expectedBeatmapSets, ref int totalItems)
|
||||
{
|
||||
var groupItem = items.Where(i => i.Model is GroupDefinition).ElementAtOrDefault(index);
|
||||
|
||||
if (groupItem == null)
|
||||
{
|
||||
Assert.Fail($"Expected group at index {index}, but that is out of bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
var itemsInGroup = items.SkipWhile(i => i != groupItem).Skip(1).TakeWhile(i => i.Model is not GroupDefinition);
|
||||
|
||||
var groupModel = (GroupDefinition)groupItem.Model;
|
||||
|
||||
Assert.That(groupModel.Title, Is.EqualTo(expectedTitle));
|
||||
Assert.That(itemsInGroup.Select(i => i.Model).OfType<BeatmapInfo>(), Is.EquivalentTo(expectedBeatmapSets.SelectMany(bs => bs.Beatmaps)));
|
||||
|
||||
totalItems += itemsInGroup.Count() + 1;
|
||||
}
|
||||
|
||||
private static void assertTotal(List<CarouselItem> items, int total)
|
||||
{
|
||||
Assert.That(items.Count, Is.EqualTo(total));
|
||||
}
|
||||
|
||||
private static void addBeatmapSet(Action<BeatmapSetInfo> change, List<BeatmapSetInfo> list, out BeatmapSetInfo added)
|
||||
{
|
||||
var set = TestResources.CreateTestBeatmapSetInfo();
|
||||
change(set);
|
||||
list.Add(set);
|
||||
added = set;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
SelectedMods.SetDefault();
|
||||
|
||||
Config.SetValue(OsuSetting.SongSelectSortingMode, SortMode.Title);
|
||||
Config.SetValue(OsuSetting.SongSelectGroupingMode, GroupMode.All);
|
||||
Config.SetValue(OsuSetting.SongSelectGroupMode, GroupMode.NoGrouping);
|
||||
|
||||
SongSelect = null!;
|
||||
});
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
[Explicit]
|
||||
public void TestSorting()
|
||||
{
|
||||
SortAndGroupBy(SortMode.Artist, GroupMode.All);
|
||||
SortAndGroupBy(SortMode.Artist, GroupMode.NoGrouping);
|
||||
SortAndGroupBy(SortMode.Difficulty, GroupMode.Difficulty);
|
||||
SortAndGroupBy(SortMode.Artist, GroupMode.Artist);
|
||||
}
|
||||
|
||||
@@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddBeatmaps(2, 3);
|
||||
WaitForDrawablePanels();
|
||||
|
||||
SortAndGroupBy(SortMode.Difficulty, GroupMode.All);
|
||||
SortAndGroupBy(SortMode.Difficulty, GroupMode.NoGrouping);
|
||||
WaitForFiltering();
|
||||
|
||||
AddUntilStep("standalone panels displayed", () => GetVisiblePanels<PanelBeatmapStandalone>().Any());
|
||||
|
||||
@@ -60,21 +60,21 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(new StarDifficulty(star, 0), $"{star} Star(s)"))
|
||||
Item = new CarouselItem(new StarDifficultyGroupDefinition(0, $"{star} Star(s)", new StarDifficulty(star, 0)))
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(new StarDifficulty(star, 0), $"{star} Star(s)")),
|
||||
Item = new CarouselItem(new StarDifficultyGroupDefinition(1, $"{star} Star(s)", new StarDifficulty(star, 0))),
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(new StarDifficulty(star, 0), $"{star} Star(s)")),
|
||||
Item = new CarouselItem(new StarDifficultyGroupDefinition(2, $"{star} Star(s)", new StarDifficulty(star, 0))),
|
||||
Expanded = { Value = true },
|
||||
},
|
||||
new PanelGroupStarDifficulty
|
||||
{
|
||||
Item = new CarouselItem(new GroupDefinition(new StarDifficulty(star, 0), $"{star} Star(s)")),
|
||||
Item = new CarouselItem(new StarDifficultyGroupDefinition(3, $"{star} Star(s)", new StarDifficulty(star, 0))),
|
||||
Expanded = { Value = true },
|
||||
KeyboardSelected = { Value = true },
|
||||
},
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Position = new Vector2(275, 5)
|
||||
});
|
||||
|
||||
filter.PinItem(GroupMode.All);
|
||||
filter.PinItem(GroupMode.RecentlyPlayed);
|
||||
filter.PinItem(GroupMode.NoGrouping);
|
||||
filter.PinItem(GroupMode.LastPlayed);
|
||||
|
||||
filter.Current.ValueChanged += grouping =>
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ namespace osu.Game.Beatmaps
|
||||
/// Once in this state, online status changes should be ignored unless the beatmap is reverted or submitted.
|
||||
/// </summary>
|
||||
[LocalisableDescription(typeof(SongSelectStrings), nameof(SongSelectStrings.LocallyModified))]
|
||||
[Description("Local")]
|
||||
LocallyModified = -4,
|
||||
|
||||
[Description("Unknown")]
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
|
||||
SetDefault(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1);
|
||||
|
||||
SetDefault(OsuSetting.SongSelectGroupingMode, GroupMode.All);
|
||||
SetDefault(OsuSetting.SongSelectGroupMode, GroupMode.NoGrouping);
|
||||
SetDefault(OsuSetting.SongSelectSortingMode, SortMode.Title);
|
||||
|
||||
SetDefault(OsuSetting.RandomSelectAlgorithm, RandomSelectAlgorithm.RandomPermutation);
|
||||
@@ -389,7 +389,7 @@ namespace osu.Game.Configuration
|
||||
SaveUsername,
|
||||
DisplayStarsMinimum,
|
||||
DisplayStarsMaximum,
|
||||
SongSelectGroupingMode,
|
||||
SongSelectGroupMode,
|
||||
SongSelectSortingMode,
|
||||
RandomSelectAlgorithm,
|
||||
ModSelectHotkeyStyle,
|
||||
|
||||
@@ -31,7 +31,11 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
|
||||
public bool FilteringActive { get; set; }
|
||||
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty };
|
||||
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
||||
|
||||
public bool MatchingFilter
|
||||
{
|
||||
set => this.FadeTo(value ? 1f : 0f, 100);
|
||||
}
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||
|
||||
|
||||
@@ -207,7 +207,10 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
}
|
||||
}
|
||||
|
||||
bool IFilterable.FilteringActive { set { } }
|
||||
bool IFilterable.FilteringActive
|
||||
{
|
||||
set { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,10 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}
|
||||
|
||||
public bool FilteringActive { set { } }
|
||||
public bool FilteringActive
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -74,6 +74,11 @@ namespace osu.Game.Rulesets.Judgements
|
||||
/// </summary>
|
||||
public int HighestComboAtJudgement { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The highest combo achieved after this <see cref="JudgementResult"/> occurred.
|
||||
/// </summary>
|
||||
public int HighestComboAfterJudgement { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// The health prior to this <see cref="JudgementResult"/> occurring.
|
||||
/// </summary>
|
||||
|
||||
@@ -202,7 +202,6 @@ namespace osu.Game.Rulesets.Scoring
|
||||
{
|
||||
Ruleset = ruleset;
|
||||
|
||||
Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue);
|
||||
Accuracy.ValueChanged += _ => updateRank();
|
||||
|
||||
Mods.ValueChanged += mods =>
|
||||
@@ -238,7 +237,10 @@ namespace osu.Game.Rulesets.Scoring
|
||||
else if (result.Type.BreaksCombo())
|
||||
Combo.Value = 0;
|
||||
|
||||
HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value);
|
||||
|
||||
result.ComboAfterJudgement = Combo.Value;
|
||||
result.HighestComboAfterJudgement = HighestCombo.Value;
|
||||
|
||||
if (result.Judgement.MaxResult.AffectsAccuracy())
|
||||
{
|
||||
@@ -281,8 +283,11 @@ namespace osu.Game.Rulesets.Scoring
|
||||
if (!TrackHitEvents)
|
||||
throw new InvalidOperationException(@$"Rewind is not supported when {nameof(TrackHitEvents)} is disabled.");
|
||||
|
||||
Combo.Value = result.ComboAtJudgement;
|
||||
HighestCombo.Value = result.HighestComboAtJudgement;
|
||||
// the reason this is written so funnily rather than just using `ComboAtJudgement`
|
||||
// is to nullify impact of ordering when reverting concurrent judgement results
|
||||
// (think mania and multiple judgements within a frame).
|
||||
Combo.Value -= (result.ComboAfterJudgement - result.ComboAtJudgement);
|
||||
HighestCombo.Value -= (result.HighestComboAfterJudgement - result.HighestComboAtJudgement);
|
||||
|
||||
if (result.FailedAtJudgement && !ApplyNewJudgementsWhenFailed)
|
||||
return;
|
||||
|
||||
@@ -356,8 +356,15 @@ namespace osu.Game.Screens.Edit.Timing
|
||||
waveformGraph.Waveform = beatmap.Value.Waveform;
|
||||
}
|
||||
|
||||
public int BeatIndex { set => beatIndexText.Text = value.ToString(); }
|
||||
public Vector2 WaveformScale { set => waveformGraph.Scale = value; }
|
||||
public int BeatIndex
|
||||
{
|
||||
set => beatIndexText.Text = value.ToString();
|
||||
}
|
||||
|
||||
public Vector2 WaveformScale
|
||||
{
|
||||
set => waveformGraph.Scale = value;
|
||||
}
|
||||
|
||||
public void WaveformOffsetTo(float value, bool animated) =>
|
||||
this.TransformTo(nameof(waveformOffset), value, animated ? 300 : 0, Easing.OutQuint);
|
||||
|
||||
@@ -28,7 +28,10 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
protected override bool IsActive => FreeMods.Value.Count > 0;
|
||||
|
||||
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
|
||||
public new Action Action
|
||||
{
|
||||
set => throw new NotSupportedException("The click action is handled by the button itself.");
|
||||
}
|
||||
|
||||
private OsuSpriteText count = null!;
|
||||
private Circle circle = null!;
|
||||
|
||||
@@ -21,7 +21,10 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
protected override bool IsActive => Freestyle.Value;
|
||||
|
||||
public new Action Action { set => throw new NotSupportedException("The click action is handled by the button itself."); }
|
||||
public new Action Action
|
||||
{
|
||||
set => throw new NotSupportedException("The click action is handled by the button itself.");
|
||||
}
|
||||
|
||||
private OsuSpriteText text = null!;
|
||||
private Circle circle = null!;
|
||||
|
||||
@@ -20,7 +20,10 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
|
||||
public readonly JudgementCount Result;
|
||||
|
||||
public JudgementCounter(JudgementCount result) => Result = result;
|
||||
public JudgementCounter(JudgementCount result)
|
||||
{
|
||||
Result = result;
|
||||
}
|
||||
|
||||
public OsuSpriteText ResultName = null!;
|
||||
private FillFlowContainer flowContainer = null!;
|
||||
|
||||
@@ -140,7 +140,10 @@ namespace osu.Game.Screens.Ranking
|
||||
set => Alpha = value ? 1 : 0;
|
||||
}
|
||||
|
||||
public bool FilteringActive { set { } }
|
||||
public bool FilteringActive
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
public GroupFlow(string? name)
|
||||
{
|
||||
@@ -245,8 +248,15 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
public IEnumerable<LocalisableString> FilterTerms => [Tag.FullName, Tag.Description];
|
||||
|
||||
public bool MatchingFilter { set => Alpha = value ? 1 : 0; }
|
||||
public bool FilteringActive { set { } }
|
||||
public bool MatchingFilter
|
||||
{
|
||||
set => Alpha = value ? 1 : 0;
|
||||
}
|
||||
|
||||
public bool FilteringActive
|
||||
{
|
||||
set { }
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
|
||||
@@ -7,8 +7,8 @@ namespace osu.Game.Screens.Select.Filter
|
||||
{
|
||||
public enum GroupMode
|
||||
{
|
||||
[Description("All")]
|
||||
All,
|
||||
[Description("No Grouping")]
|
||||
NoGrouping,
|
||||
|
||||
[Description("Artist")]
|
||||
Artist,
|
||||
@@ -37,19 +37,16 @@ namespace osu.Game.Screens.Select.Filter
|
||||
[Description("My Maps")]
|
||||
MyMaps,
|
||||
|
||||
[Description("No Grouping")]
|
||||
NoGrouping,
|
||||
|
||||
[Description("Rank Achieved")]
|
||||
RankAchieved,
|
||||
|
||||
[Description("Ranked Status")]
|
||||
RankedStatus,
|
||||
|
||||
[Description("Recently Played")]
|
||||
RecentlyPlayed,
|
||||
[Description("Last Played")]
|
||||
LastPlayed,
|
||||
|
||||
[Description("Title")]
|
||||
Title
|
||||
Title,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select
|
||||
private void load(OsuColour colours, OsuConfigManager config)
|
||||
{
|
||||
sortMode = config.GetBindable<SortMode>(OsuSetting.SongSelectSortingMode);
|
||||
groupMode = config.GetBindable<GroupMode>(OsuSetting.SongSelectGroupingMode);
|
||||
groupMode = config.GetBindable<GroupMode>(OsuSetting.SongSelectGroupMode);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
||||
@@ -411,10 +411,10 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
switch (item.Model)
|
||||
{
|
||||
case GroupDefinition group:
|
||||
if (group.Data is StarDifficulty)
|
||||
return starsGroupPanelPool.Get();
|
||||
case StarDifficultyGroupDefinition:
|
||||
return starsGroupPanelPool.Get();
|
||||
|
||||
case GroupDefinition:
|
||||
return groupPanelPool.Get();
|
||||
|
||||
case BeatmapInfo:
|
||||
@@ -433,5 +433,15 @@ namespace osu.Game.Screens.SelectV2
|
||||
#endregion
|
||||
}
|
||||
|
||||
public record GroupDefinition(object Data, string Title);
|
||||
/// <summary>
|
||||
/// Defines a grouping header for a set of carousel items.
|
||||
/// </summary>
|
||||
/// <param name="Order">The order of this group in the carousel, sorted using ascending order.</param>
|
||||
/// <param name="Title">The title of this group.</param>
|
||||
public record GroupDefinition(int Order, string Title);
|
||||
|
||||
/// <summary>
|
||||
/// Defines a grouping header for a set of carousel items grouped by star difficulty.
|
||||
/// </summary>
|
||||
public record StarDifficultyGroupDefinition(int Order, string Title, StarDifficulty Difficulty) : GroupDefinition(Order, Title);
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Carousel;
|
||||
using osu.Game.Screens.Select;
|
||||
@@ -19,15 +21,15 @@ namespace osu.Game.Screens.SelectV2
|
||||
/// <summary>
|
||||
/// Beatmap sets contain difficulties as related panels. This dictionary holds the relationships between set-difficulties to allow expanding them on selection.
|
||||
/// </summary>
|
||||
public IDictionary<BeatmapSetInfo, HashSet<CarouselItem>> SetItems => setItems;
|
||||
public IDictionary<BeatmapSetInfo, HashSet<CarouselItem>> SetItems => setMap;
|
||||
|
||||
/// <summary>
|
||||
/// Groups contain children which are group-selectable. This dictionary holds the relationships between groups-panels to allow expanding them on selection.
|
||||
/// </summary>
|
||||
public IDictionary<GroupDefinition, HashSet<CarouselItem>> GroupItems => groupItems;
|
||||
public IDictionary<GroupDefinition, HashSet<CarouselItem>> GroupItems => groupMap;
|
||||
|
||||
private readonly Dictionary<BeatmapSetInfo, HashSet<CarouselItem>> setItems = new Dictionary<BeatmapSetInfo, HashSet<CarouselItem>>();
|
||||
private readonly Dictionary<GroupDefinition, HashSet<CarouselItem>> groupItems = new Dictionary<GroupDefinition, HashSet<CarouselItem>>();
|
||||
private readonly Dictionary<BeatmapSetInfo, HashSet<CarouselItem>> setMap = new Dictionary<BeatmapSetInfo, HashSet<CarouselItem>>();
|
||||
private readonly Dictionary<GroupDefinition, HashSet<CarouselItem>> groupMap = new Dictionary<GroupDefinition, HashSet<CarouselItem>>();
|
||||
|
||||
private readonly Func<FilterCriteria> getCriteria;
|
||||
|
||||
@@ -40,72 +42,70 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
setItems.Clear();
|
||||
groupItems.Clear();
|
||||
setMap.Clear();
|
||||
groupMap.Clear();
|
||||
|
||||
var criteria = getCriteria();
|
||||
var newItems = new List<CarouselItem>();
|
||||
|
||||
BeatmapInfo? lastBeatmap = null;
|
||||
BeatmapSetsGroupedTogether = criteria.Sort != SortMode.Difficulty && criteria.Group != GroupMode.Difficulty;
|
||||
|
||||
GroupDefinition? lastGroup = null;
|
||||
CarouselItem? lastGroupItem = null;
|
||||
var groups = getGroups((List<CarouselItem>)items, criteria);
|
||||
|
||||
HashSet<CarouselItem>? currentGroupItems = null;
|
||||
HashSet<CarouselItem>? currentSetItems = null;
|
||||
|
||||
BeatmapSetsGroupedTogether = criteria.Sort != SortMode.Difficulty;
|
||||
|
||||
foreach (var item in items)
|
||||
foreach (var (group, itemsInGroup) in groups)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
var beatmap = (BeatmapInfo)item.Model;
|
||||
CarouselItem? groupItem = null;
|
||||
HashSet<CarouselItem>? currentGroupItems = null;
|
||||
HashSet<CarouselItem>? currentSetItems = null;
|
||||
BeatmapInfo? lastBeatmap = null;
|
||||
|
||||
if (createGroupIfRequired(criteria, beatmap, lastGroup) is GroupDefinition newGroup)
|
||||
if (group != null)
|
||||
{
|
||||
// When reaching a new group, ensure we reset any beatmap set tracking.
|
||||
currentSetItems = null;
|
||||
lastBeatmap = null;
|
||||
groupMap[group] = currentGroupItems = new HashSet<CarouselItem>();
|
||||
|
||||
groupItems[newGroup] = currentGroupItems = new HashSet<CarouselItem>();
|
||||
lastGroup = newGroup;
|
||||
|
||||
addItem(lastGroupItem = new CarouselItem(newGroup)
|
||||
addItem(groupItem = new CarouselItem(group)
|
||||
{
|
||||
DrawHeight = PanelGroup.HEIGHT,
|
||||
DepthLayer = -2,
|
||||
});
|
||||
}
|
||||
|
||||
if (BeatmapSetsGroupedTogether)
|
||||
foreach (var item in itemsInGroup)
|
||||
{
|
||||
bool newBeatmapSet = lastBeatmap?.BeatmapSet!.ID != beatmap.BeatmapSet!.ID;
|
||||
var beatmap = (BeatmapInfo)item.Model;
|
||||
|
||||
if (newBeatmapSet)
|
||||
if (BeatmapSetsGroupedTogether)
|
||||
{
|
||||
setItems[beatmap.BeatmapSet!] = currentSetItems = new HashSet<CarouselItem>();
|
||||
bool newBeatmapSet = lastBeatmap?.BeatmapSet!.ID != beatmap.BeatmapSet!.ID;
|
||||
|
||||
if (lastGroupItem != null)
|
||||
lastGroupItem.NestedItemCount++;
|
||||
|
||||
addItem(new CarouselItem(beatmap.BeatmapSet!)
|
||||
if (newBeatmapSet)
|
||||
{
|
||||
DrawHeight = PanelBeatmapSet.HEIGHT,
|
||||
DepthLayer = -1
|
||||
});
|
||||
if (!setMap.TryGetValue(beatmap.BeatmapSet!, out currentSetItems))
|
||||
setMap[beatmap.BeatmapSet!] = currentSetItems = new HashSet<CarouselItem>();
|
||||
|
||||
if (groupItem != null)
|
||||
groupItem.NestedItemCount++;
|
||||
|
||||
addItem(new CarouselItem(beatmap.BeatmapSet!)
|
||||
{
|
||||
DrawHeight = PanelBeatmapSet.HEIGHT,
|
||||
DepthLayer = -1
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastGroupItem != null)
|
||||
lastGroupItem.NestedItemCount++;
|
||||
else
|
||||
{
|
||||
if (groupItem != null)
|
||||
groupItem.NestedItemCount++;
|
||||
|
||||
item.DrawHeight = PanelBeatmapStandalone.HEIGHT;
|
||||
}
|
||||
item.DrawHeight = PanelBeatmapStandalone.HEIGHT;
|
||||
}
|
||||
|
||||
addItem(item);
|
||||
lastBeatmap = beatmap;
|
||||
addItem(item);
|
||||
lastBeatmap = beatmap;
|
||||
}
|
||||
|
||||
void addItem(CarouselItem i)
|
||||
{
|
||||
@@ -114,7 +114,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
currentGroupItems?.Add(i);
|
||||
currentSetItems?.Add(i);
|
||||
|
||||
i.IsVisible = i.Model is GroupDefinition || (lastGroup == null && (i.Model is BeatmapSetInfo || currentSetItems == null));
|
||||
i.IsVisible = i.Model is GroupDefinition || (group == null && (i.Model is BeatmapSetInfo || currentSetItems == null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,40 +122,208 @@ namespace osu.Game.Screens.SelectV2
|
||||
}, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private GroupDefinition? createGroupIfRequired(FilterCriteria criteria, BeatmapInfo beatmap, GroupDefinition? lastGroup)
|
||||
private List<GroupMapping> getGroups(List<CarouselItem> items, FilterCriteria criteria)
|
||||
{
|
||||
switch (criteria.Group)
|
||||
{
|
||||
case GroupMode.NoGrouping:
|
||||
return new List<GroupMapping> { new GroupMapping(null, items) };
|
||||
|
||||
case GroupMode.Artist:
|
||||
char groupChar = lastGroup?.Data as char? ?? (char)0;
|
||||
char beatmapFirstChar = char.ToUpperInvariant(beatmap.Metadata.Artist[0]);
|
||||
return getGroupsBy(b => defineGroupAlphabetically(b.BeatmapSet!.Metadata.Artist), items);
|
||||
|
||||
if (beatmapFirstChar > groupChar)
|
||||
return new GroupDefinition(beatmapFirstChar, $"{beatmapFirstChar}");
|
||||
case GroupMode.Author:
|
||||
return getGroupsBy(b => defineGroupAlphabetically(b.BeatmapSet!.Metadata.Author.Username), items);
|
||||
|
||||
break;
|
||||
case GroupMode.Title:
|
||||
return getGroupsBy(b => defineGroupAlphabetically(b.BeatmapSet!.Metadata.Title), items);
|
||||
|
||||
case GroupMode.DateAdded:
|
||||
return getGroupsBy(b => defineGroupByDate(b.BeatmapSet!.DateAdded), items);
|
||||
|
||||
case GroupMode.LastPlayed:
|
||||
return getGroupsBy(b =>
|
||||
{
|
||||
DateTimeOffset? maxLastPlayed = aggregateMax(b, items, bb => bb.LastPlayed);
|
||||
|
||||
if (maxLastPlayed == null)
|
||||
return new GroupDefinition(int.MaxValue, "Never");
|
||||
|
||||
return defineGroupByDate(maxLastPlayed.Value);
|
||||
}, items);
|
||||
|
||||
case GroupMode.RankedStatus:
|
||||
return getGroupsBy(b => defineGroupByStatus(b.BeatmapSet!.Status), items);
|
||||
|
||||
case GroupMode.BPM:
|
||||
return getGroupsBy(b =>
|
||||
{
|
||||
double maxBPM = aggregateMax(b, items, bb => bb.BPM);
|
||||
return defineGroupByBPM(maxBPM);
|
||||
}, items);
|
||||
|
||||
case GroupMode.Difficulty:
|
||||
var starGroup = lastGroup?.Data as StarDifficulty? ?? new StarDifficulty(-1, 0);
|
||||
double beatmapStarRating = Math.Round(beatmap.StarRating, 2);
|
||||
return getGroupsBy(b => defineGroupByStars(b.StarRating), items);
|
||||
|
||||
if (beatmapStarRating >= starGroup.Stars + 1)
|
||||
case GroupMode.Length:
|
||||
return getGroupsBy(b =>
|
||||
{
|
||||
starGroup = new StarDifficulty((int)Math.Floor(beatmapStarRating), 0);
|
||||
double maxLength = aggregateMax(b, items, bb => bb.Length);
|
||||
return defineGroupByLength(maxLength);
|
||||
}, items);
|
||||
|
||||
if (starGroup.Stars == 0)
|
||||
return new GroupDefinition(starGroup, "Below 1 Star");
|
||||
case GroupMode.Collections:
|
||||
// TODO: needs implementation
|
||||
goto case GroupMode.NoGrouping;
|
||||
|
||||
if (starGroup.Stars == 1)
|
||||
return new GroupDefinition(starGroup, "1 Star");
|
||||
case GroupMode.Favourites:
|
||||
// TODO: needs implementation
|
||||
goto case GroupMode.NoGrouping;
|
||||
|
||||
return new GroupDefinition(starGroup, $"{starGroup.Stars} Stars");
|
||||
}
|
||||
case GroupMode.MyMaps:
|
||||
// TODO: needs implementation
|
||||
goto case GroupMode.NoGrouping;
|
||||
|
||||
break;
|
||||
case GroupMode.RankAchieved:
|
||||
// TODO: needs implementation
|
||||
goto case GroupMode.NoGrouping;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
private List<GroupMapping> getGroupsBy(Func<BeatmapInfo, GroupDefinition> getGroup, List<CarouselItem> items)
|
||||
{
|
||||
return items.GroupBy(i => getGroup((BeatmapInfo)i.Model))
|
||||
.OrderBy(s => s.Key.Order)
|
||||
.Select(g => new GroupMapping(g.Key, g.ToList()))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private GroupDefinition defineGroupAlphabetically(string name)
|
||||
{
|
||||
char firstChar = name.FirstOrDefault();
|
||||
|
||||
if (char.IsAsciiDigit(firstChar))
|
||||
return new GroupDefinition(int.MinValue, "0-9");
|
||||
|
||||
if (char.IsAsciiLetter(firstChar))
|
||||
return new GroupDefinition(char.ToUpperInvariant(firstChar) - 'A', char.ToUpperInvariant(firstChar).ToString());
|
||||
|
||||
return new GroupDefinition(int.MaxValue, "Other");
|
||||
}
|
||||
|
||||
private GroupDefinition defineGroupByDate(DateTimeOffset date)
|
||||
{
|
||||
var now = DateTimeOffset.Now;
|
||||
var elapsed = now - date;
|
||||
|
||||
if (elapsed.TotalDays < 1)
|
||||
return new GroupDefinition(0, "Today");
|
||||
|
||||
if (elapsed.TotalDays < 2)
|
||||
return new GroupDefinition(1, "Yesterday");
|
||||
|
||||
if (elapsed.TotalDays < 7)
|
||||
return new GroupDefinition(2, "Last week");
|
||||
|
||||
if (elapsed.TotalDays < 30)
|
||||
return new GroupDefinition(3, "1 month ago");
|
||||
|
||||
for (int i = 60; i <= 150; i += 30)
|
||||
{
|
||||
if (elapsed.TotalDays < i)
|
||||
return new GroupDefinition(i, $"{i / 30} months ago");
|
||||
}
|
||||
|
||||
return null;
|
||||
return new GroupDefinition(151, "Over 5 months ago");
|
||||
}
|
||||
|
||||
private GroupDefinition defineGroupByStatus(BeatmapOnlineStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case BeatmapOnlineStatus.Ranked:
|
||||
case BeatmapOnlineStatus.Approved:
|
||||
return new GroupDefinition(0, BeatmapOnlineStatus.Ranked.GetDescription());
|
||||
|
||||
case BeatmapOnlineStatus.Qualified:
|
||||
return new GroupDefinition(1, status.GetDescription());
|
||||
|
||||
case BeatmapOnlineStatus.WIP:
|
||||
return new GroupDefinition(2, status.GetDescription());
|
||||
|
||||
case BeatmapOnlineStatus.Pending:
|
||||
return new GroupDefinition(3, status.GetDescription());
|
||||
|
||||
case BeatmapOnlineStatus.Graveyard:
|
||||
return new GroupDefinition(4, status.GetDescription());
|
||||
|
||||
case BeatmapOnlineStatus.LocallyModified:
|
||||
return new GroupDefinition(5, status.GetDescription());
|
||||
|
||||
case BeatmapOnlineStatus.None:
|
||||
return new GroupDefinition(6, status.GetDescription());
|
||||
|
||||
case BeatmapOnlineStatus.Loved:
|
||||
return new GroupDefinition(7, status.GetDescription());
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(status), status, null);
|
||||
}
|
||||
}
|
||||
|
||||
private GroupDefinition defineGroupByBPM(double bpm)
|
||||
{
|
||||
for (int i = 1; i < 6; i++)
|
||||
{
|
||||
if (bpm < i * 60)
|
||||
return new GroupDefinition(i, $"Under {i * 60} BPM");
|
||||
}
|
||||
|
||||
return new GroupDefinition(6, "Over 300 BPM");
|
||||
}
|
||||
|
||||
private GroupDefinition defineGroupByStars(double stars)
|
||||
{
|
||||
int starInt = (int)Math.Round(stars, 2);
|
||||
var starDifficulty = new StarDifficulty(starInt, 0);
|
||||
|
||||
if (starInt == 0)
|
||||
return new StarDifficultyGroupDefinition(0, "Below 1 Star", starDifficulty);
|
||||
|
||||
if (starInt == 1)
|
||||
return new StarDifficultyGroupDefinition(1, "1 Star", starDifficulty);
|
||||
|
||||
return new StarDifficultyGroupDefinition(starInt, $"{starInt} Stars", starDifficulty);
|
||||
}
|
||||
|
||||
private GroupDefinition defineGroupByLength(double length)
|
||||
{
|
||||
for (int i = 1; i < 6; i++)
|
||||
{
|
||||
if (length <= i * 60_000)
|
||||
{
|
||||
if (i == 1)
|
||||
return new GroupDefinition(1, "1 minute or less");
|
||||
|
||||
return new GroupDefinition(i, $"{i} minutes or less");
|
||||
}
|
||||
}
|
||||
|
||||
if (length <= 10 * 60_000)
|
||||
return new GroupDefinition(10, "10 minutes or less");
|
||||
|
||||
return new GroupDefinition(11, "Over 10 minutes");
|
||||
}
|
||||
|
||||
private static T? aggregateMax<T>(BeatmapInfo b, IEnumerable<CarouselItem> items, Func<BeatmapInfo, T> func)
|
||||
{
|
||||
var matchedBeatmaps = items.Select(i => i.Model).Cast<BeatmapInfo>().Where(beatmap => beatmap.BeatmapSet!.Equals(b.BeatmapSet));
|
||||
return matchedBeatmaps.Max(func);
|
||||
}
|
||||
|
||||
private record GroupMapping(GroupDefinition? Group, List<CarouselItem> ItemsInGroup);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,7 +185,7 @@ namespace osu.Game.Screens.SelectV2
|
||||
difficultyRangeSlider.UpperBound = config.GetBindable<double>(OsuSetting.DisplayStarsMaximum);
|
||||
config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConvertedBeatmapsButton.Active);
|
||||
config.BindWith(OsuSetting.SongSelectSortingMode, sortDropdown.Current);
|
||||
config.BindWith(OsuSetting.SongSelectGroupingMode, groupDropdown.Current);
|
||||
config.BindWith(OsuSetting.SongSelectGroupMode, groupDropdown.Current);
|
||||
|
||||
ruleset.BindValueChanged(_ => updateCriteria());
|
||||
mods.BindValueChanged(m =>
|
||||
|
||||
@@ -47,9 +47,15 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
protected Container Content { get; private set; } = null!;
|
||||
|
||||
public Drawable Background { set => backgroundContainer.Child = value; }
|
||||
public Drawable Background
|
||||
{
|
||||
set => backgroundContainer.Child = value;
|
||||
}
|
||||
|
||||
public Drawable Icon { set => iconContainer.Child = value; }
|
||||
public Drawable Icon
|
||||
{
|
||||
set => iconContainer.Child = value;
|
||||
}
|
||||
|
||||
private Color4? accentColour;
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Backgrounds;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@@ -142,9 +141,8 @@ namespace osu.Game.Screens.SelectV2
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
var group = (GroupDefinition)Item.Model;
|
||||
var stars = (StarDifficulty)group.Data;
|
||||
int starNumber = (int)stars.Stars;
|
||||
var group = (StarDifficultyGroupDefinition)Item.Model;
|
||||
int starNumber = (int)group.Difficulty.Stars;
|
||||
|
||||
ratingColour = starNumber >= 9 ? OsuColour.Gray(0.2f) : colours.ForStarDifficulty(starNumber);
|
||||
|
||||
|
||||
@@ -58,7 +58,10 @@ namespace osu.Game.Utils
|
||||
Logger.NewEntry += processLogEntry;
|
||||
}
|
||||
|
||||
~SentryLogger() => Dispose(false);
|
||||
~SentryLogger()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public void AttachUser(IBindable<APIUser> user)
|
||||
{
|
||||
|
||||
+2
-1
@@ -304,7 +304,7 @@
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_BLOCK_STATEMENTS/@EntryValue">1</s:Int64>
|
||||
<s:Int64 x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/BLANK_LINES_BEFORE_CASE/@EntryValue">1</s:Int64>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/CASE_BLOCK_BRACES/@EntryValue">NEXT_LINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">MULTILINE</s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/EMPTY_BLOCK_STYLE/@EntryValue">DO_NOT_CHANGE</s:String>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_FOR_STMT/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_FOREACH_STMT/@EntryValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/INDENT_NESTED_LOCK_STMT/@EntryValue">True</s:Boolean>
|
||||
@@ -781,6 +781,7 @@ See the LICENCE file in the repository root for full licence text.
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=StaticReadonly/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue"><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=0c4c6401_002D2a1f_002D4db1_002Da21f_002D562f51542cf8/@EntryIndexedValue"><Policy><Descriptor Staticness="Any" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected, FileLocal" Description="Events"><ElementKinds><Kind Name="EVENT" /></ElementKinds></Descriptor><Policy Inspect="True" WarnAboutPrefixesAndSuffixes="False" Prefix="" Suffix="" Style="AaBb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue"><Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=236f7aa5_002D7b06_002D43ca_002Dbf2a_002D9b31bfcff09a/@EntryIndexedValue"><Policy><Descriptor Staticness="Any" AccessRightKinds="Private" Description="Constant fields (private)"><ElementKinds><Kind Name="CONSTANT_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /></Policy></s:String>
|
||||
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=2c62818f_002D621b_002D4425_002Dadc9_002D78611099bfcb/@EntryIndexedValue"><Policy><Descriptor Staticness="Any" AccessRightKinds="Any" Description="Type parameters"><ElementKinds><Kind Name="TYPE_PARAMETER" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /></Policy></s:String>
|
||||
|
||||
Reference in New Issue
Block a user