Skip to content

Commit 318afba

Browse files
committed
allow beatmap conversion from non-std rulesets
Supersedes ppy#23875, fix ppy#9263 Gives the ruleset full control which maps to show both with and without converts enabled - e.g. a competitive/harder version ruleset such as for mania could make mania maps show without enabling converts, as well as control which ones to support only if conversions are enabled in the settings. There is a CanConvert function in BeatmapConverter which I considered using as well, however it looks like this beatmap converter isn't directly accessible in the carousel and adding it may be quite severe on performance impact. Also consider for example generally mania->taiko converts might be good to have, but a quality control function inside CanConvert could make the conversion fail if it's unplayable, so two different interfaces make more sense in that case. This way, maps may also show if they fail in conversion, but playing them will show a notification that the maps aren't playable. For the future an async task that just goes through maps to try and see if they are convertible for different modes and caches that might be useful. I did not change how the default rulesets behave, this is just for custom rulesets to be able to play more maps and start off with a larger pool of maps, which I think is an important part of custom ruleset adoption. I adjusted the scrolling example ruleset (pippidon) to include this as well.
1 parent 5832ba8 commit 318afba

File tree

9 files changed

+123
-5
lines changed

9 files changed

+123
-5
lines changed

Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/Beatmaps/PippidonBeatmapConverter.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
2828
}
2929
}
3030

31-
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition && h is IHasYPosition);
31+
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is IHasYPosition);
3232

3333
protected override IEnumerable<PippidonHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken)
3434
{
@@ -43,6 +43,6 @@ protected override IEnumerable<PippidonHitObject> ConvertHitObject(HitObject ori
4343
private int getLane(HitObject hitObject) => (int)Math.Clamp(
4444
(getUsablePosition(hitObject) - minPosition) / (maxPosition - minPosition) * PippidonPlayfield.LANE_COUNT, 0, PippidonPlayfield.LANE_COUNT - 1);
4545

46-
private float getUsablePosition(HitObject h) => (h as IHasYPosition)?.Y ?? ((IHasXPosition)h).X;
46+
private float getUsablePosition(HitObject h) => (h as IHasXPosition)?.X ?? ((IHasYPosition)h).Y;
4747
}
4848
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using osu.Game.Rulesets.Filter;
5+
6+
namespace osu.Game.Rulesets.Pippidon.Beatmaps
7+
{
8+
public class PippidonRulesetConversionSupport : IRulesetConvertSupport
9+
{
10+
public bool CanBePlayed(RulesetInfo ruleset, bool conversionEnabled)
11+
{
12+
// Always show ctb maps even without converts, since extensive playtesting has shown that the maps match
13+
// pippidon very well and we also don't have many maps to start out with yet.
14+
// Show std only when converts are enabled
15+
return ruleset.ShortName == "pippidon" || ruleset.ShortName == "fruits" ||
16+
(conversionEnabled && ruleset.ShortName == "osu");
17+
}
18+
}
19+
}

Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon/PippidonRuleset.cs

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using osu.Framework.Input.Bindings;
88
using osu.Game.Beatmaps;
99
using osu.Game.Rulesets.Difficulty;
10+
using osu.Game.Rulesets.Filter;
1011
using osu.Game.Rulesets.Mods;
1112
using osu.Game.Rulesets.Pippidon.Beatmaps;
1213
using osu.Game.Rulesets.Pippidon.Mods;
@@ -37,6 +38,8 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
3738
}
3839
}
3940

41+
public override IRulesetConvertSupport GetRulesetMapConvertSupport() => new PippidonRulesetConversionSupport();
42+
4043
public override string ShortName => "pippidon";
4144

4245
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]

osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs

+54
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// See the LICENCE file in the repository root for full licence text.
33

44
using System.Collections.Generic;
5+
using System.Linq;
56
using NUnit.Framework;
67
using osu.Framework.Bindables;
78
using osu.Game.Beatmaps;
@@ -99,6 +100,59 @@ public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets()
99100
Assert.IsFalse(carouselItem.Filtered.Value);
100101
}
101102

