1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-21 03:02:54 +08:00

Merge branch 'master' into fix-password-popover-back-button

This commit is contained in:
Bartłomiej Dach 2021-09-11 14:18:25 +02:00
commit addba43e7d
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
38 changed files with 547 additions and 203 deletions

View File

@ -4,6 +4,7 @@
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines; using BenchmarkDotNet.Engines;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
namespace osu.Game.Benchmarks namespace osu.Game.Benchmarks
@ -37,7 +38,25 @@ namespace osu.Game.Benchmarks
[Benchmark] [Benchmark]
public void BenchmarkGetAllMods() public void BenchmarkGetAllMods()
{ {
ruleset.GetAllMods().Consume(new Consumer()); ruleset.CreateAllMods().Consume(new Consumer());
}
[Benchmark]
public void BenchmarkGetAllModsForReference()
{
ruleset.AllMods.Consume(new Consumer());
}
[Benchmark]
public void BenchmarkGetForAcronym()
{
ruleset.CreateModFromAcronym("DT");
}
[Benchmark]
public void BenchmarkGetForType()
{
ruleset.CreateMod<ModDoubleTime>();
} }
} }
} }

View File

@ -3,6 +3,7 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Mods namespace osu.Game.Tests.Mods
@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods
public class ModSettingsEqualityComparison public class ModSettingsEqualityComparison
{ {
[Test] [Test]
public void Test() public void TestAPIMod()
{ {
var apiMod1 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.25 } });
var apiMod2 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
var apiMod3 = new APIMod(new OsuModDoubleTime { SpeedChange = { Value = 1.26 } });
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2));
Assert.That(apiMod2, Is.EqualTo(apiMod2));
Assert.That(apiMod2, Is.EqualTo(apiMod3));
Assert.That(apiMod3, Is.EqualTo(apiMod2));
}
[Test]
public void TestMod()
{
var ruleset = new OsuRuleset();
var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } }; var mod1 = new OsuModDoubleTime { SpeedChange = { Value = 1.25 } };
var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; var mod2 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } }; var mod3 = new OsuModDoubleTime { SpeedChange = { Value = 1.26 } };
var apiMod1 = new APIMod(mod1);
var apiMod2 = new APIMod(mod2); var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
var apiMod3 = new APIMod(mod3); var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
Assert.That(mod1, Is.Not.EqualTo(mod2)); Assert.That(mod1, Is.Not.EqualTo(mod2));
Assert.That(apiMod1, Is.Not.EqualTo(apiMod2)); Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doulbeConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod2)); Assert.That(mod2, Is.EqualTo(mod2));
Assert.That(apiMod2, Is.EqualTo(apiMod2)); Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
Assert.That(mod2, Is.EqualTo(mod3)); Assert.That(mod2, Is.EqualTo(mod3));
Assert.That(apiMod2, Is.EqualTo(apiMod3)); Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod3));
Assert.That(mod3, Is.EqualTo(mod2)); Assert.That(mod3, Is.EqualTo(mod2));
Assert.That(apiMod3, Is.EqualTo(apiMod2)); Assert.That(doulbeConvertedMod3, Is.EqualTo(doulbeConvertedMod2));
} }
} }
} }

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Configuration; using osu.Game.Configuration;
@ -71,7 +70,7 @@ namespace osu.Game.Tests.Visual.Gameplay
var working = CreateWorkingBeatmap(rulesetInfo); var working = CreateWorkingBeatmap(rulesetInfo);
Beatmap.Value = working; Beatmap.Value = working;
SelectedMods.Value = new[] { ruleset.GetAllMods().First(m => m is ModNoFail) }; SelectedMods.Value = new[] { ruleset.CreateMod<ModNoFail>() };
Player = CreatePlayer(ruleset); Player = CreatePlayer(ruleset);

View File

@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar; using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
@ -388,6 +389,19 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden); AddAssert("now playing is hidden", () => nowPlayingOverlay.State.Value == Visibility.Hidden);
} }
[Test]
public void TestExitGameFromSongSelect()
{
PushAndConfirm(() => new TestPlaySongSelect());
exitViaEscapeAndConfirm();
pushEscape(); // returns to osu! logo
AddStep("Hold escape", () => InputManager.PressKey(Key.Escape));
AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles);
AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null);
}
private void pushEscape() => private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape)); AddStep("Press escape", () => InputManager.Key(Key.Escape));

View File

