1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Merge pull request #27214 from Givikap120/freemod_mapinfo_fix

Fix mod selection in online-play rooms not accounting for mods of selected item
This commit is contained in:
Bartłomiej Dach 2024-03-07 12:45:14 +01:00 committed by GitHub
commit fba44e67a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 140 additions and 27 deletions

View File

@ -22,6 +22,7 @@ using osu.Game.Overlays;
using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko;
@ -286,6 +287,41 @@ namespace osu.Game.Tests.Visual.Multiplayer
}); });
} }
[Test]
[FlakyTest] // See above
public void TestModSelectOverlay()
{
AddStep("add playlist item", () =>
{
SelectedRoom.Value.Playlist.Add(new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo)
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
RequiredMods = new[]
{
new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 2.0 } }),
new APIMod(new OsuModStrictTracking()),
},
AllowedMods = new[]
{
new APIMod(new OsuModFlashlight()),
}
});
});
ClickButtonWhenEnabled<MultiplayerMatchSettingsOverlay.CreateOrUpdateButton>();
AddUntilStep("wait for join", () => RoomJoined);
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
AddAssert("mod select shows unranked", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().Ranked.Value == false);
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.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());
AddAssert("score multiplier = 1.35", () => screen.UserModsSelectOverlay.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);
AddAssert("score multiplier = 1.20", () => screen.UserModsSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(1.2).Within(0.01));
}
private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen private partial class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen
{ {
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]

View File

@ -57,6 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Mods = { BindTarget = SelectedMods },
}); });
AddStep("set beatmap", () => AddStep("set beatmap", () =>

View File

@ -40,8 +40,7 @@ namespace osu.Game.Overlays.Mods
public Bindable<IBeatmapInfo?> BeatmapInfo { get; } = new Bindable<IBeatmapInfo?>(); public Bindable<IBeatmapInfo?> BeatmapInfo { get; } = new Bindable<IBeatmapInfo?>();
[Resolved] public Bindable<IReadOnlyList<Mod>> Mods { get; } = new Bindable<IReadOnlyList<Mod>>();
private Bindable<IReadOnlyList<Mod>> mods { get; set; } = null!;
public BindableBool Collapsed { get; } = new BindableBool(true); public BindableBool Collapsed { get; } = new BindableBool(true);
@ -53,7 +52,7 @@ namespace osu.Game.Overlays.Mods
[Resolved] [Resolved]
private OsuGameBase game { get; set; } = null!; private OsuGameBase game { get; set; } = null!;
private IBindable<RulesetInfo> gameRuleset = null!; protected IBindable<RulesetInfo> GameRuleset = null!;
private CancellationTokenSource? cancellationSource; private CancellationTokenSource? cancellationSource;
private IBindable<StarDifficulty?> starDifficulty = null!; private IBindable<StarDifficulty?> starDifficulty = null!;
@ -101,15 +100,15 @@ namespace osu.Game.Overlays.Mods
{ {
base.LoadComplete(); base.LoadComplete();
mods.BindValueChanged(_ => Mods.BindValueChanged(_ =>
{ {
modSettingChangeTracker?.Dispose(); modSettingChangeTracker?.Dispose();
modSettingChangeTracker = new ModSettingChangeTracker(mods.Value); modSettingChangeTracker = new ModSettingChangeTracker(Mods.Value);
modSettingChangeTracker.SettingChanged += _ => updateValues(); modSettingChangeTracker.SettingChanged += _ => updateValues();
updateValues(); updateValues();
}, true); }, true);
BeatmapInfo.BindValueChanged(_ => updateValues(), true); BeatmapInfo.BindValueChanged(_ => updateValues());
Collapsed.BindValueChanged(_ => Collapsed.BindValueChanged(_ =>
{ {
@ -118,11 +117,12 @@ namespace osu.Game.Overlays.Mods
updateCollapsedState(); updateCollapsedState();
}); });
gameRuleset = game.Ruleset.GetBoundCopy(); GameRuleset = game.Ruleset.GetBoundCopy();
gameRuleset.BindValueChanged(_ => updateValues()); GameRuleset.BindValueChanged(_ => updateValues());
BeatmapInfo.BindValueChanged(_ => updateValues(), true); BeatmapInfo.BindValueChanged(_ => updateValues());
updateValues();
updateCollapsedState(); updateCollapsedState();
} }
@ -166,17 +166,17 @@ namespace osu.Game.Overlays.Mods
}); });
double rate = 1; double rate = 1;
foreach (var mod in mods.Value.OfType<IApplicableToRate>()) foreach (var mod in Mods.Value.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate); rate = mod.ApplyToRate(0, rate);
bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate); bpmDisplay.Current.Value = FormatUtils.RoundBPM(BeatmapInfo.Value.BPM, rate);
BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty); BeatmapDifficulty originalDifficulty = new BeatmapDifficulty(BeatmapInfo.Value.Difficulty);
foreach (var mod in mods.Value.OfType<IApplicableToDifficulty>()) foreach (var mod in Mods.Value.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(originalDifficulty); mod.ApplyToDifficulty(originalDifficulty);
Ruleset ruleset = gameRuleset.Value.CreateInstance(); Ruleset ruleset = GameRuleset.Value.CreateInstance();
BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate); BeatmapDifficulty adjustedDifficulty = ruleset.GetRateAdjustedDisplayDifficulty(originalDifficulty, rate);
TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty); TooltipContent = new AdjustedAttributesTooltip.Data(originalDifficulty, adjustedDifficulty);
@ -195,7 +195,7 @@ namespace osu.Game.Overlays.Mods
RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint); RightContent.FadeTo(Collapsed.Value && !IsHovered ? 0 : 1, transition_duration, Easing.OutQuint);
} }
private partial class BPMDisplay : RollingCounter<int> public partial class BPMDisplay : RollingCounter<int>
{ {
protected override double RollingDuration => 250; protected override double RollingDuration => 250;

View File

@ -43,6 +43,14 @@ namespace osu.Game.Overlays.Mods
[Cached] [Cached]
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>()); public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// Contains a list of mods which <see cref="ModSelectOverlay"/> should read from to display effects on the selected beatmap.
/// </summary>
/// <remarks>
/// This is different from <see cref="SelectedMods"/> in screens like online-play rooms, where there are required mods activated from the playlist.
/// </remarks>
public Bindable<IReadOnlyList<Mod>> ActiveMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary> /// <summary>
/// Contains a dictionary with the current <see cref="ModState"/> of all mods applicable for the current ruleset. /// Contains a dictionary with the current <see cref="ModState"/> of all mods applicable for the current ruleset.
/// </summary> /// </summary>
@ -97,6 +105,8 @@ namespace osu.Game.Overlays.Mods
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection; protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
protected virtual IReadOnlyList<Mod> ComputeActiveMods() => SelectedMods.Value;
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() protected virtual IEnumerable<ShearedButton> CreateFooterButtons()
{ {
if (AllowCustomisation) if (AllowCustomisation)
@ -279,7 +289,7 @@ namespace osu.Game.Overlays.Mods
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
BeatmapInfo = { Value = beatmap?.BeatmapInfo } BeatmapInfo = { Value = Beatmap?.BeatmapInfo },
}, },
} }
}); });
@ -316,20 +326,26 @@ namespace osu.Game.Overlays.Mods
SelectedMods.BindValueChanged(_ => SelectedMods.BindValueChanged(_ =>
{ {
updateRankingInformation();
updateFromExternalSelection(); updateFromExternalSelection();
updateCustomisation(); updateCustomisation();
ActiveMods.Value = ComputeActiveMods();
}, true);
ActiveMods.BindValueChanged(_ =>
{
updateOverlayInformation();
modSettingChangeTracker?.Dispose(); modSettingChangeTracker?.Dispose();
if (AllowCustomisation) if (AllowCustomisation)
{ {
// Importantly, use SelectedMods.Value here (and not the ValueChanged NewValue) as the latter can // Importantly, use ActiveMods.Value here (and not the ValueChanged NewValue) as the latter can
// potentially be stale, due to complexities in the way change trackers work. // potentially be stale, due to complexities in the way change trackers work.
// //
// See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988 // See https://github.com/ppy/osu/pull/23284#issuecomment-1529056988
modSettingChangeTracker = new ModSettingChangeTracker(SelectedMods.Value); modSettingChangeTracker = new ModSettingChangeTracker(ActiveMods.Value);
modSettingChangeTracker.SettingChanged += _ => updateRankingInformation(); modSettingChangeTracker.SettingChanged += _ => updateOverlayInformation();
} }
}, true); }, true);
@ -454,18 +470,25 @@ namespace osu.Game.Overlays.Mods
modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); modState.ValidForSelection.Value = modState.Mod.Type != ModType.System && modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
} }
private void updateRankingInformation() /// <summary>
/// Updates any information displayed on the overlay regarding the effects of the active mods.
/// This reads from <see cref="ActiveMods"/> instead of <see cref="SelectedMods"/>.
/// </summary>
private void updateOverlayInformation()
{ {
if (rankingInformationDisplay == null) if (rankingInformationDisplay != null)
return; {
double multiplier = 1.0;
double multiplier = 1.0; foreach (var mod in ActiveMods.Value)
multiplier *= mod.ScoreMultiplier;
foreach (var mod in SelectedMods.Value) rankingInformationDisplay.ModMultiplier.Value = multiplier;
multiplier *= mod.ScoreMultiplier; rankingInformationDisplay.Ranked.Value = ActiveMods.Value.All(m => m.Ranked);
}
rankingInformationDisplay.ModMultiplier.Value = multiplier; if (beatmapAttributesDisplay != null)
rankingInformationDisplay.Ranked.Value = SelectedMods.Value.All(m => m.Ranked); beatmapAttributesDisplay.Mods.Value = ActiveMods.Value;
} }
private void updateCustomisation() private void updateCustomisation()

