1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 20:03:22 +08:00

Move available mods to global context (#7172)

Move available mods to global context

Co-authored-by: Dan Balasescu <smoogipoo@smgi.me>
This commit is contained in:
Dean Herbert 2019-12-13 22:16:53 +09:00 committed by GitHub
commit 5a8b4113a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 134 additions and 89 deletions

View File

@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Catch.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return base.CreatePlayer(ruleset);
}
}

View File

@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Catch.Tests
private void addToPlayfield(DrawableCatchHitObject drawable)
{
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawableRuleset.Playfield.Add(drawable);

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Tests
[SetUp]
public void SetUp() => Schedule(() =>
{
Mods.Value = new[] { new CatchModHidden() };
SelectedMods.Value = new[] { new CatchModHidden() };
});
}
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableHitCircle(circle, auto);
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
return drawable;

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[SetUp]
public void SetUp() => Schedule(() =>
{
Mods.Value = new[] { new OsuModHidden() };
SelectedMods.Value = new[] { new OsuModHidden() };
});
}
}

View File

@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
SelectedMods.Value = new Mod[] { new OsuModAutoplay(), new OsuModFlashlight(), };
return base.CreatePlayer(ruleset);
}

View File

@ -362,7 +362,7 @@ namespace osu.Game.Rulesets.Osu.Tests
var drawable = CreateDrawableSlider(slider);
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
drawable.OnNewResult += onNewResult;

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[SetUp]
public void SetUp() => Schedule(() =>
{
Mods.Value = new[] { new OsuModHidden() };
SelectedMods.Value = new[] { new OsuModHidden() };
});
}
}

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Tests
Depth = depthIndex++
};
foreach (var mod in Mods.Value.OfType<IApplicableToDrawableHitObjects>())
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObjects>())
mod.ApplyToDrawableHitObjects(new[] { drawable });
Add(drawable);

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests
[SetUp]
public void SetUp() => Schedule(() =>
{
Mods.Value = new[] { new OsuModHidden() };
SelectedMods.Value = new[] { new OsuModHidden() };
});
}
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new TaikoModSuddenDeath() }).ToArray();
return new ScoreAccessiblePlayer();
}

View File

@ -280,7 +280,7 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("Song select has selection", () => songSelect.Carousel.SelectedBeatmap != null);
AddStep("Set default user settings", () =>
{
Mods.Value = Mods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
songSelect.DimLevel.Value = 0.7f;
songSelect.BlurLevel.Value = 0.4f;
});

View File

@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new ScoreAccessiblePlayer();
}

View File

@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
SelectedMods.Value = Array.Empty<Mod>();
return new FailPlayer();
}

View File

@ -14,7 +14,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Array.Empty<Mod>();
SelectedMods.Value = Array.Empty<Mod>();
return new FailPlayer();
}

View File

@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override Player CreatePlayer(Ruleset ruleset)
{
Mods.Value = Mods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
SelectedMods.Value = SelectedMods.Value.Concat(new[] { ruleset.GetAutoplayMod() }).ToArray();
return new RulesetExposingPlayer();
}

View File

@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Gameplay
beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
mod.ApplyToTrack(Beatmap.Value.Track);
InputManager.Child = container = new TestPlayerLoaderContainer(
@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test]
public void TestEarlyExit()
{
AddStep("load dummy beatmap", () => ResetPlayer(false, () => Mods.Value = new[] { new OsuModNightcore() }));
AddStep("load dummy beatmap", () => ResetPlayer(false, () => SelectedMods.Value = new[] { new OsuModNightcore() }));
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
AddAssert("mod rate applied", () => Beatmap.Value.Track.Rate != 1);
AddStep("exit loader", () => loader.Exit());
@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay
TestMod playerMod1 = null;
TestMod playerMod2 = null;
AddStep("load player", () => { ResetPlayer(true, () => Mods.Value = new[] { gameMod = new TestMod() }); });
AddStep("load player", () => { ResetPlayer(true, () => SelectedMods.Value = new[] { gameMod = new TestMod() }); });
AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen());
AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre));

View File