@ -5,10 +5,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -17,10 +17,11 @@ using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Users; using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online namespace osu.Game.Tests.Visual.Online
{ {
public class TestSceneBeatmapListingOverlay : OsuTestScene public class TestSceneBeatmapListingOverlay : OsuManualInputManagerTestScene
{ {
private readonly List<APIBeatmapSet> setsForResponse = new List<APIBeatmapSet>(); private readonly List<APIBeatmapSet> setsForResponse = new List<APIBeatmapSet>();
@ -28,27 +29,33 @@ namespace osu.Game.Tests.Visual.Online
private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType<BeatmapListingSearchControl>().Single(); private BeatmapListingSearchControl searchControl => overlay.ChildrenOfType<BeatmapListingSearchControl>().Single();
[BackgroundDependencyLoader] [SetUpSteps]
private void load() public void SetUpSteps()
{ {
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } }; AddStep("setup overlay", () =>
((DummyAPIAccess)API).HandleRequest = req =>
{ {
if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false; Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
setsForResponse.Clear();
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse });
{
BeatmapSets = setsForResponse,
});
return true;
};
AddStep("initialize dummy", () => AddStep("initialize dummy", () =>
{ {
var api = (DummyAPIAccess)API;
api.HandleRequest = req =>
{
if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
{
BeatmapSets = setsForResponse,
});
return true;
};
// non-supporter user // non-supporter user
((DummyAPIAccess)API).LocalUser.Value = new User api.LocalUser.Value = new User
{ {
Username = "TestBot", Username = "TestBot",
Id = API.LocalUser.Value.Id + 1, Id = API.LocalUser.Value.Id + 1,
@ -56,6 +63,51 @@ namespace osu.Game.Tests.Visual.Online
}); });
} }
[Test]
public void TestHideViaBack()
{
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("hide", () => InputManager.Key(Key.Escape));
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
}
[Test]
public void TestHideViaBackWithSearch()
{
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("search something", () => overlay.ChildrenOfType<SearchTextBox>().First().Text = "search");
AddStep("kill search", () => InputManager.Key(Key.Escape));
AddAssert("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType<SearchTextBox>().First().Text));
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("hide", () => InputManager.Key(Key.Escape));
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
}
[Test]
public void TestHideViaBackWithScrolledSearch()
{
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("show many results", () => fetchFor(Enumerable.Repeat(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet, 100).ToArray()));
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
AddStep("scroll to bottom", () => overlay.ChildrenOfType<OverlayScrollContainer>().First().ScrollToEnd());
AddStep("kill search", () => InputManager.Key(Key.Escape));
AddUntilStep("search textbox empty", () => string.IsNullOrEmpty(overlay.ChildrenOfType<SearchTextBox>().First().Text));
AddUntilStep("is scrolled to top", () => overlay.ChildrenOfType<OverlayScrollContainer>().First().Current == 0);
AddAssert("is visible", () => overlay.State.Value == Visibility.Visible);
AddStep("hide", () => InputManager.Key(Key.Escape));
AddUntilStep("is hidden", () => overlay.State.Value == Visibility.Hidden);
}
[Test] [Test]
public void TestNoBeatmapsPlaceholder() public void TestNoBeatmapsPlaceholder()
{ {
@ -63,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet)); AddStep("fetch for 1 beatmap", () => fetchFor(CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet));
AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any()); AddUntilStep("placeholder hidden", () => !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
AddStep("fetch for 0 beatmaps", () => fetchFor()); AddStep("fetch for 0 beatmaps", () => fetchFor());
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true); AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
@ -193,13 +245,15 @@ namespace osu.Game.Tests.Visual.Online
noPlaceholderShown(); noPlaceholderShown();
} }
private static int searchCount;
private void fetchFor(params BeatmapSetInfo[] beatmaps) private void fetchFor(params BeatmapSetInfo[] beatmaps)
{ {
setsForResponse.Clear(); setsForResponse.Clear();
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b))); setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
// trigger arbitrary change for fetching. // trigger arbitrary change for fetching.
searchControl.Query.TriggerChange(); searchControl.Query.Value = $"search {searchCount++}";
} }
private void setRankAchievedFilter(ScoreRank[] ranks) private void setRankAchievedFilter(ScoreRank[] ranks)
@ -229,8 +283,8 @@ namespace osu.Game.Tests.Visual.Online
private void noPlaceholderShown() private void noPlaceholderShown()
{ {
AddUntilStep("no placeholder shown", () => AddUntilStep("no placeholder shown", () =>
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any() !overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any(d => d.IsPresent)
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any()); && !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
} }
private class TestAPIBeatmapSet : APIBeatmapSet private class TestAPIBeatmapSet : APIBeatmapSet

View File

@ -21,6 +21,8 @@ namespace osu.Game.Tests.Visual.Online
protected override bool UseOnlineAPI => true; protected override bool UseOnlineAPI => true;
private int nextBeatmapSetId = 1;
public TestSceneBeatmapSetOverlay() public TestSceneBeatmapSetOverlay()
{ {
Add(overlay = new TestBeatmapSetOverlay()); Add(overlay = new TestBeatmapSetOverlay());
@ -240,12 +242,23 @@ namespace osu.Game.Tests.Visual.Online
{ {
AddStep("show explicit map", () => AddStep("show explicit map", () =>
{ {
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; var beatmapSet = getBeatmapSet();
beatmapSet.OnlineInfo.HasExplicitContent = true; beatmapSet.OnlineInfo.HasExplicitContent = true;
overlay.ShowBeatmapSet(beatmapSet); overlay.ShowBeatmapSet(beatmapSet);
}); });
} }
[Test]
public void TestFeaturedBeatmap()
{
AddStep("show featured map", () =>
{
var beatmapSet = getBeatmapSet();
beatmapSet.OnlineInfo.TrackId = 1;
overlay.ShowBeatmapSet(beatmapSet);
});
}
[Test] [Test]
public void TestHide() public void TestHide()
{ {
@ -308,6 +321,14 @@ namespace osu.Game.Tests.Visual.Online
}; };
} }
private BeatmapSetInfo getBeatmapSet()
{
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
// Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`).
beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++;
return beatmapSet;
}
private void downloadAssert(bool shown) private void downloadAssert(bool shown)
{ {
AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown); AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.HeaderContent.DownloadButtonsVisible == shown);

View File

@ -99,16 +99,23 @@ namespace osu.Game.Tests.Visual.Online
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets) private void load(RulesetStore rulesets)
{ {
var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; var normal = getBeatmapSet();
normal.OnlineInfo.HasVideo = true; normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true; normal.OnlineInfo.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet(); var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets); var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; var explicitMap = getBeatmapSet();
explicitMap.OnlineInfo.HasExplicitContent = true; explicitMap.OnlineInfo.HasExplicitContent = true;
var featuredMap = getBeatmapSet();
featuredMap.OnlineInfo.TrackId = 1;
var explicitFeaturedMap = getBeatmapSet();
explicitFeaturedMap.OnlineInfo.HasExplicitContent = true;
explicitFeaturedMap.OnlineInfo.TrackId = 2;
Child = new BasicScrollContainer Child = new BasicScrollContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -125,13 +132,19 @@ namespace osu.Game.Tests.Visual.Online
new GridBeatmapPanel(undownloadable), new GridBeatmapPanel(undownloadable),
new GridBeatmapPanel(manyDifficulties), new GridBeatmapPanel(manyDifficulties),
new GridBeatmapPanel(explicitMap), new GridBeatmapPanel(explicitMap),
new GridBeatmapPanel(featuredMap),
new GridBeatmapPanel(explicitFeaturedMap),
new ListBeatmapPanel(normal), new ListBeatmapPanel(normal),
new ListBeatmapPanel(undownloadable), new ListBeatmapPanel(undownloadable),
new ListBeatmapPanel(manyDifficulties), new ListBeatmapPanel(manyDifficulties),
new ListBeatmapPanel(explicitMap) new ListBeatmapPanel(explicitMap),
new ListBeatmapPanel(featuredMap),
new ListBeatmapPanel(explicitFeaturedMap)
}, },
}, },
}; };
BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
} }
} }
} }