View File

@ -0,0 +1,53 @@
// 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.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens.OnlinePlay.Match
{
public partial class RoomModSelectOverlay : UserModSelectOverlay
{
[Resolved]
private IBindable<PlaylistItem> selectedItem { get; set; } = null!;
[Resolved]
private RulesetStore rulesets { get; set; } = null!;
private readonly List<Mod> roomRequiredMods = new List<Mod>();
public RoomModSelectOverlay()
: base(OverlayColourScheme.Plum)
{
}
protected override void LoadComplete()
{
base.LoadComplete();
selectedItem.BindValueChanged(v =>
{
roomRequiredMods.Clear();
if (v.NewValue is PlaylistItem item)
{
var rulesetInstance = rulesets.GetRuleset(item.RulesetID)?.CreateInstance();
Debug.Assert(rulesetInstance != null);
roomRequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance)));
}
ActiveMods.Value = ComputeActiveMods();
}, true);
}
protected override IReadOnlyList<Mod> ComputeActiveMods() => roomRequiredMods.Concat(base.ComputeActiveMods()).ToList();
}
}

View File

@ -241,7 +241,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
} }
}; };
LoadComponent(UserModsSelectOverlay = new UserModSelectOverlay(OverlayColourScheme.Plum) LoadComponent(UserModsSelectOverlay = new RoomModSelectOverlay
{ {
SelectedMods = { BindTarget = UserMods }, SelectedMods = { BindTarget = UserMods },
IsValidMod = _ => false IsValidMod = _ => false