@ -256,17 +256,17 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("change ruleset", () =>
{
Mods.ValueChanged += onModChange;
SelectedMods.ValueChanged += onModChange;
songSelect.Ruleset.ValueChanged += onRulesetChange;
Ruleset.Value = new TaikoRuleset().RulesetInfo;
Mods.ValueChanged -= onModChange;
SelectedMods.ValueChanged -= onModChange;
songSelect.Ruleset.ValueChanged -= onRulesetChange;
});
AddAssert("mods changed before ruleset", () => modChangeIndex < rulesetChangeIndex);
AddAssert("empty mods", () => !Mods.Value.Any());
AddAssert("empty mods", () => !SelectedMods.Value.Any());
void onModChange(ValueChangedEvent<IReadOnlyList<Mod>> e) => modChangeIndex = actionIndex++;
void onRulesetChange(ValueChangedEvent<RulesetInfo> e) => rulesetChangeIndex = actionIndex++;
@ -275,7 +275,7 @@ namespace osu.Game.Tests.Visual.SongSelect
[Test]
public void TestModsRetainedBetweenSongSelect()
{
AddAssert("empty mods", () => !Mods.Value.Any());
AddAssert("empty mods", () => !SelectedMods.Value.Any());
createSongSelect();
@ -285,7 +285,7 @@ namespace osu.Game.Tests.Visual.SongSelect
createSongSelect();
AddAssert("mods retained", () => Mods.Value.Any());
AddAssert("mods retained", () => SelectedMods.Value.Any());
}
[Test]
@ -332,7 +332,7 @@ namespace osu.Game.Tests.Visual.SongSelect
private void checkMusicPlaying(bool playing) =>
AddUntilStep($"music {(playing ? "" : "not ")}playing", () => music.IsPlaying == playing);
private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => Mods.Value = mods);
private void changeMods(params Mod[] mods) => AddStep($"change mods to {string.Join(", ", mods.Select(m => m.Acronym))}", () => SelectedMods.Value = mods);
private void changeRuleset(int id) => AddStep($"change ruleset to {id}", () => Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == id));

View File

@ -127,7 +127,7 @@ namespace osu.Game.Tests.Visual.UserInterface
var noFailMod = new OsuRuleset().GetModsFor(ModType.DifficultyReduction).FirstOrDefault(m => m is OsuModNoFail);
AddStep("set mods externally", () => { Mods.Value = new[] { noFailMod }; });
AddStep("set mods externally", () => { SelectedMods.Value = new[] { noFailMod }; });
changeRuleset(0);

View File

@ -2,15 +2,20 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
namespace osu.Game.Tests.Visual.UserInterface
{
@ -18,28 +23,51 @@ namespace osu.Game.Tests.Visual.UserInterface
{
private TestModSelectOverlay modSelect;
[BackgroundDependencyLoader]
private void load()
private readonly Mod testCustomisableMod = new TestModCustomisable1();
[Test]
public void TestButtonShowsOnCustomisableMod()
{
Add(modSelect = new TestModSelectOverlay
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
});
createModSelect();
var testMod = new TestModCustomisable1();
AddStep("open", modSelect.Show);
AddStep("open", () => modSelect.Show());
AddAssert("button disabled", () => !modSelect.CustomiseButton.Enabled.Value);
AddUntilStep("wait for button load", () => modSelect.ButtonsLoaded);
AddStep("select mod", () => modSelect.SelectMod(testMod));
AddStep("select mod", () => modSelect.SelectMod(testCustomisableMod));
AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
AddStep("open Customisation", () => modSelect.CustomiseButton.Click());
AddStep("deselect mod", () => modSelect.SelectMod(testMod));
AddStep("deselect mod", () => modSelect.SelectMod(testCustomisableMod));
AddAssert("controls hidden", () => modSelect.ModSettingsContainer.Alpha == 0);
}
[Test]
public void TestButtonShowsOnModAlreadyAdded()
{
AddStep("set active mods", () => SelectedMods.Value = new List<Mod> { testCustomisableMod });
createModSelect();
AddAssert("mods still active", () => SelectedMods.Value.Count == 1);
AddStep("open", () => modSelect.Show());
AddAssert("button enabled", () => modSelect.CustomiseButton.Enabled.Value);
}
private void createModSelect()
{
AddStep("create mod select", () =>
{
Ruleset.Value = new TestRulesetInfo();
Child = modSelect = new TestModSelectOverlay
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
};
});
}
private class TestModSelectOverlay : ModSelectOverlay
{
public new Container ModSettingsContainer => base.ModSettingsContainer;
@ -50,24 +78,36 @@ namespace osu.Game.Tests.Visual.UserInterface
public void SelectMod(Mod mod) =>
ModSectionsContainer.Children.Single(s => s.ModType == mod.Type)
.ButtonsContainer.OfType<ModButton>().Single(b => b.Mods.Any(m => m.GetType() == mod.GetType())).SelectNext(1);
}
protected override void LoadComplete()
public class TestRulesetInfo : RulesetInfo
{
public override Ruleset CreateInstance() => new TestCustomisableModRuleset();
public class TestCustomisableModRuleset : Ruleset
{
base.LoadComplete();
foreach (var section in ModSectionsContainer)
public override IEnumerable<Mod> GetModsFor(ModType type)
{
if (section.ModType == ModType.Conversion)
if (type == ModType.Conversion)
{
section.Mods = new Mod[]
return new Mod[]
{
new TestModCustomisable1(),
new TestModCustomisable2()
};
}
else
section.Mods = Array.Empty<Mod>();
return Array.Empty<Mod>();
}
public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList<Mod> mods = null) => throw new NotImplementedException();
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException();
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
public override string Description { get; } = "test";
public override string ShortName { get; } = "tst";
}
}