View File

@ -89,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select EZ mod", () => AddStep("select EZ mod", () =>
{ {
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModEasy>().Single() }; SelectedMods.Value = new[] { ruleset.CreateMod<ModEasy>() };
}); });
AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue)); AddAssert("circle size bar is blue", () => barIsBlue(advancedStats.FirstValue));
@ -106,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select HR mod", () => AddStep("select HR mod", () =>
{ {
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
SelectedMods.Value = new[] { ruleset.GetAllMods().OfType<ModHardRock>().Single() }; SelectedMods.Value = new[] { ruleset.CreateMod<ModHardRock>() };
}); });
AddAssert("circle size bar is red", () => barIsRed(advancedStats.FirstValue)); 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", () => AddStep("select unchanged Difficulty Adjust mod", () =>
{ {
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
var difficultyAdjustMod = ruleset.GetAllMods().OfType<ModDifficultyAdjust>().Single(); var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>();
difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty); difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty);
SelectedMods.Value = new[] { difficultyAdjustMod }; SelectedMods.Value = new[] { difficultyAdjustMod };
}); });
@ -142,7 +142,7 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select changed Difficulty Adjust mod", () => AddStep("select changed Difficulty Adjust mod", () =>
{ {
var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance();
var difficultyAdjustMod = ruleset.GetAllMods().OfType<OsuModDifficultyAdjust>().Single(); var difficultyAdjustMod = ruleset.CreateMod<OsuModDifficultyAdjust>();
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty; var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);

View File

@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
AddStep("setup display", () => 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) }; OsuLogo logo = new OsuLogo { Scale = new Vector2(0.15f) };

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Width = 200, Width = 200,
Current = Current =
{ {
Value = new OsuRuleset().GetAllMods().ToArray(), Value = new OsuRuleset().CreateAllMods().ToArray(),
} }
}; };
}); });

View File

@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -17,5 +19,16 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime())); AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime()));
AddStep("change mod", () => icon.Mod = new OsuModEasy()); AddStep("change mod", () => icon.Mod = new OsuModEasy());
} }
[Test]
public void TestInterfaceModType()
{
ModIcon icon = null;
var ruleset = new OsuRuleset();
AddStep("create mod icon", () => Child = icon = new ModIcon(ruleset.AllMods.First(m => m.Acronym == "DT")));
AddStep("change mod", () => icon.Mod = ruleset.AllMods.First(m => m.Acronym == "EZ"));
}
} }
} }

View File

@ -158,8 +158,8 @@ namespace osu.Game.Tests.Visual.UserInterface
var mania = new ManiaRuleset(); var mania = new ManiaRuleset();
testModsWithSameBaseType( testModsWithSameBaseType(
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), mania.CreateMod<ManiaModFadeIn>(),
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); mania.CreateMod<ManiaModHidden>());
} }
[Test] [Test]

View File

@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Tests.Components
private void success(APIBeatmap apiBeatmap) private void success(APIBeatmap apiBeatmap)
{ {
beatmap = apiBeatmap.ToBeatmap(rulesets); 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().AllMods;
foreach (var mod in mods) foreach (var mod in mods)
{ {

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -49,7 +48,7 @@ namespace osu.Game.Tournament.Components
} }
var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0); var ruleset = rulesets.GetRuleset(ladderInfo.Ruleset.Value?.ID ?? 0);
var modIcon = ruleset?.CreateInstance().GetAllMods().FirstOrDefault(mod => mod.Acronym == modAcronym); var modIcon = ruleset?.CreateInstance().CreateModFromAcronym(modAcronym);
if (modIcon == null) if (modIcon == null)
return; return;

View File

@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps
/// The song language of this beatmap set. /// The song language of this beatmap set.
/// </summary> /// </summary>
public BeatmapSetOnlineLanguage Language { get; set; } public BeatmapSetOnlineLanguage Language { get; set; }
/// <summary>
/// The track ID of this beatmap set.
/// Non-null only if the track is linked to a featured artist track entry.
/// </summary>
public int? TrackId { get; set; }
} }
public class BeatmapSetOnlineGenre public class BeatmapSetOnlineGenre