103+
[Test]
104+
[TestCase(true)]
105+
[TestCase(false)]
106+
public void TestCriteriaMatchingCustomConvertRules(bool convertsStd)
107+
{
108+
var exampleBeatmapInfo = getExampleBeatmap();
109+
var criteria = new FilterCriteria
110+
{
111+
Ruleset = new RulesetInfo { OnlineID = -1, ShortName = "custom" },
112+
AllowConvertedBeatmaps = true,
113+
RulesetConvertSupport = new CustomConvertSupport(["custom"], convertsStd ? ["osu"] : [])
114+
};
115+
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
116+
carouselItem.Filter(criteria);
117+
Assert.AreEqual(!convertsStd, carouselItem.Filtered.Value);
118+
}
119+
120+
[Test]
121+
[TestCase(true)]
122+
[TestCase(false)]
123+
public void TestCriteriaMatchingCustomNativeConvertRules(bool allowConverts)
124+
{
125+
var exampleBeatmapInfo = getExampleBeatmap();
126+
var criteria = new FilterCriteria
127+
{
128+
Ruleset = new RulesetInfo { OnlineID = -1, ShortName = "custom" },
129+
AllowConvertedBeatmaps = allowConverts,
130+
RulesetConvertSupport = new CustomConvertSupport(["custom", "osu"], [])
131+
};
132+
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
133+
carouselItem.Filter(criteria);
134+
Assert.IsFalse(carouselItem.Filtered.Value);
135+
}
136+
137+
private class CustomConvertSupport : IRulesetConvertSupport
138+
{
139+
private readonly string[] nativeFormats, convertFormats;
140+
141+
public CustomConvertSupport(string[] nativeFormats, string[] convertFormats)
142+
{
143+
this.nativeFormats = nativeFormats;
144+
this.convertFormats = convertFormats;
145+
}
146+
147+
public bool CanBePlayed(RulesetInfo ruleset, bool conversionEnabled)
148+
{
149+
if (conversionEnabled && convertFormats.Contains(ruleset.ShortName))
150+
return true;
151+
152+
return nativeFormats.Contains(ruleset.ShortName);
153+
}
154+
}
155+
102156
[Test]
103157
[TestCase(true)]
104158
[TestCase(false)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using osu.Game.Screens.Select;
5+
6+
namespace osu.Game.Rulesets.Filter
7+
{
8+
/// <summary>
9+
/// Allows for changing which beatmap rulesets are displayed in song select (as implemented in <see cref="FilterCriteria"/>)
10+
/// with ruleset-specific criteria.
11+
/// </summary>
12+
public interface IRulesetConvertSupport
13+
{
14+
/// <summary>
15+
/// Checks whether maps from the supplied <paramref name="ruleset"/> may be played with this ruleset with or
16+
/// without beatmap conversion enabled.
17+
/// </summary>
18+
/// <param name="ruleset">The foreign ruleset to check if it may be played.</param>
19+
/// <param name="conversionEnabled">Indicates if the player wants converts or not.</param>
20+
/// <returns>
21+
/// <c>true</c> if the beatmap can be played and should be shown in the beatmap list,
22+
/// <c>false</c> otherwise.
23+
/// </returns>
24+
bool CanBePlayed(RulesetInfo ruleset, bool conversionEnabled);
25+
}
26+
}

osu.Game/Rulesets/Ruleset.cs

+7
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,13 @@ protected Ruleset()
395395
/// </summary>
396396
public virtual IRulesetFilterCriteria? CreateRulesetFilterCriteria() => null;
397397

398+
/// <summary>
399+
/// Creates ruleset-specific conversion support, used in filtering which beatmaps to show based on selected ruleset.
400+
/// This interface describes which maps from other rulesets may be used without beatmap conversion enabled and which ones may be used when beatmap conversion is enabled.
401+
/// If not set, osu treats this ruleset as the only native format and osu standard as only ruleset that maps can be converted from.
402+
/// </summary>
403+
public virtual IRulesetConvertSupport? GetRulesetMapConvertSupport() => null;
404+
398405
/// <summary>
399406
/// Can be overridden to add ruleset-specific sections to the editor beatmap setup screen.
400407
/// </summary>

osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ private bool checkMatch(FilterCriteria criteria)
3333
{
3434
bool match =
3535
criteria.Ruleset == null ||
36-
BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName ||
37-
(BeatmapInfo.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps);
36+
(criteria.RulesetConvertSupport?.CanBePlayed(BeatmapInfo.Ruleset, criteria.AllowConvertedBeatmaps) ??
37+
(BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName ||
38+
(BeatmapInfo.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps)));
3839

3940
if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true)
4041
{

osu.Game/Screens/Select/FilterControl.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ public virtual FilterCriteria CreateCriteria()
7777
if (!maximumStars.IsDefault)
7878
criteria.UserStarDifficulty.Max = maximumStars.Value;
7979

80-
criteria.RulesetCriteria = ruleset.Value.CreateInstance().CreateRulesetFilterCriteria();
80+
var rulesetInstance = ruleset.Value.CreateInstance();
81+
criteria.RulesetCriteria = rulesetInstance.CreateRulesetFilterCriteria();
82+
criteria.RulesetConvertSupport = rulesetInstance.GetRulesetMapConvertSupport();
8183

8284
FilterQueryParser.ApplyQueries(criteria, query);
8385
return criteria;

osu.Game/Screens/Select/FilterCriteria.cs

+6
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,12 @@ public string SearchText
122122

123123
public IRulesetFilterCriteria? RulesetCriteria { get; set; }
124124

125+
/// <summary>
126+
/// If set, overrides which maps can be played and should be shown, instead of only showing maps from our own
127+
/// ruleset and std with map conversion enabled.
128+
/// </summary>
129+
public IRulesetConvertSupport? RulesetConvertSupport { get; set; }
130+
125131
public readonly struct OptionalSet<T> : IEquatable<OptionalSet<T>>
126132
where T : struct, Enum
127133
{

0 commit comments

Comments
 (0)