View File

@ -81,7 +81,12 @@ namespace osu.Game
// todo: move this to SongSelect once Screen has the ability to unsuspend.
[Cached]
[Cached(typeof(IBindable<IReadOnlyList<Mod>>))]
protected readonly Bindable<IReadOnlyList<Mod>> Mods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
/// <summary>
/// Mods available for the current <see cref="Ruleset"/>.
/// </summary>
public readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> AvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
protected Bindable<WorkingBeatmap> Beatmap { get; private set; } // cached via load() method
@ -233,6 +238,19 @@ namespace osu.Game
PreviewTrackManager previewTrackManager;
dependencies.Cache(previewTrackManager = new PreviewTrackManager());
Add(previewTrackManager);
Ruleset.BindValueChanged(onRulesetChanged);
}
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
{
var dict = new Dictionary<ModType, IReadOnlyList<Mod>>();
foreach (ModType type in Enum.GetValues(typeof(ModType)))
dict[type] = r.NewValue?.CreateInstance().GetModsFor(type).ToList();
SelectedMods.Value = Array.Empty<Mod>();
AvailableMods.Value = dict;
}
protected virtual Container CreateScalingContainer() => new DrawSizePreservingFillContainer();

View File

@ -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.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Mods;
@ -12,14 +12,13 @@ using osuTK;
namespace osu.Game.Overlays.Mods
{
public class ModControlSection : Container
public class ModControlSection : CompositeDrawable
{
protected FillFlowContainer FlowContent;
protected override Container<Drawable> Content => FlowContent;
public readonly Mod Mod;
public ModControlSection(Mod mod)
public ModControlSection(Mod mod, IEnumerable<Drawable> modControls)
{
Mod = mod;
@ -33,9 +32,8 @@ namespace osu.Game.Overlays.Mods
Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
ChildrenEnumerable = modControls
};
AddRange(Mod.CreateSettingsControls());
}
[BackgroundDependencyLoader]

View File

@ -20,7 +20,6 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods.Sections;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens;
using osuTK;
@ -50,7 +49,7 @@ namespace osu.Game.Overlays.Mods
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
protected readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods;
protected Color4 LowMultiplierColour;
protected Color4 HighMultiplierColour;
@ -322,14 +321,14 @@ namespace osu.Game.Overlays.Mods
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, IBindable<RulesetInfo> ruleset, AudioManager audio, Bindable<IReadOnlyList<Mod>> mods)
private void load(OsuColour colours, AudioManager audio, Bindable<IReadOnlyList<Mod>> selectedMods, OsuGameBase osu)
{
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
Ruleset.BindTo(ruleset);
if (mods != null) SelectedMods.BindTo(mods);
availableMods = osu.AvailableMods.GetBoundCopy();
SelectedMods.BindTo(selectedMods);
sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off");
@ -360,7 +359,7 @@ namespace osu.Game.Overlays.Mods
{
base.LoadComplete();
Ruleset.BindValueChanged(rulesetChanged, true);
availableMods.BindValueChanged(availableModsChanged, true);
SelectedMods.BindValueChanged(selectedModsChanged, true);
}
@ -410,22 +409,12 @@ namespace osu.Game.Overlays.Mods
return base.OnKeyDown(e);
}
private void rulesetChanged(ValueChangedEvent<RulesetInfo> e)
private void availableModsChanged(ValueChangedEvent<Dictionary<ModType, IReadOnlyList<Mod>>> mods)
{
if (e.NewValue == null) return;
var instance = e.NewValue.CreateInstance();
if (mods.NewValue == null) return;
foreach (var section in ModSectionsContainer.Children)
section.Mods = instance.GetModsFor(section.ModType);
// attempt to re-select any already selected mods.
// this may be the first time we are receiving the ruleset, in which case they will still match.
selectedModsChanged(new ValueChangedEvent<IReadOnlyList<Mod>>(SelectedMods.Value, SelectedMods.Value));
// write the mods back to the SelectedMods bindable in the case a change was not applicable.
// this generally isn't required as the previous line will perform deselection; just here for safety.
refreshSelectedMods();
section.Mods = mods.NewValue[section.ModType];
}
private void selectedModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
@ -462,17 +451,17 @@ namespace osu.Game.Overlays.Mods
private void updateModSettings(ValueChangedEvent<IReadOnlyList<Mod>> selectedMods)
{
foreach (var added in selectedMods.NewValue.Except(selectedMods.OldValue))
ModSettingsContent.Clear();
foreach (var mod in selectedMods.NewValue)
{
var controls = added.CreateSettingsControls().ToList();
if (controls.Count > 0)
ModSettingsContent.Add(new ModControlSection(added) { Children = controls });
var settings = mod.CreateSettingsControls().ToList();
if (settings.Count > 0)
ModSettingsContent.Add(new ModControlSection(mod, settings));
}
foreach (var removed in selectedMods.OldValue.Except(selectedMods.NewValue))
ModSettingsContent.RemoveAll(section => section.Mod == removed);
bool hasSettings = ModSettingsContent.Count > 0;
bool hasSettings = ModSettingsContent.Children.Count > 0;
CustomiseButton.Enabled.Value = hasSettings;
if (!hasSettings)
@ -502,8 +491,8 @@ namespace osu.Game.Overlays.Mods
{
base.Dispose(isDisposing);
Ruleset.UnbindAll();
SelectedMods.UnbindAll();
availableMods?.UnbindAll();
SelectedMods?.UnbindAll();
}
#endregion

View File

@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual
var working = CreateWorkingBeatmap(rulesetInfo);
Beatmap.Value = working;
Mods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) };
Player?.Exit();
Player = null;

View File

@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual
protected Bindable<RulesetInfo> Ruleset;
protected Bindable<IReadOnlyList<Mod>> Mods;
protected Bindable<IReadOnlyList<Mod>> SelectedMods;
protected new OsuScreenDependencies Dependencies { get; private set; }
@ -72,8 +72,8 @@ namespace osu.Game.Tests.Visual
Ruleset = Dependencies.Ruleset;
Ruleset.SetDefault();
Mods = Dependencies.Mods;
Mods.SetDefault();
SelectedMods = Dependencies.Mods;
SelectedMods.SetDefault();
if (!UseOnlineAPI)
{

View File

@ -53,14 +53,14 @@ namespace osu.Game.Tests.Visual
{
var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail);
if (noFailMod != null)
Mods.Value = new[] { noFailMod };
SelectedMods.Value = new[] { noFailMod };
}
if (Autoplay)
{
var mod = ruleset.GetAutoplayMod();
if (mod != null)
Mods.Value = Mods.Value.Concat(mod.Yield()).ToArray();
SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray();
}
Player = CreatePlayer(ruleset);