View File

@ -70,7 +70,7 @@ namespace osu.Game.Graphics.UserInterface
return base.OnKeyDown(e); return base.OnKeyDown(e);
} }
public bool OnPressed(GlobalAction action) public virtual bool OnPressed(GlobalAction action)
{ {
if (!HasFocus) return false; if (!HasFocus) return false;

View File

@ -16,7 +16,7 @@ using osu.Game.Utils;
namespace osu.Game.Online.API namespace osu.Game.Online.API
{ {
[MessagePackObject] [MessagePackObject]
public class APIMod : IMod, IEquatable<APIMod> public class APIMod : IEquatable<APIMod>
{ {
[JsonProperty("acronym")] [JsonProperty("acronym")]
[Key(0)] [Key(0)]
@ -48,7 +48,7 @@ namespace osu.Game.Online.API
public Mod ToMod(Ruleset ruleset) public Mod ToMod(Ruleset ruleset)
{ {
Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym); Mod resultMod = ruleset.CreateModFromAcronym(Acronym);
if (resultMod == null) if (resultMod == null)
throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}."); throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}.");
@ -67,15 +67,12 @@ namespace osu.Game.Online.API
return resultMod; return resultMod;
} }
public bool Equals(IMod other) => other is APIMod them && Equals(them);
public bool Equals(APIMod other) public bool Equals(APIMod other)
{ {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(this, other)) return true;
return Acronym == other.Acronym && return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
} }
public override string ToString() public override string ToString()

View File

@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests
private readonly BeatmapInfo beatmap; private readonly BeatmapInfo beatmap;
private readonly BeatmapLeaderboardScope scope; private readonly BeatmapLeaderboardScope scope;
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;
private readonly IEnumerable<Mod> mods; private readonly IEnumerable<IMod> mods;
public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<Mod> mods = null) public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable<IMod> mods = null)
{ {
if (!beatmap.OnlineBeatmapID.HasValue) if (!beatmap.OnlineBeatmapID.HasValue)
throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}.");
@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests
this.beatmap = beatmap; this.beatmap = beatmap;
this.scope = scope; this.scope = scope;
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
this.mods = mods ?? Array.Empty<Mod>(); this.mods = mods ?? Array.Empty<IMod>();
Success += onSuccess; Success += onSuccess;
} }

View File

@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"ratings")] [JsonProperty(@"ratings")]
private int[] ratings { get; set; } private int[] ratings { get; set; }
[JsonProperty(@"track_id")]
private int? trackId { get; set; }
[JsonProperty(@"user_id")] [JsonProperty(@"user_id")]
private int creatorId private int creatorId
{ {
@ -106,7 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses
Availability = availability, Availability = availability,
HasFavourited = hasFavourited, HasFavourited = hasFavourited,
Genre = genre, Genre = genre,
Language = language Language = language,
TrackId = trackId
}, },
}; };

View File

