diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index aa59cb7fa3..2835ec9499 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -38,25 +38,25 @@ namespace osu.Game.Benchmarks [Benchmark] public void BenchmarkGetAllMods() { - ruleset.GetAllMods().Consume(new Consumer()); + ruleset.CreateAllMods().Consume(new Consumer()); } [Benchmark] public void BenchmarkGetAllModsForReference() { - ruleset.GetAllModsForReference().Consume(new Consumer()); + ruleset.AllMods.Consume(new Consumer()); } [Benchmark] public void BenchmarkGetForAcronym() { - ruleset.GetModForAcronym("DT"); + ruleset.CreateModFromAcronym("DT"); } [Benchmark] public void BenchmarkGetForType() { - ruleset.GetMod(); + ruleset.CreateMod(); } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs index b7dcad3825..8487eecdfa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAllRulesetPlayers.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Gameplay var working = CreateWorkingBeatmap(rulesetInfo); Beatmap.Value = working; - SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; + SelectedMods.Value = new[] { ruleset.CreateAllMods().First(m => m is ModNoFail) }; Player = CreatePlayer(ruleset); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 40b2f66d74..5e22bde1d5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select EZ mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateAllMods().OfType().Single() }; }); AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); @@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select HR mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - SelectedMods.Value = new[] { ruleset.GetAllMods().OfType().Single() }; + SelectedMods.Value = new[] { ruleset.CreateAllMods().OfType().Single() }; }); AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); @@ -123,7 +123,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select unchanged Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateAllMods().OfType().Single(); difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select changed Difficulty Adjust mod", () => { var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); - var difficultyAdjustMod = ruleset.GetAllMods().OfType().Single(); + var difficultyAdjustMod = ruleset.CreateAllMods().OfType().Single(); var originalDifficulty = advancedStats.Beatmap.BaseDifficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs index 66ac700c51..f91d3f595b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapMetadataDisplay.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect { AddStep("setup display", () => { - var randomMods = Ruleset.Value.CreateInstance().GetAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); + var randomMods = Ruleset.Value.CreateInstance().CreateAllMods().OrderBy(_ => RNG.Next()).Take(5).ToList(); OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) }; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs index 8f057c663b..10eab148de 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModFlowDisplay.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface Width = 200, Current = { - Value = new OsuRuleset().GetAllMods().ToArray(), + Value = new OsuRuleset().CreateAllMods().ToArray(), } }; }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index 9e253e089d..28e5644998 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface var mania = new ManiaRuleset(); testModsWithSameBaseType( - mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), - mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); + mania.CreateAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), + mania.CreateAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); } [Test] diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index b4d9fa4222..cc1568f429 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components private void success(APIBeatmap apiBeatmap) { beatmap = apiBeatmap.ToBeatmap(rulesets); - var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().GetAllMods(); + var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().CreateAllMods(); foreach (var mod in mods) { diff --git a/osu.Game.Tournament/Components/TournamentModIcon.cs b/osu.Game.Tournament/Components/TournamentModIcon.cs index b8486b5b2d..7c4e9c69a2 100644 --- a/osu.Game.Tournament/Components/TournamentModIcon.cs +++ b/osu.Game.Tournament/Components/TournamentModIcon.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tournament.Components } var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); - var modIcon = ruleset?.CreateInstance().GetModForAcronym(modAcronym); + var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym); if (modIcon == null) return; diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 5514ce5e40..5e552dac09 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -48,7 +48,7 @@ namespace osu.Game.Online.API public Mod ToMod(Ruleset ruleset) { - Mod resultMod = ruleset.GetModForAcronym(Acronym); + Mod resultMod = ruleset.CreateModFromAcronym(Acronym); if (resultMod == null) throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index d1c6f0a55a..567df524b1 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -23,10 +23,10 @@ namespace osu.Game.Online.API.Requests.Responses var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.GetModForAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); + var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.GetMod()).ToArray(); + mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); var scoreInfo = new ScoreInfo { diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 517da11c8c..2683d7bc6d 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllModsForReference().Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m.CreateInstance()))); modsContainer.ForEach(button => { diff --git a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs index 8d0746f24d..0f51439252 100644 --- a/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs +++ b/osu.Game/Overlays/Mods/IncompatibilityDisplayingModButton.cs @@ -107,9 +107,9 @@ namespace osu.Game.Overlays.Mods var incompatibleTypes = mod.IncompatibleMods; - var allMods = ruleset.Value.CreateInstance().GetAllModsForReference(); + var allMods = ruleset.Value.CreateInstance().AllMods; - incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).ToList(); + incompatibleMods.Value = allMods.Where(m => m.GetType() != mod.GetType() && incompatibleTypes.Any(t => t.IsInstanceOfType(m))).Select(m => m.CreateInstance()).ToList(); incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods"; } } diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index a5e19f293c..ac17e49e60 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Newtonsoft.Json; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods { @@ -11,7 +11,22 @@ namespace osu.Game.Rulesets.Mods /// /// The shortened name of this mod. /// - [JsonProperty("acronym")] string Acronym { get; } + + /// + /// The icon of this mod. + /// + IconUsage? Icon { get; } + + /// + /// Whether this mod is playable by an end user. + /// Should be false for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example). + /// + bool UserPlayable { get; } + + /// + /// Create a fresh instance based on this mod. + /// + Mod CreateInstance() => ((Mod)this).DeepClone(); } } diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 1199d8a956..fedee857c3 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -33,9 +33,6 @@ namespace osu.Game.Rulesets.Mods /// public abstract string Acronym { get; } - /// - /// The icon of this mod. - /// [JsonIgnore] public virtual IconUsage? Icon => null; @@ -106,10 +103,6 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual bool HasImplementation => this is IApplicableMod; - /// - /// Whether this mod is playable by an end user. - /// Should be false for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example). - /// [JsonIgnore] public virtual bool UserPlayable => true; diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 34e4606133..82707ad382 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -39,45 +39,48 @@ namespace osu.Game.Rulesets { public RulesetInfo RulesetInfo { get; internal set; } + private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); + + /// + /// A queryable source containing all available mods. + /// Call for consumption purposes. + /// + public IEnumerable AllMods + { + get + { + if (!(RulesetInfo.ID is int id)) + return CreateAllMods(); + + if (!mod_reference_cache.TryGetValue(id, out var mods)) + mod_reference_cache[id] = mods = CreateAllMods().Cast().ToArray(); + + return mods; + } + } + /// /// Returns fresh instances of all mods. /// /// /// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings) - /// use instead. + /// use instead. /// - public IEnumerable GetAllMods() => Enum.GetValues(typeof(ModType)).Cast() - // Confine all mods of each mod type into a single IEnumerable - .SelectMany(GetModsFor) - // Filter out all null mods - .Where(mod => mod != null) - // Resolve MultiMods as their .Mods property - .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); - - private static readonly ConcurrentDictionary mod_reference_cache = new ConcurrentDictionary(); - - /// - /// Returns all mods for a query-only purpose. - /// Bindables should not be considered usable when retrieving via this method (use instead). - /// - public IEnumerable GetAllModsForReference() - { - if (!(RulesetInfo.ID is int id)) - return GetAllMods(); - - if (!mod_reference_cache.TryGetValue(id, out var mods)) - mod_reference_cache[id] = mods = GetAllMods().ToArray(); - - return mods; - } + public IEnumerable CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast() + // Confine all mods of each mod type into a single IEnumerable + .SelectMany(GetModsFor) + // Filter out all null mods + .Where(mod => mod != null) + // Resolve MultiMods as their .Mods property + .SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); /// /// Returns a fresh instance of the mod matching the specified acronym. /// /// The acronym to query for . - public Mod GetModForAcronym(string acronym) + public Mod CreateModFromAcronym(string acronym) { - var type = GetAllModsForReference().FirstOrDefault(m => m.Acronym == acronym)?.GetType(); + var type = AllMods.FirstOrDefault(m => m.Acronym == acronym)?.GetType(); if (type != null) return (Mod)Activator.CreateInstance(type); @@ -88,10 +91,10 @@ namespace osu.Game.Rulesets /// /// Returns a fresh instance of the mod matching the specified type. /// - public T GetMod() + public T CreateMod() where T : Mod { - var type = GetAllModsForReference().FirstOrDefault(m => m is T)?.GetType(); + var type = AllMods.FirstOrDefault(m => m is T)?.GetType(); if (type != null) return (T)Activator.CreateInstance(type); @@ -179,7 +182,7 @@ namespace osu.Game.Rulesets } [CanBeNull] - public ModAutoplay GetAutoplayMod() => GetMod(); + public ModAutoplay GetAutoplayMod() => CreateMod(); public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 010f33e3ed..2e1a29372d 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -67,7 +67,7 @@ namespace osu.Game.Scoring.Legacy // lazer replays get a really high version number. if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) - scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetMod()).ToArray(); + scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod()).ToArray(); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; diff --git a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs index 54a83f4305..b7803f3420 100644 --- a/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyModConversionTest.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps protected void TestToLegacy(LegacyMods expectedLegacyMods, Type[] providedModTypes) { var ruleset = CreateRuleset(); - var modInstances = ruleset.GetAllMods() + var modInstances = ruleset.CreateAllMods() .Where(mod => providedModTypes.Contains(mod.GetType())) .ToArray(); var actualLegacyMods = ruleset.ConvertToLegacyMods(modInstances); diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 8ce71ace69..5ce6aae647 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests RulesetID = ruleset.ID ?? 0; Mods = excessMods - ? ruleset.CreateInstance().GetAllMods().ToArray() + ? ruleset.CreateInstance().CreateAllMods().ToArray() : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; TotalScore = 2845370; diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index a31a6433ea..b34f7e2d5f 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual if (!AllowFail) { - var noFailMod = ruleset.GetMod(); + var noFailMod = ruleset.CreateMod(); if (noFailMod != null) SelectedMods.Value = new[] { noFailMod }; }