Skip to content

Allow beatmap conversion from non-std rulesets #32535

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public PippidonBeatmapConverter(IBeatmap beatmap, Ruleset ruleset)
}
}

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

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

private float getUsablePosition(HitObject h) => (h as IHasYPosition)?.Y ?? ((IHasXPosition)h).X;
private float getUsablePosition(HitObject h) => (h as IHasXPosition)?.X ?? ((IHasYPosition)h).Y;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Game.Rulesets.Filter;

namespace osu.Game.Rulesets.Pippidon.Beatmaps
{
public class PippidonRulesetConversionSupport : IRulesetConvertSupport
{
public bool CanBePlayed(RulesetInfo ruleset, bool conversionEnabled)
{
// Always show ctb maps even without converts, since extensive playtesting has shown that the maps match
// pippidon very well and we also don't have many maps to start out with yet.
// Show std only when converts are enabled
return ruleset.ShortName == "pippidon" || ruleset.ShortName == "fruits" ||
(conversionEnabled && ruleset.ShortName == "osu");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Filter;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Pippidon.Beatmaps;
using osu.Game.Rulesets.Pippidon.Mods;
Expand Down Expand Up @@ -37,6 +38,8 @@ public override IEnumerable<Mod> GetModsFor(ModType type)
}
}

public override IRulesetConvertSupport GetRulesetMapConvertSupport() => new PippidonRulesetConversionSupport();

public override string ShortName => "pippidon";

public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
Expand Down
54 changes: 54 additions & 0 deletions osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// 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.Bindables;
using osu.Game.Beatmaps;
Expand Down Expand Up @@ -99,6 +100,59 @@ public void TestCriteriaMatchingConvertedBeatmapsForCustomRulesets()
Assert.IsFalse(carouselItem.Filtered.Value);
}

[Test]
[TestCase(true)]
[TestCase(false)]
public void TestCriteriaMatchingCustomConvertRules(bool convertsStd)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { OnlineID = -1, ShortName = "custom" },
AllowConvertedBeatmaps = true,
RulesetConvertSupport = new CustomConvertSupport(["custom"], convertsStd ? ["osu"] : [])
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(!convertsStd, carouselItem.Filtered.Value);
}

[Test]
[TestCase(true)]
[TestCase(false)]
public void TestCriteriaMatchingCustomNativeConvertRules(bool allowConverts)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Ruleset = new RulesetInfo { OnlineID = -1, ShortName = "custom" },
AllowConvertedBeatmaps = allowConverts,
RulesetConvertSupport = new CustomConvertSupport(["custom", "osu"], [])
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.IsFalse(carouselItem.Filtered.Value);
}

private class CustomConvertSupport : IRulesetConvertSupport
{
private readonly string[] nativeFormats, convertFormats;

public CustomConvertSupport(string[] nativeFormats, string[] convertFormats)
{
this.nativeFormats = nativeFormats;
this.convertFormats = convertFormats;
}

public bool CanBePlayed(RulesetInfo ruleset, bool conversionEnabled)
{
if (conversionEnabled && convertFormats.Contains(ruleset.ShortName))
return true;

return nativeFormats.Contains(ruleset.ShortName);
}
}

[Test]
[TestCase(true)]
[TestCase(false)]
Expand Down
26 changes: 26 additions & 0 deletions osu.Game/Rulesets/Filter/IRulesetConvertSupport.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using osu.Game.Screens.Select;

namespace osu.Game.Rulesets.Filter
{
/// <summary>
/// Allows for changing which beatmap rulesets are displayed in song select (as implemented in <see cref="FilterCriteria"/>)
/// with ruleset-specific criteria.
/// </summary>
public interface IRulesetConvertSupport
{
/// <summary>
/// Checks whether maps from the supplied <paramref name="ruleset"/> may be played with this ruleset with or
/// without beatmap conversion enabled.
/// </summary>
/// <param name="ruleset">The foreign ruleset to check if it may be played.</param>
/// <param name="conversionEnabled">Indicates if the player wants converts or not.</param>
/// <returns>
/// <c>true</c> if the beatmap can be played and should be shown in the beatmap list,
/// <c>false</c> otherwise.
/// </returns>
bool CanBePlayed(RulesetInfo ruleset, bool conversionEnabled);
}
}
7 changes: 7 additions & 0 deletions osu.Game/Rulesets/Ruleset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,13 @@ protected Ruleset()
/// </summary>
public virtual IRulesetFilterCriteria? CreateRulesetFilterCriteria() => null;

/// <summary>
/// Creates ruleset-specific conversion support, used in filtering which beatmaps to show based on selected ruleset.
/// 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.
/// If not set, osu treats this ruleset as the only native format and osu standard as only ruleset that maps can be converted from.
/// </summary>
public virtual IRulesetConvertSupport? GetRulesetMapConvertSupport() => null;

/// <summary>
/// Can be overridden to add ruleset-specific sections to the editor beatmap setup screen.
/// </summary>
Expand Down
6 changes: 4 additions & 2 deletions osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ private bool checkMatch(FilterCriteria criteria)
{
bool match =
criteria.Ruleset == null ||
BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName ||
(BeatmapInfo.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 && criteria.AllowConvertedBeatmaps);
(criteria.RulesetConvertSupport?.CanBePlayed(BeatmapInfo.Ruleset, criteria.AllowConvertedBeatmaps) ??
(BeatmapInfo.Ruleset.ShortName == criteria.Ruleset.ShortName ||
(BeatmapInfo.Ruleset.OnlineID == 0 && criteria.Ruleset.OnlineID != 0 &&
criteria.AllowConvertedBeatmaps)));

if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true)
{
Expand Down
4 changes: 3 additions & 1 deletion osu.Game/Screens/Select/FilterControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@ public virtual FilterCriteria CreateCriteria()
if (!maximumStars.IsDefault)
criteria.UserStarDifficulty.Max = maximumStars.Value;

criteria.RulesetCriteria = ruleset.Value.CreateInstance().CreateRulesetFilterCriteria();
var rulesetInstance = ruleset.Value.CreateInstance();
criteria.RulesetCriteria = rulesetInstance.CreateRulesetFilterCriteria();
criteria.RulesetConvertSupport = rulesetInstance.GetRulesetMapConvertSupport();

FilterQueryParser.ApplyQueries(criteria, query);
return criteria;
Expand Down
6 changes: 6 additions & 0 deletions osu.Game/Screens/Select/FilterCriteria.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ public string SearchText

public IRulesetFilterCriteria? RulesetCriteria { get; set; }

/// <summary>
/// If set, overrides which maps can be played and should be shown, instead of only showing maps from our own
/// ruleset and std with map conversion enabled.
/// </summary>
public IRulesetConvertSupport? RulesetConvertSupport { get; set; }

public readonly struct OptionalSet<T> : IEquatable<OptionalSet<T>>
where T : struct, Enum
{
Expand Down