@ -23,10 +23,10 @@ namespace osu.Game.Online.API.Requests.Responses
var rulesetInstance = ruleset.CreateInstance(); var rulesetInstance = ruleset.CreateInstance();
var mods = Mods != null ? rulesetInstance.GetAllMods().Where(mod => Mods.Contains(mod.Acronym)).ToArray() : Array.Empty<Mod>(); var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty<Mod>();
// all API scores provided by this class are considered to be legacy. // all API scores provided by this class are considered to be legacy.
mods = mods.Append(rulesetInstance.GetAllMods().OfType<ModClassic>().Single()).ToArray(); mods = mods.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
var scoreInfo = new ScoreInfo var scoreInfo = new ScoreInfo
{ {

View File

@ -3,21 +3,22 @@
using System; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osuTK;
using osu.Framework.Bindables;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using osuTK.Graphics;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Scoring; using osu.Game.Scoring;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing namespace osu.Game.Overlays.BeatmapListing
{ {
@ -117,7 +118,7 @@ namespace osu.Game.Overlays.BeatmapListing
textBox = new BeatmapSearchTextBox textBox = new BeatmapSearchTextBox
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
TypingStarted = () => TypingStarted?.Invoke(), TextChanged = () => TypingStarted?.Invoke(),
}, },
new ReverseChildIDFillFlowContainer<Drawable> new ReverseChildIDFillFlowContainer<Drawable>
{ {
@ -167,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapListing
/// <summary> /// <summary>
/// Any time the text box receives key events (even while masked). /// Any time the text box receives key events (even while masked).
/// </summary> /// </summary>
public Action TypingStarted; public Action TextChanged;
protected override Color4 SelectionColour => Color4.Gray; protected override Color4 SelectionColour => Color4.Gray;
@ -181,7 +182,16 @@ namespace osu.Game.Overlays.BeatmapListing
if (!base.OnKeyDown(e)) if (!base.OnKeyDown(e))
return false; return false;
TypingStarted?.Invoke(); TextChanged?.Invoke();
return true;
}
public override bool OnPressed(GlobalAction action)
{
if (!base.OnPressed(action))
return false;
TextChanged?.Invoke();
return true; return true;
} }
} }

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
private const float horizontal_padding = 10; private const float horizontal_padding = 10;
private const float vertical_padding = 5; private const float vertical_padding = 5;
private FillFlowContainer bottomPanel, statusContainer, titleContainer; private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer;
private PlayButton playButton; private PlayButton playButton;
private Box progressBar; private Box progressBar;
@ -89,11 +89,19 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
}, },
} }
}, },
new OsuSpriteText artistContainer = new FillFlowContainer
{ {
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), AutoSizeAxes = Axes.Both,
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) Direction = FillDirection.Horizontal,
}, Children = new Drawable[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
}
}
}
}, },
}, },
new Container new Container
@ -213,6 +221,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
}); });
} }
if (SetInfo.OnlineInfo?.TrackId != null)
{
artistContainer.Add(new FeaturedArtistBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.OnlineInfo?.HasVideo ?? false) if (SetInfo.OnlineInfo?.HasVideo ?? false)
{ {
statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); statusContainer.Add(new IconPill(FontAwesome.Solid.Film));

View File

@ -27,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
private const float vertical_padding = 5; private const float vertical_padding = 5;
private const float height = 70; private const float height = 70;
private FillFlowContainer statusContainer, titleContainer; private FillFlowContainer statusContainer, titleContainer, artistContainer;
protected BeatmapPanelDownloadButton DownloadButton; protected BeatmapPanelDownloadButton DownloadButton;
private PlayButton playButton; private PlayButton playButton;
private Box progressBar; private Box progressBar;
@ -112,10 +112,18 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
}, },
} }
}, },
new OsuSpriteText artistContainer = new FillFlowContainer
{ {
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), AutoSizeAxes = Axes.Both,
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
},
}, },
} }
}, },
@ -227,6 +235,16 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
}); });
} }
if (SetInfo.OnlineInfo?.TrackId != null)
{
artistContainer.Add(new FeaturedArtistBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.OnlineInfo?.HasVideo ?? false) if (SetInfo.OnlineInfo?.HasVideo ?? false)
{ {
statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });

View File

@ -37,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly OsuSpriteText title, artist; private readonly OsuSpriteText title, artist;
private readonly AuthorInfo author; private readonly AuthorInfo author;
private readonly ExplicitContentBeatmapPill explicitContentPill; private readonly ExplicitContentBeatmapPill explicitContentPill;
private readonly FeaturedArtistBeatmapPill featuredArtistPill;
private readonly FillFlowContainer downloadButtonsContainer; private readonly FillFlowContainer downloadButtonsContainer;
private readonly BeatmapAvailability beatmapAvailability; private readonly BeatmapAvailability beatmapAvailability;
private readonly BeatmapSetOnlineStatusPill onlineStatusPill; private readonly BeatmapSetOnlineStatusPill onlineStatusPill;
@ -129,10 +130,25 @@ namespace osu.Game.Overlays.BeatmapSet
} }
} }
}, },
artist = new OsuSpriteText new FillFlowContainer
{ {
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Bottom = 20 } AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Bottom = 20 },
Children = new Drawable[]
{
artist = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true),
},
featuredArtistPill = new FeaturedArtistBeatmapPill
{
Alpha = 0f,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Margin = new MarginPadding { Left = 10 }
}
}
}, },
new Container new Container
{ {
@ -233,6 +249,7 @@ namespace osu.Game.Overlays.BeatmapSet
artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist); artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist);
explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0; explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0;
featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0;
onlineStatusPill.FadeIn(500, Easing.OutQuint); onlineStatusPill.FadeIn(500, Easing.OutQuint);
onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status;

View File

@ -0,0 +1,47 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
namespace osu.Game.Overlays.BeatmapSet
{
public class FeaturedArtistBeatmapPill : CompositeDrawable
{
public FeaturedArtistBeatmapPill()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, OverlayColourProvider colourProvider)
{
InternalChild = new CircularContainer
{
Masking = true,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider?.Background5 ?? colours.Gray2,
},
new OsuSpriteText
{
Margin = new MarginPadding { Horizontal = 10f, Vertical = 2f },
Text = BeatmapsetsStrings.FeaturedArtistBadgeLabel.ToUpper(),
Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold),
Colour = OverlayColourProvider.Blue.Colour1,
}
}
};
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.BeatmapSet
{ {
public class LeaderboardModSelector : CompositeDrawable public class LeaderboardModSelector : CompositeDrawable
{ {
public readonly BindableList<Mod> SelectedMods = new BindableList<Mod>(); public readonly BindableList<IMod> SelectedMods = new BindableList<IMod>();
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>(); public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
private readonly FillFlowContainer<ModButton> modsContainer; private readonly FillFlowContainer<ModButton> modsContainer;
@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet
return; return;
modsContainer.Add(new ModButton(new ModNoMod())); modsContainer.Add(new ModButton(new ModNoMod()));
modsContainer.AddRange(ruleset.NewValue.CreateInstance().GetAllMods().Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m)));
modsContainer.ForEach(button => modsContainer.ForEach(button =>
{ {
@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet
updateHighlighted(); updateHighlighted();
} }
private void selectionChanged(Mod mod, bool selected) private void selectionChanged(IMod mod, bool selected)
{ {
if (selected) if (selected)
SelectedMods.Add(mod); SelectedMods.Add(mod);
@ -101,9 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet
private const int duration = 200; private const int duration = 200;
public readonly BindableBool Highlighted = new BindableBool(); public readonly BindableBool Highlighted = new BindableBool();
public Action<Mod, bool> OnSelectionChanged; public Action<IMod, bool> OnSelectionChanged;
public ModButton(Mod mod) public ModButton(IMod mod)
: base(mod) : base(mod)
{ {
Scale = new Vector2(0.4f); Scale = new Vector2(0.4f);

View File

@ -26,9 +26,6 @@ namespace osu.Game.Overlays.Changelog
private const float image_container_width = 164; private const float image_container_width = 164;
private const float heart_size = 75; private const float heart_size = 75;
private readonly FillFlowContainer textContainer;
private readonly Container imageContainer;
public ChangelogSupporterPromo() public ChangelogSupporterPromo()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -38,6 +35,12 @@ namespace osu.Game.Overlays.Changelog
Vertical = 20, Vertical = 20,
Horizontal = 50, Horizontal = 50,
}; };
}
[BackgroundDependencyLoader]
private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider)
{
SupporterPromoLinkFlowContainer supportLinkText;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Changelog
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.3f), Colour = colourProvider.Background5,
}, },
new Container new Container
{ {
@ -68,7 +71,7 @@ namespace osu.Game.Overlays.Changelog
Padding = new MarginPadding { Horizontal = 75 }, Padding = new MarginPadding { Horizontal = 75 },
Children = new Drawable[] Children = new Drawable[]
{ {
textContainer = new FillFlowContainer new FillFlowContainer
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -76,91 +79,84 @@ namespace osu.Game.Overlays.Changelog
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Padding = new MarginPadding { Right = 50 + image_container_width }, Padding = new MarginPadding { Right = 50 + image_container_width },
Children = new Drawable[]
{
new OsuSpriteText
{
Text = ChangelogStrings.SupportHeading,
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
Margin = new MarginPadding { Bottom = 20 },
},
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
{
t.Font = t.Font.With(size: 14);
t.Colour = colour.PinkLighter;
})
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
new OsuTextFlowContainer(t =>
{
t.Font = t.Font.With(size: 12);
t.Colour = colour.PinkLighter;
})
{
Text = ChangelogStrings.SupportText2.ToString(),
Margin = new MarginPadding { Top = 10 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
},
}, },
imageContainer = new Container new Container
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = image_container_width, Width = image_container_width,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Children = new Drawable[]
{
new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding { Bottom = 28 },
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Texture = textures.Get(@"Online/supporter-pippi"),
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(heart_size),
Margin = new MarginPadding { Top = 70 },
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = colour.Pink,
Radius = 10,
Roundness = heart_size / 2,
},
Child = new Sprite
{
Size = new Vector2(heart_size),
Texture = textures.Get(@"Online/supporter-heart"),
},
},
}
} }
} }
}, },
} }
}, },
}; };
}
[BackgroundDependencyLoader]
private void load(OsuColour colour, TextureStore textures)
{
SupporterPromoLinkFlowContainer supportLinkText;
textContainer.Children = new Drawable[]
{
new OsuSpriteText
{
Text = ChangelogStrings.SupportHeading,
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light),
Margin = new MarginPadding { Bottom = 20 },
},
supportLinkText = new SupporterPromoLinkFlowContainer(t =>
{
t.Font = t.Font.With(size: 14);
t.Colour = colour.PinkLighter;
})
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
new OsuTextFlowContainer(t =>
{
t.Font = t.Font.With(size: 12);
t.Colour = colour.PinkLighter;
})
{
Text = ChangelogStrings.SupportText2.ToString(),
Margin = new MarginPadding { Top = 10 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
}
};
supportLinkText.AddText("Support further development of osu! and "); supportLinkText.AddText("Support further development of osu! and ");
supportLinkText.AddLink("become an osu!supporter", @"https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold)); supportLinkText.AddLink("become an osu!supporter", @"https://osu.ppy.sh/home/support", t => t.Font = t.Font.With(weight: FontWeight.Bold));
supportLinkText.AddText(" today!"); supportLinkText.AddText(" today!");
imageContainer.Children = new Drawable[]
{
new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding { Bottom = 28 },
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Texture = textures.Get(@"Online/supporter-pippi"),
},
new Container
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Size = new Vector2(heart_size),
Margin = new MarginPadding { Top = 70 },
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = colour.Pink,
Radius = 10,
Roundness = heart_size / 2,
},
Child = new Sprite
{
Size = new Vector2(heart_size),
Texture = textures.Get(@"Online/supporter-heart"),
},
},
};
} }
private class SupporterPromoLinkFlowContainer : LinkFlowContainer private class SupporterPromoLinkFlowContainer : LinkFlowContainer

View File

@ -107,9 +107,9 @@ namespace osu.Game.Overlays.Mods
var incompatibleTypes = mod.IncompatibleMods; var incompatibleTypes = mod.IncompatibleMods;
var allMods = ruleset.Value.CreateInstance().GetAllMods(); 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"; incompatibleText.Text = incompatibleMods.Value.Any() ? "Incompatible with:" : "Compatible with all mods";
} }
} }

View File

@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using Newtonsoft.Json; using osu.Framework.Graphics.Sprites;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
@ -11,7 +11,37 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// The shortened name of this mod. /// The shortened name of this mod.
/// </summary> /// </summary>
[JsonProperty("acronym")]
string Acronym { get; } string Acronym { get; }
/// <summary>
/// The name of this mod.
/// </summary>
string Name { get; }
/// <summary>
/// The user readable description of this mod.
/// </summary>
string Description { get; }
/// <summary>
/// The type of this mod.
/// </summary>
ModType Type { get; }
/// <summary>
/// The icon of this mod.
/// </summary>
IconUsage? Icon { get; }
/// <summary>
/// Whether this mod is playable by an end user.
/// Should be <c>false</c> for cases where the user is not interacting with the game (so it can be excluded from multiplayer selection, for example).
/// </summary>
bool UserPlayable { get; }
/// <summary>
/// Create a fresh <see cref="Mod"/> instance based on this mod.
/// </summary>
Mod CreateInstance() => (Mod)Activator.CreateInstance(GetType());
} }
} }

View File

@ -22,32 +22,17 @@ namespace osu.Game.Rulesets.Mods
[ExcludeFromDynamicCompile] [ExcludeFromDynamicCompile]
public abstract class Mod : IMod, IEquatable<Mod>, IDeepCloneable<Mod> public abstract class Mod : IMod, IEquatable<Mod>, IDeepCloneable<Mod>
{ {
/// <summary>
/// The name of this mod.
/// </summary>
[JsonIgnore] [JsonIgnore]
public abstract string Name { get; } public abstract string Name { get; }
/// <summary>
/// The shortened name of this mod.
/// </summary>
public abstract string Acronym { get; } public abstract string Acronym { get; }
/// <summary>
/// The icon of this mod.
/// </summary>
[JsonIgnore] [JsonIgnore]
public virtual IconUsage? Icon => null; public virtual IconUsage? Icon => null;
/// <summary>
/// The type of this mod.
/// </summary>
[JsonIgnore] [JsonIgnore]
public virtual ModType Type => ModType.Fun; public virtual ModType Type => ModType.Fun;
/// <summary>
/// The user readable description of this mod.
/// </summary>
[JsonIgnore] [JsonIgnore]
public abstract string Description { get; } public abstract string Description { get; }
@ -106,10 +91,6 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore] [JsonIgnore]
public virtual bool HasImplementation => this is IApplicableMod; public virtual bool HasImplementation => this is IApplicableMod;
/// <summary>
/// Whether this mod is playable by an end user.
/// Should be <c>false</c> for cases where the user is not interacting with the game (so it can be excluded from mutliplayer selection, for example).
/// </summary>
[JsonIgnore] [JsonIgnore]
public virtual bool UserPlayable => true; public virtual bool UserPlayable => true;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -38,13 +39,58 @@ namespace osu.Game.Rulesets
{ {
public RulesetInfo RulesetInfo { get; internal set; } public RulesetInfo RulesetInfo { get; internal set; }
public IEnumerable<Mod> GetAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>() private static readonly ConcurrentDictionary<int, IMod[]> mod_reference_cache = new ConcurrentDictionary<int, IMod[]>();
// Confine all mods of each mod type into a single IEnumerable<Mod>
.SelectMany(GetModsFor) /// <summary>
// Filter out all null mods /// A queryable source containing all available mods.
.Where(mod => mod != null) /// Call <see cref="IMod.CreateInstance"/> for consumption purposes.
// Resolve MultiMods as their .Mods property /// </summary>
.SelectMany(mod => (mod as MultiMod)?.Mods ?? new[] { mod }); public IEnumerable<IMod> 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<IMod>().ToArray();
return mods;
}
}
/// <summary>
/// Returns fresh instances of all mods.
/// </summary>
/// <remarks>
/// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings)
/// use <see cref="AllMods"/> instead.
/// </remarks>
public IEnumerable<Mod> CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast<ModType>()
// Confine all mods of each mod type into a single IEnumerable<Mod>
.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 });
/// <summary>
/// Returns a fresh instance of the mod matching the specified acronym.
/// </summary>
/// <param name="acronym">The acronym to query for .</param>
public Mod CreateModFromAcronym(string acronym)
{
return AllMods.FirstOrDefault(m => m.Acronym == acronym)?.CreateInstance();
}
/// <summary>
/// Returns a fresh instance of the mod matching the specified type.
/// </summary>
public T CreateMod<T>()
where T : Mod
{
return AllMods.FirstOrDefault(m => m is T)?.CreateInstance() as T;
}
public abstract IEnumerable<Mod> GetModsFor(ModType type); public abstract IEnumerable<Mod> GetModsFor(ModType type);
@ -126,7 +172,7 @@ namespace osu.Game.Rulesets
} }
[CanBeNull] [CanBeNull]
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().FirstOrDefault(); public ModAutoplay GetAutoplayMod() => CreateMod<ModAutoplay>();
public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null; public virtual ISkin CreateLegacySkinProvider([NotNull] ISkin skin, IBeatmap beatmap) => null;

View File

@ -30,12 +30,12 @@ namespace osu.Game.Rulesets.UI
private const float size = 80; private const float size = 80;
public virtual LocalisableString TooltipText => showTooltip ? mod.IconTooltip : null; public virtual LocalisableString TooltipText => showTooltip ? ((mod as Mod)?.IconTooltip ?? mod.Name) : null;
private Mod mod; private IMod mod;
private readonly bool showTooltip; private readonly bool showTooltip;
public Mod Mod public IMod Mod
{ {
get => mod; get => mod;
set set
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
/// <param name="mod">The mod to be displayed</param> /// <param name="mod">The mod to be displayed</param>
/// <param name="showTooltip">Whether a tooltip describing the mod should display on hover.</param> /// <param name="showTooltip">Whether a tooltip describing the mod should display on hover.</param>
public ModIcon(Mod mod, bool showTooltip = true) public ModIcon(IMod mod, bool showTooltip = true)
{ {
this.mod = mod ?? throw new ArgumentNullException(nameof(mod)); this.mod = mod ?? throw new ArgumentNullException(nameof(mod));
this.showTooltip = showTooltip; this.showTooltip = showTooltip;
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
updateMod(mod); updateMod(mod);
} }
private void updateMod(Mod value) private void updateMod(IMod value)
{ {
modAcronym.Text = value.Acronym; modAcronym.Text = value.Acronym;
modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question; modIcon.Icon = value.Icon ?? FontAwesome.Solid.Question;

View File

@ -67,7 +67,7 @@ namespace osu.Game.Scoring.Legacy
// lazer replays get a really high version number. // lazer replays get a really high version number.
if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION) if (version < LegacyScoreEncoder.FIRST_LAZER_VERSION)
scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.GetAllMods().OfType<ModClassic>().Single()).ToArray(); scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod<ModClassic>()).ToArray();
currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods);
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;

View File

@ -115,7 +115,9 @@ namespace osu.Game.Screens.Menu
if (setInfo == null) if (setInfo == null)
return false; return false;
return (initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0])) != null; initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
return UsingThemedIntro = initialBeatmap != null;
} }
} }
@ -165,7 +167,7 @@ namespace osu.Game.Screens.Menu
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack();
protected void StartTrack() protected virtual void StartTrack()
{ {
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
if (UsingThemedIntro) if (UsingThemedIntro)
@ -184,7 +186,6 @@ namespace osu.Game.Screens.Menu
{ {
beatmap.Value = initialBeatmap; beatmap.Value = initialBeatmap;
Track = initialBeatmap.Track; Track = initialBeatmap.Track;
UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice;
// ensure the track starts at maximum volume // ensure the track starts at maximum volume
musicController.CurrentTrack.FinishTransforms(); musicController.CurrentTrack.FinishTransforms();

View File

@ -41,6 +41,9 @@ namespace osu.Game.Screens.Menu
private Sample welcome; private Sample welcome;
private DecoupleableInterpolatingFramedClock decoupledClock;
private TrianglesIntroSequence intro;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -56,10 +59,18 @@ namespace osu.Game.Screens.Menu
{ {
PrepareMenuLoad(); PrepareMenuLoad();
LoadComponentAsync(new TrianglesIntroSequence(logo, background) decoupledClock = new DecoupleableInterpolatingFramedClock
{
IsCoupled = false
};
if (UsingThemedIntro)
decoupledClock.ChangeSource(Track);
LoadComponentAsync(intro = new TrianglesIntroSequence(logo, background)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(UsingThemedIntro ? Track : null), Clock = decoupledClock,
LoadMenu = LoadMenu LoadMenu = LoadMenu
}, t => }, t =>
{ {
@ -72,12 +83,25 @@ namespace osu.Game.Screens.Menu
} }
} }
public override void OnSuspending(IScreen next)
{
base.OnSuspending(next);
// important as there is a clock attached to a track which will likely be disposed before returning to this screen.
intro.Expire();
}
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
{ {
base.OnResuming(last); base.OnResuming(last);
background.FadeOut(100); background.FadeOut(100);
} }
protected override void StartTrack()
{
decoupledClock.Start();
}
private class TrianglesIntroSequence : CompositeDrawable private class TrianglesIntroSequence : CompositeDrawable
{ {
private readonly OsuLogo logo; private readonly OsuLogo logo;

View File

@ -34,7 +34,7 @@ namespace osu.Game.Tests.Beatmaps
protected void TestToLegacy(LegacyMods expectedLegacyMods, Type[] providedModTypes) protected void TestToLegacy(LegacyMods expectedLegacyMods, Type[] providedModTypes)
{ {
var ruleset = CreateRuleset(); var ruleset = CreateRuleset();
var modInstances = ruleset.GetAllMods() var modInstances = ruleset.CreateAllMods()
.Where(mod => providedModTypes.Contains(mod.GetType())) .Where(mod => providedModTypes.Contains(mod.GetType()))
.ToArray(); .ToArray();
var actualLegacyMods = ruleset.ConvertToLegacyMods(modInstances); var actualLegacyMods = ruleset.ConvertToLegacyMods(modInstances);

View File

@ -28,7 +28,7 @@ namespace osu.Game.Tests
RulesetID = ruleset.ID ?? 0; RulesetID = ruleset.ID ?? 0;
Mods = excessMods Mods = excessMods
? ruleset.CreateInstance().GetAllMods().ToArray() ? ruleset.CreateInstance().CreateAllMods().ToArray()
: new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; : new Mod[] { new TestModHardRock(), new TestModDoubleTime() };
TotalScore = 2845370; TotalScore = 2845370;

View File

@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual
if (!AllowFail) if (!AllowFail)
{ {
var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); var noFailMod = ruleset.CreateMod<ModNoFail>();
if (noFailMod != null) if (noFailMod != null)
SelectedMods.Value = new[] { noFailMod }; SelectedMods.Value = new[] { noFailMod };
} }