1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 07:23:14 +08:00

Merge branch 'master' into chat-command

This commit is contained in:
Dan Balasescu 2021-09-13 13:32:44 +09:00 committed by GitHub
commit d9b4fae4e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 1569 additions and 432 deletions

View File

@ -1,8 +1,8 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Benchmarks" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0/osu.Game.Benchmarks.dll" />
<option name="PROGRAM_PARAMETERS" value="--filter *" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Release/net5.0" />
<option name="EXE_PATH" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0/osu.Game.Benchmarks.dll" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/osu.Game.Benchmarks/bin/Debug/net5.0" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="USE_MONO" value="0" />
@ -14,7 +14,7 @@
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net5.0" />
<method v="2">
<option name="Build" enabled="true" />
<option name="Build" />
</method>
</configuration>
</component>

View File

@ -31,12 +31,11 @@ If you are looking to install or test osu! without setting up a development envi
**Latest build:**
| [Windows (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS(iOS 10+)](https://osu.ppy.sh/home/testflight) | [Android (5+)](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | [macOS 10.12+](https://github.com/ppy/osu/releases/latest/download/osu.app.zip) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk)
| ------------- | ------------- | ------------- | ------------- | ------------- |
- The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets.
- When running on Windows 7 or 8.1, *[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net50&pivots=os-windows#dependencies)** may be required to correctly run .NET 5 applications if your operating system is not up-to-date with the latest service packs.
If your platform is not listed above, there is still a chance you can manually build it by following the instructions below.
## Developing a custom ruleset

View File

@ -14,9 +14,9 @@ namespace osu.Game.Benchmarks
[Params(1, 10, 100)]
public int Times { get; set; }
[GlobalSetup]
public void GlobalSetup()
public override void SetUp()
{
base.SetUp();
mod = new OsuModDoubleTime();
}

View File

@ -0,0 +1,62 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Engines;
using osu.Game.Online.API;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
namespace osu.Game.Benchmarks
{
public class BenchmarkRuleset : BenchmarkTest
{
private OsuRuleset ruleset;
private APIMod apiModDoubleTime;
private APIMod apiModDifficultyAdjust;
public override void SetUp()
{
base.SetUp();
ruleset = new OsuRuleset();
apiModDoubleTime = new APIMod { Acronym = "DT" };
apiModDifficultyAdjust = new APIMod { Acronym = "DA" };
}
[Benchmark]
public void BenchmarkToModDoubleTime()
{
apiModDoubleTime.ToMod(ruleset);
}
[Benchmark]
public void BenchmarkToModDifficultyAdjust()
{
apiModDifficultyAdjust.ToMod(ruleset);
}
[Benchmark]
public void BenchmarkGetAllMods()
{
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

@ -1,6 +1,7 @@
// 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 BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;
namespace osu.Game.Benchmarks
@ -11,7 +12,7 @@ namespace osu.Game.Benchmarks
{
BenchmarkSwitcher
.FromAssembly(typeof(Program).Assembly)
.Run(args);
.Run(args, DefaultConfig.Instance.WithOption(ConfigOptions.DisableOptimizationsValidator, true));
}
}
}

View File

@ -1,6 +1,7 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Allocation;
@ -13,6 +14,10 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Framework.Bindables;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
namespace osu.Game.Rulesets.Mania.Tests
{
@ -22,14 +27,65 @@ namespace osu.Game.Rulesets.Mania.Tests
[Resolved]
private RulesetConfigCache configCache { get; set; }
private readonly Bindable<bool> configTimingBasedNoteColouring = new Bindable<bool>();
private Bindable<bool> configTimingBasedNoteColouring;
protected override void LoadComplete()
private ManualClock clock;
private DrawableManiaRuleset drawableRuleset;
[SetUpSteps]
public void SetUpSteps()
{
AddStep("setup hierarchy", () => Child = new Container
{
Clock = new FramedClock(clock = new ManualClock()),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
drawableRuleset = (DrawableManiaRuleset)Ruleset.Value.CreateInstance().CreateDrawableRulesetWith(createTestBeatmap())
}
});
AddStep("retrieve config bindable", () =>
{
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
configTimingBasedNoteColouring = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring);
});
}
[Test]
public void TestSimple()
{
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
}
[Test]
public void TestToggleOffScreen()
{
AddStep("enable", () => configTimingBasedNoteColouring.Value = true);
seekTo(10000);
AddStep("disable", () => configTimingBasedNoteColouring.Value = false);
seekTo(0);
AddAssert("all notes not coloured", () => this.ChildrenOfType<DrawableNote>().All(note => note.Colour == Colour4.White));
seekTo(10000);
AddStep("enable again", () => configTimingBasedNoteColouring.Value = true);
seekTo(0);
AddAssert("some notes coloured", () => this.ChildrenOfType<DrawableNote>().Any(note => note.Colour != Colour4.White));
}
private void seekTo(double time)
{
AddStep($"seek to {time}", () => clock.CurrentTime = time);
AddUntilStep("wait for seek", () => Precision.AlmostEquals(drawableRuleset.FrameStableClock.CurrentTime, time, 1));
}
private ManiaBeatmap createTestBeatmap()
{
const double beat_length = 500;
var ruleset = new ManiaRuleset();
var beatmap = new ManiaBeatmap(new StageDefinition { Columns = 1 })
{
HitObjects =
@ -45,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Tests
new Note { StartTime = beat_length }
},
ControlPointInfo = new ControlPointInfo(),
BeatmapInfo = { Ruleset = ruleset.RulesetInfo },
BeatmapInfo = { Ruleset = Ruleset.Value },
};
foreach (var note in beatmap.HitObjects)
@ -57,24 +113,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
BeatLength = beat_length
});
Child = new Container
{
Clock = new FramedClock(new ManualClock()),
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new[]
{
ruleset.CreateDrawableRulesetWith(beatmap)
}
};
var config = (ManiaRulesetConfigManager)configCache.GetConfigFor(Ruleset.Value.CreateInstance());
config.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
AddStep("Enable", () => configTimingBasedNoteColouring.Value = true);
AddStep("Disable", () => configTimingBasedNoteColouring.Value = false);
return beatmap;
}
}
}

View File

@ -66,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
}
protected override void OnApply()
{
base.OnApply();
updateSnapColour();
}
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
{
base.OnDirectionChanged(e);

View File

@ -64,6 +64,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsFalse(beatmapInfo.LetterboxInBreaks);
Assert.IsFalse(beatmapInfo.SpecialStyle);
Assert.IsFalse(beatmapInfo.WidescreenStoryboard);
Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate);
Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown);
Assert.AreEqual(0, beatmapInfo.CountdownOffset);
}

View File

@ -3,6 +3,7 @@
using NUnit.Framework;
using osu.Game.Online.API;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
namespace osu.Game.Tests.Mods
@ -11,26 +12,42 @@ namespace osu.Game.Tests.Mods
public class ModSettingsEqualityComparison
{
[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 mod2 = 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 apiMod3 = new APIMod(mod3);
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
var doulbeConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
var doulbeConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
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(apiMod2, Is.EqualTo(apiMod2));
Assert.That(doulbeConvertedMod2, Is.EqualTo(doulbeConvertedMod2));
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(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.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Configuration;
@ -71,7 +70,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.CreateMod<ModNoFail>() };
Player = CreatePlayer(ruleset);

View File

@ -130,6 +130,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
Type = { Value = MatchType.HeadToHead },
}));
AddUntilStep("wait for panel load", () => drawableRoom.ChildrenOfType<RecentParticipantsList>().Any());
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
AddStep("set password", () => room.Password.Value = "password");

View File

@ -51,6 +51,24 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("room join password correct", () => lastJoinedPassword == null);
}
[Test]
public void TestPopoverHidesOnBackButton()
{
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
AddStep("select room", () => InputManager.Key(Key.Down));
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
AddAssert("textbox has focus", () => InputManager.FocusedDrawable is OsuPasswordTextBox);
AddStep("hit escape", () => InputManager.Key(Key.Escape));
AddAssert("textbox lost focus", () => InputManager.FocusedDrawable is SearchTextBox);
AddStep("hit escape", () => InputManager.Key(Key.Escape));
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableLoungeRoom.PasswordEntryPopover>().Any());
}
[Test]
public void TestPopoverHidesOnLeavingScreen()
{

View File

@ -15,6 +15,7 @@ using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Menu;
using osu.Game.Screens.OnlinePlay.Components;
using osu.Game.Screens.OnlinePlay.Lounge;
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);
}
[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() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape));

View File

@ -5,10 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
@ -17,10 +17,11 @@ using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
using osu.Game.Scoring;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneBeatmapListingOverlay : OsuTestScene
public class TestSceneBeatmapListingOverlay : OsuManualInputManagerTestScene
{
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();
[BackgroundDependencyLoader]
private void load()
[SetUpSteps]
public void SetUpSteps()
{
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
((DummyAPIAccess)API).HandleRequest = req =>
AddStep("setup overlay", () =>
{
if (!(req is SearchBeatmapSetsRequest searchBeatmapSetsRequest)) return false;
searchBeatmapSetsRequest.TriggerSuccess(new SearchBeatmapSetsResponse
{
BeatmapSets = setsForResponse,
});
return true;
};
Child = overlay = new BeatmapListingOverlay { State = { Value = Visibility.Visible } };
setsForResponse.Clear();
});
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
((DummyAPIAccess)API).LocalUser.Value = new User
api.LocalUser.Value = new User
{
Username = "TestBot",
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]
public void TestNoBeatmapsPlaceholder()
{
@ -63,7 +115,7 @@ namespace osu.Game.Tests.Visual.Online
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
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());
AddUntilStep("placeholder shown", () => overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().SingleOrDefault()?.IsPresent == true);
@ -193,13 +245,15 @@ namespace osu.Game.Tests.Visual.Online
noPlaceholderShown();
}
private static int searchCount;
private void fetchFor(params BeatmapSetInfo[] beatmaps)
{
setsForResponse.Clear();
setsForResponse.AddRange(beatmaps.Select(b => new TestAPIBeatmapSet(b)));
// trigger arbitrary change for fetching.
searchControl.Query.TriggerChange();
searchControl.Query.Value = $"search {searchCount++}";
}
private void setRankAchievedFilter(ScoreRank[] ranks)
@ -229,8 +283,8 @@ namespace osu.Game.Tests.Visual.Online
private void noPlaceholderShown()
{
AddUntilStep("no placeholder shown", () =>
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any()
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any());
!overlay.ChildrenOfType<BeatmapListingOverlay.SupporterRequiredDrawable>().Any(d => d.IsPresent)
&& !overlay.ChildrenOfType<BeatmapListingOverlay.NotFoundDrawable>().Any(d => d.IsPresent));
}
private class TestAPIBeatmapSet : APIBeatmapSet

View File

@ -21,6 +21,8 @@ namespace osu.Game.Tests.Visual.Online
protected override bool UseOnlineAPI => true;
private int nextBeatmapSetId = 1;
public TestSceneBeatmapSetOverlay()
{
Add(overlay = new TestBeatmapSetOverlay());
@ -240,12 +242,23 @@ namespace osu.Game.Tests.Visual.Online
{
AddStep("show explicit map", () =>
{
var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
var beatmapSet = getBeatmapSet();
beatmapSet.OnlineInfo.HasExplicitContent = true;
overlay.ShowBeatmapSet(beatmapSet);
});
}
[Test]
public void TestFeaturedBeatmap()
{
AddStep("show featured map", () =>
{
var beatmapSet = getBeatmapSet();
beatmapSet.OnlineInfo.TrackId = 1;
overlay.ShowBeatmapSet(beatmapSet);
});
}
[Test]
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)
{
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]
private void load(RulesetStore rulesets)
{
var normal = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
var normal = getBeatmapSet();
normal.OnlineInfo.HasVideo = true;
normal.OnlineInfo.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets);
var explicitMap = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
var explicitMap = getBeatmapSet();
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
{
RelativeSizeAxes = Axes.Both,
@ -125,13 +132,19 @@ namespace osu.Game.Tests.Visual.Online
new GridBeatmapPanel(undownloadable),
new GridBeatmapPanel(manyDifficulties),
new GridBeatmapPanel(explicitMap),
new GridBeatmapPanel(featuredMap),
new GridBeatmapPanel(explicitFeaturedMap),
new ListBeatmapPanel(normal),
new ListBeatmapPanel(undownloadable),
new ListBeatmapPanel(manyDifficulties),
new ListBeatmapPanel(explicitMap)
new ListBeatmapPanel(explicitMap),
new ListBeatmapPanel(featuredMap),
new ListBeatmapPanel(explicitFeaturedMap)
},
},
};
BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet;
}
}
}

View File

@ -32,6 +32,12 @@ namespace osu.Game.Tests.Visual.Playlists
private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType<RoomsContainer>().First();
[Test]
public void TestManyRooms()
{
AddStep("add rooms", () => RoomManager.AddRooms(500));
}
[Test]
public void TestScrollByDraggingRooms()
{

View File

@ -203,6 +203,71 @@ namespace osu.Game.Tests.Visual.Ranking
assertExpandedPanelCentred();
}
[Test]
public void TestKeyboardNavigation()
{
var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 };
var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 };
var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 };
createListStep(() => new ScorePanelList());
AddStep("add scores and select middle", () =>
{
// order of addition purposefully scrambled.
list.AddScore(middleScore);
list.AddScore(lowestScore);
list.AddScore(highestScore);
list.SelectedScore.Value = middleScore;
});
assertScoreState(highestScore, false);
assertScoreState(middleScore, true);
assertScoreState(lowestScore, false);
AddStep("press left", () => InputManager.Key(Key.Left));
assertScoreState(highestScore, true);
assertScoreState(middleScore, false);
assertScoreState(lowestScore, false);
assertExpandedPanelCentred();
AddStep("press left at start of list", () => InputManager.Key(Key.Left));
assertScoreState(highestScore, true);
assertScoreState(middleScore, false);
assertScoreState(lowestScore, false);
assertExpandedPanelCentred();
AddStep("press right", () => InputManager.Key(Key.Right));
assertScoreState(highestScore, false);
assertScoreState(middleScore, true);
assertScoreState(lowestScore, false);
assertExpandedPanelCentred();
AddStep("press right again", () => InputManager.Key(Key.Right));
assertScoreState(highestScore, false);
assertScoreState(middleScore, false);
assertScoreState(lowestScore, true);
assertExpandedPanelCentred();
AddStep("press right at end of list", () => InputManager.Key(Key.Right));
assertScoreState(highestScore, false);
assertScoreState(middleScore, false);
assertScoreState(lowestScore, true);
assertExpandedPanelCentred();
AddStep("press left", () => InputManager.Key(Key.Left));
assertScoreState(highestScore, false);
assertScoreState(middleScore, true);
assertScoreState(lowestScore, false);
assertExpandedPanelCentred();
}
private void createListStep(Func<ScorePanelList> creationFunc)
{
AddStep("create list", () => Child = list = creationFunc().With(d =>

View File

@ -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<ModEasy>().Single() };
SelectedMods.Value = new[] { ruleset.CreateMod<ModEasy>() };
});
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<ModHardRock>().Single() };
SelectedMods.Value = new[] { ruleset.CreateMod<ModHardRock>() };
});
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<ModDifficultyAdjust>().Single();
var difficultyAdjustMod = ruleset.CreateMod<ModDifficultyAdjust>();
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<OsuModDifficultyAdjust>().Single();
var difficultyAdjustMod = ruleset.CreateMod<OsuModDifficultyAdjust>();
var originalDifficulty = advancedStats.Beatmap.BaseDifficulty;
difficultyAdjustMod.ReadFromDifficulty(originalDifficulty);

View File

@ -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) };

View File

@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface
Width = 200,
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.
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
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("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();
testModsWithSameBaseType(
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)),
mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden)));
mania.CreateMod<ManiaModFadeIn>(),
mania.CreateMod<ManiaModHidden>());
}
[Test]

View File

@ -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().AllMods;
foreach (var mod in mods)
{

View File

@ -1,7 +1,6 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -49,7 +48,7 @@ namespace osu.Game.Tournament.Components
}
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)
return;

View File

@ -93,6 +93,12 @@ namespace osu.Game.Beatmaps
public bool WidescreenStoryboard { get; set; }
public bool EpilepsyWarning { get; set; }
/// <summary>
/// Whether or not sound samples should change rate when playing with speed-changing mods.
/// TODO: only read/write supported for now, requires implementation in gameplay.
/// </summary>
public bool SamplesMatchPlaybackRate { get; set; }
public CountdownType Countdown { get; set; } = CountdownType.Normal;
/// <summary>

View File

@ -129,6 +129,7 @@ namespace osu.Game.Beatmaps
Ruleset = ruleset,
Metadata = metadata,
WidescreenStoryboard = true,
SamplesMatchPlaybackRate = true,
}
}
};

View File

@ -90,6 +90,12 @@ namespace osu.Game.Beatmaps
/// The song language of this beatmap set.
/// </summary>
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

View File

@ -180,6 +180,10 @@ namespace osu.Game.Beatmaps.Formats
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
break;
case @"SamplesMatchPlaybackRate":
beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1;
break;
case @"Countdown":
beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value);
break;

View File

@ -105,8 +105,8 @@ namespace osu.Game.Beatmaps.Formats
if (beatmap.BeatmapInfo.RulesetID == 3)
writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}"));
writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}"));
// if (b.SamplesMatchPlaybackRate)
// writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate)
writer.WriteLine(@"SamplesMatchPlaybackRate: 1");
}
private void handleEditor(TextWriter writer)

View File

@ -4,6 +4,7 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
@ -167,9 +168,21 @@ namespace osu.Game.Configuration
}
}
private static readonly ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]> property_info_cache = new ConcurrentDictionary<Type, (SettingSourceAttribute, PropertyInfo)[]>();
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj)
{
foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
var type = obj.GetType();
if (!property_info_cache.TryGetValue(type, out var properties))
property_info_cache[type] = properties = getSettingsSourceProperties(type).ToArray();
return properties;
}
private static IEnumerable<(SettingSourceAttribute, PropertyInfo)> getSettingsSourceProperties(Type type)
{
foreach (var property in type.GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
{
var attr = property.GetCustomAttribute<SettingSourceAttribute>(true);

View File

@ -87,23 +87,25 @@ namespace osu.Game.Graphics.Containers
private void createLink(IEnumerable<Drawable> drawables, LinkDetails link, string tooltipText, Action action = null)
{
AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
var linkCompiler = CreateLinkCompiler(drawables.OfType<SpriteText>());
linkCompiler.RelativeSizeAxes = Axes.Both;
linkCompiler.TooltipText = tooltipText;
linkCompiler.Action = () =>
{
RelativeSizeAxes = Axes.Both,
TooltipText = tooltipText,
Action = () =>
{
if (action != null)
action();
else if (game != null)
game.HandleLink(link);
// fallback to handle cases where OsuGame is not available, ie. tournament client.
else if (link.Action == LinkAction.External)
host.OpenUrlExternally(link.Argument);
},
});
if (action != null)
action();
else if (game != null)
game.HandleLink(link);
// fallback to handle cases where OsuGame is not available, ie. tournament client.
else if (link.Action == LinkAction.External)
host.OpenUrlExternally(link.Argument);
};
AddInternal(linkCompiler);
}
protected virtual DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new DrawableLinkCompiler(parts);
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.
// However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation.
// Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow.

View File

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

View File

@ -4,14 +4,17 @@
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osuTK;
namespace osu.Game.Graphics.UserInterfaceV2
{
public class OsuPopover : Popover
public class OsuPopover : Popover, IKeyBindingHandler<GlobalAction>
{
private const float fade_duration = 250;
private const double scale_duration = 500;
@ -51,5 +54,23 @@ namespace osu.Game.Graphics.UserInterfaceV2
this.ScaleTo(0.7f, scale_duration, Easing.OutQuint);
this.FadeOut(fade_duration, Easing.OutQuint);
}
public bool OnPressed(GlobalAction action)
{
if (State.Value == Visibility.Hidden)
return false;
if (action == GlobalAction.Back)
{
Hide();
return true;
}
return false;
}
public void OnReleased(GlobalAction action)
{
}
}
}

View File

@ -0,0 +1,515 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using osu.Game.Database;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20210912144011_AddSamplesMatchPlaybackRate")]
partial class AddSamplesMatchPlaybackRate
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<double>("SliderMultiplier");
b.Property<double>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<double>("AudioLeadIn");
b.Property<double>("BPM");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("Countdown");
b.Property<int>("CountdownOffset");
b.Property<double>("DistanceSpacing");
b.Property<bool>("EpilepsyWarning");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<double>("Length");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SamplesMatchPlaybackRate");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<int>("Status");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("WidescreenStoryboard");
b.HasKey("ID");
b.HasIndex("BaseDifficultyID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash");
b.HasIndex("MD5Hash");
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("BeatmapInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<int>("AuthorID")
.HasColumnName("AuthorID");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<DateTimeOffset>("DateAdded");
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.Property<int>("Status");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapSetID")
.IsUnique();
b.ToTable("BeatmapSetInfo");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Key")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<int?>("SkinInfoID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("SkinInfoID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int?>("ScoreInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("ScoreInfoID");
b.ToTable("ScoreFileInfo");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<double>("Accuracy")
.HasColumnType("DECIMAL(1,4)");
b.Property<int>("BeatmapInfoID");
b.Property<int>("Combo");
b.Property<DateTimeOffset>("Date");
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int>("MaxCombo");
b.Property<string>("ModsJson")
.HasColumnName("Mods");
b.Property<long?>("OnlineScoreID");
b.Property<double?>("PP");
b.Property<int>("Rank");
b.Property<int>("RulesetID");
b.Property<string>("StatisticsJson")
.HasColumnName("Statistics");
b.Property<long>("TotalScore");
b.Property<int?>("UserID")
.HasColumnName("UserID");
b.Property<string>("UserString")
.HasColumnName("User");
b.HasKey("ID");
b.HasIndex("BeatmapInfoID");
b.HasIndex("OnlineScoreID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("ScoreInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.Property<int>("SkinInfoID");
b.HasKey("ID");
b.HasIndex("FileInfoID");
b.HasIndex("SkinInfoID");
b.ToTable("SkinFileInfo");
});
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Creator");
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.ToTable("SkinInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
.WithMany()
.HasForeignKey("BaseDifficultyID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
.WithMany("Beatmaps")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("Beatmaps")
.HasForeignKey("MetadataID");
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
.WithMany("Files")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("BeatmapSets")
.HasForeignKey("MetadataID");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.HasOne("osu.Game.Skinning.SkinInfo")
.WithMany("Settings")
.HasForeignKey("SkinInfoID");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Scoring.ScoreInfo")
.WithMany("Files")
.HasForeignKey("ScoreInfoID");
});
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
.WithMany("Scores")
.HasForeignKey("BeatmapInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
{
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Skinning.SkinInfo")
.WithMany("Files")
.HasForeignKey("SkinInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,23 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace osu.Game.Migrations
{
public partial class AddSamplesMatchPlaybackRate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "SamplesMatchPlaybackRate",
table: "BeatmapInfo",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "SamplesMatchPlaybackRate",
table: "BeatmapInfo");
}
}
}

View File

@ -81,6 +81,8 @@ namespace osu.Game.Migrations
b.Property<int>("RulesetID");
b.Property<bool>("SamplesMatchPlaybackRate");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");

View File

@ -16,7 +16,7 @@ using osu.Game.Utils;
namespace osu.Game.Online.API
{
[MessagePackObject]
public class APIMod : IMod, IEquatable<APIMod>
public class APIMod : IEquatable<APIMod>
{
[JsonProperty("acronym")]
[Key(0)]
@ -48,31 +48,31 @@ namespace osu.Game.Online.API
public Mod ToMod(Ruleset ruleset)
{
Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == 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}.");
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
if (Settings.Count > 0)
{
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
continue;
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
{
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
continue;
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue);
}
}
return resultMod;
}
public bool Equals(IMod other) => other is APIMod them && Equals(them);
public bool Equals(APIMod other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Acronym == other.Acronym &&
Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
return Acronym == other.Acronym && Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
}
public override string ToString()

View File

@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests
private readonly BeatmapInfo beatmap;
private readonly BeatmapLeaderboardScope scope;
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)
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.scope = scope;
this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset));
this.mods = mods ?? Array.Empty<Mod>();
this.mods = mods ?? Array.Empty<IMod>();
Success += onSuccess;
}

View File

@ -63,6 +63,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"ratings")]
private int[] ratings { get; set; }
[JsonProperty(@"track_id")]
private int? trackId { get; set; }
[JsonProperty(@"user_id")]
private int creatorId
{
@ -106,7 +109,8 @@ namespace osu.Game.Online.API.Requests.Responses
Availability = availability,
HasFavourited = hasFavourited,
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 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.
mods = mods.Append(rulesetInstance.GetAllMods().OfType<ModClassic>().Single()).ToArray();
mods = mods.Append(rulesetInstance.CreateMod<ModClassic>()).ToArray();
var scoreInfo = new ScoreInfo
{

View File

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

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private FillFlowContainer bottomPanel, statusContainer, titleContainer;
private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer;
private PlayButton playButton;
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),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
AutoSizeAxes = Axes.Both,
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
@ -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)
{
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 height = 70;
private FillFlowContainer statusContainer, titleContainer;
private FillFlowContainer statusContainer, titleContainer, artistContainer;
protected BeatmapPanelDownloadButton DownloadButton;
private PlayButton playButton;
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),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
AutoSizeAxes = Axes.Both,
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)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });

View File

@ -75,6 +75,7 @@ namespace osu.Game.Overlays
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Masking = true,
Padding = new MarginPadding { Horizontal = 20 },
Children = new Drawable[]
{
@ -186,21 +187,16 @@ namespace osu.Game.Overlays
if (lastContent != null)
{
var transform = lastContent.FadeOut(100, Easing.OutQuint);
lastContent.FadeOut(100, Easing.OutQuint);
if (lastContent == notFoundContent || lastContent == supporterRequiredContent)
{
// the placeholders may be used multiple times, so don't expire/dispose them.
transform.Schedule(() => panelTarget.Remove(lastContent));
}
else
{
// Consider the case when the new content is smaller than the last content.
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y).Then().Schedule(() => lastContent.Expire());
}
// Consider the case when the new content is smaller than the last content.
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
var sequence = lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y);
if (lastContent != notFoundContent && lastContent != supporterRequiredContent)
sequence.Then().Schedule(() => lastContent.Expire());
}
if (!content.IsAlive)
@ -208,6 +204,9 @@ namespace osu.Game.Overlays
content.FadeInFromZero(200, Easing.OutQuint);
currentContent = content;
// currentContent may be one of the placeholders, and still have BypassAutoSizeAxes set to Y from the last fade-out.
// restore to the initial state.
currentContent.BypassAutoSizeAxes = Axes.None;
}
protected override void Dispose(bool isDisposing)

View File

@ -37,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet
private readonly OsuSpriteText title, artist;
private readonly AuthorInfo author;
private readonly ExplicitContentBeatmapPill explicitContentPill;
private readonly FeaturedArtistBeatmapPill featuredArtistPill;
private readonly FillFlowContainer downloadButtonsContainer;
private readonly BeatmapAvailability beatmapAvailability;
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),
Margin = new MarginPadding { Bottom = 20 }
Direction = FillDirection.Horizontal,
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
{
@ -233,6 +249,7 @@ namespace osu.Game.Overlays.BeatmapSet
artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist);
explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0;
featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0;
onlineStatusPill.FadeIn(500, Easing.OutQuint);
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 readonly BindableList<Mod> SelectedMods = new BindableList<Mod>();
public readonly BindableList<IMod> SelectedMods = new BindableList<IMod>();
public readonly Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
private readonly FillFlowContainer<ModButton> modsContainer;
@ -54,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet
return;
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 =>
{
@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet
updateHighlighted();
}
private void selectionChanged(Mod mod, bool selected)
private void selectionChanged(IMod mod, bool selected)
{
if (selected)
SelectedMods.Add(mod);
@ -101,9 +101,9 @@ namespace osu.Game.Overlays.BeatmapSet
private const int duration = 200;
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)
{
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 heart_size = 75;
private readonly FillFlowContainer textContainer;
private readonly Container imageContainer;
public ChangelogSupporterPromo()
{
RelativeSizeAxes = Axes.X;
@ -38,6 +35,12 @@ namespace osu.Game.Overlays.Changelog
Vertical = 20,
Horizontal = 50,
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour, TextureStore textures, OverlayColourProvider colourProvider)
{
SupporterPromoLinkFlowContainer supportLinkText;
InternalChildren = new Drawable[]
{
@ -59,7 +62,7 @@ namespace osu.Game.Overlays.Changelog
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.3f),
Colour = colourProvider.Background5,
},
new Container
{
@ -68,7 +71,7 @@ namespace osu.Game.Overlays.Changelog
Padding = new MarginPadding { Horizontal = 75 },
Children = new Drawable[]
{
textContainer = new FillFlowContainer
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
@ -76,91 +79,84 @@ namespace osu.Game.Overlays.Changelog
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
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,
Width = image_container_width,
Anchor = 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.AddLink("become and 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!");
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
@ -170,27 +166,18 @@ namespace osu.Game.Overlays.Changelog
{
}
public new void AddLink(string text, string url, Action<SpriteText> creationParameters) =>
AddInternal(new SupporterPromoLinkCompiler(AddText(text, creationParameters)) { Url = url });
protected override DrawableLinkCompiler CreateLinkCompiler(IEnumerable<SpriteText> parts) => new SupporterPromoLinkCompiler(parts);
private class SupporterPromoLinkCompiler : DrawableLinkCompiler
{
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
public string Url;
public SupporterPromoLinkCompiler(IEnumerable<Drawable> parts)
: base(parts)
{
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
TooltipText = Url;
Action = () => game?.HandleLink(Url);
IdleColour = colour.PinkDark;
HoverColour = Color4.White;
}

View File

@ -107,9 +107,9 @@ namespace osu.Game.Overlays.Mods
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";
}
}

View File

@ -355,6 +355,12 @@ namespace osu.Game.Overlays.Volume
return base.OnMouseMove(e);
}
protected override bool OnHover(HoverEvent e)
{
State = SelectionState.Selected;
return false;
}
protected override void OnHoverLost(HoverLostEvent e)
{
}

View File

@ -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,37 @@ namespace osu.Game.Rulesets.Mods
/// <summary>
/// The shortened name of this mod.
/// </summary>
[JsonProperty("acronym")]
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]
public abstract class Mod : IMod, IEquatable<Mod>, IDeepCloneable<Mod>
{
/// <summary>
/// The name of this mod.
/// </summary>
[JsonIgnore]
public abstract string Name { get; }
/// <summary>
/// The shortened name of this mod.
/// </summary>
public abstract string Acronym { get; }
/// <summary>
/// The icon of this mod.
/// </summary>
[JsonIgnore]
public virtual IconUsage? Icon => null;
/// <summary>
/// The type of this mod.
/// </summary>
[JsonIgnore]
public virtual ModType Type => ModType.Fun;
/// <summary>
/// The user readable description of this mod.
/// </summary>
[JsonIgnore]
public abstract string Description { get; }
@ -106,10 +91,6 @@ namespace osu.Game.Rulesets.Mods
[JsonIgnore]
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]
public virtual bool UserPlayable => true;

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
@ -38,13 +39,58 @@ namespace osu.Game.Rulesets
{
public RulesetInfo RulesetInfo { get; internal set; }
public IEnumerable<Mod> GetAllMods() => 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 });
private static readonly ConcurrentDictionary<int, IMod[]> mod_reference_cache = new ConcurrentDictionary<int, IMod[]>();
/// <summary>
/// A queryable source containing all available mods.
/// Call <see cref="IMod.CreateInstance"/> for consumption purposes.
/// </summary>
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);
@ -126,7 +172,7 @@ namespace osu.Game.Rulesets
}
[CanBeNull]
public ModAutoplay GetAutoplayMod() => GetAllMods().OfType<ModAutoplay>().FirstOrDefault();
public ModAutoplay GetAutoplayMod() => CreateMod<ModAutoplay>();
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;
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;
public Mod Mod
public IMod Mod
{
get => mod;
set
@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.UI
/// </summary>
/// <param name="mod">The mod to be displayed</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.showTooltip = showTooltip;
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.UI
updateMod(mod);
}
private void updateMod(Mod value)
private void updateMod(IMod value)
{
modAcronym.Text = value.Acronym;
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.
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);
scoreInfo.Beatmap = currentBeatmap.BeatmapInfo;

View File

@ -25,6 +25,7 @@ namespace osu.Game.Screens.Edit.Setup
private LabelledSwitchButton widescreenSupport;
private LabelledSwitchButton epilepsyWarning;
private LabelledSwitchButton letterboxDuringBreaks;
private LabelledSwitchButton samplesMatchPlaybackRate;
public override LocalisableString Title => "Design";
@ -79,6 +80,12 @@ namespace osu.Game.Screens.Edit.Setup
Label = "Letterbox during breaks",
Description = "Adds horizontal letterboxing to give a cinematic look during breaks.",
Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks }
},
samplesMatchPlaybackRate = new LabelledSwitchButton
{
Label = "Samples match playback rate",
Description = "When enabled, all samples will speed up or slow down when rate-changing mods are enabled.",
Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate }
}
};
}
@ -96,6 +103,7 @@ namespace osu.Game.Screens.Edit.Setup
widescreenSupport.Current.BindValueChanged(_ => updateBeatmap());
epilepsyWarning.Current.BindValueChanged(_ => updateBeatmap());
letterboxDuringBreaks.Current.BindValueChanged(_ => updateBeatmap());
samplesMatchPlaybackRate.Current.BindValueChanged(_ => updateBeatmap());
}
private void updateCountdownSettingsVisibility() => CountdownSettings.FadeTo(EnableCountdown.Current.Value ? 1 : 0);
@ -115,6 +123,7 @@ namespace osu.Game.Screens.Edit.Setup
Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value;
Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value;
Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value;
Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value;
}
}
}

View File

@ -115,7 +115,9 @@ namespace osu.Game.Screens.Menu
if (setInfo == null)
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 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.
if (UsingThemedIntro)
@ -184,7 +186,6 @@ namespace osu.Game.Screens.Menu
{
beatmap.Value = initialBeatmap;
Track = initialBeatmap.Track;
UsingThemedIntro = !initialBeatmap.Track.IsDummyDevice;
// ensure the track starts at maximum volume
musicController.CurrentTrack.FinishTransforms();

View File

@ -41,6 +41,9 @@ namespace osu.Game.Screens.Menu
private Sample welcome;
private DecoupleableInterpolatingFramedClock decoupledClock;
private TrianglesIntroSequence intro;
[BackgroundDependencyLoader]
private void load()
{
@ -56,10 +59,18 @@ namespace osu.Game.Screens.Menu
{
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,
Clock = new FramedClock(UsingThemedIntro ? Track : null),
Clock = decoupledClock,
LoadMenu = LoadMenu
}, 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)
{
base.OnResuming(last);
background.FadeOut(100);
}
protected override void StartTrack()
{
decoupledClock.Start();
}
private class TrianglesIntroSequence : CompositeDrawable
{
private readonly OsuLogo logo;

View File

@ -43,6 +43,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
private PasswordProtectedIcon passwordIcon;
private EndDateInfo endDateInfo;
private DelayedLoadWrapper wrapper;
public DrawableRoom(Room room)
{
Room = room;
@ -63,6 +65,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colours)
{
ButtonsContainer = new Container
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X
};
InternalChildren = new[]
{
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
@ -75,155 +85,153 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
d.RelativeSizeAxes = Axes.Both;
}),
new Container
{
Name = @"Room content",
RelativeSizeAxes = Axes.Both,
// This negative padding resolves 1px gaps between this background and the background above.
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
Child = new Container
wrapper = new DelayedLoadWrapper(() =>
new Container
{
Name = @"Room content",
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = CORNER_RADIUS,
Children = new Drawable[]
// This negative padding resolves 1px gaps between this background and the background above.
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
Child = new Container
{
new GridContainer
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = CORNER_RADIUS,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
new GridContainer
{
new Dimension(GridSizeMode.Relative, 0.2f)
},
Content = new[]
{
new Drawable[]
RelativeSizeAxes = Axes.Both,
ColumnDimensions = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
},
}
}
},
new Container
{
Name = @"Left details",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Left = 20,
Vertical = 5
},
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new Drawable[]
{
new RoomStatusPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
specialCategoryPill = new RoomSpecialCategoryPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
endDateInfo = new EndDateInfo
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 3 },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new RoomNameText(),
new RoomHostText(),
}
}
},
new Dimension(GridSizeMode.Relative, 0.2f)
},
new FillFlowContainer
Content = new[]
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new Drawable[]
new Drawable[]
{
new PlaylistCountPill
new Box
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Both,
Colour = colours.Background5,
},
new StarRatingRangeDisplay
new Box
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Scale = new Vector2(0.8f)
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
},
}
}
},
new Container
{
Name = @"Left details",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
Left = 20,
Vertical = 5
},
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new Drawable[]
{
new RoomStatusPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
specialCategoryPill = new RoomSpecialCategoryPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
endDateInfo = new EndDateInfo
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 3 },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new RoomNameText(),
new RoomHostText(),
}
}
},
},
new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new Drawable[]
{
new PlaylistCountPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
new StarRatingRangeDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Scale = new Vector2(0.8f)
}
}
}
}
}
},
new FillFlowContainer
{
Name = "Right content",
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Padding = new MarginPadding
{
Right = 10,
Vertical = 20,
},
Children = new Drawable[]
new FillFlowContainer
{
ButtonsContainer = new Container
Name = "Right content",
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Padding = new MarginPadding
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X
Right = 10,
Vertical = 20,
},
recentParticipantsList = new RecentParticipantsList
Children = new Drawable[]
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
NumberOfCircles = NumberOfAvatars
ButtonsContainer,
recentParticipantsList = new RecentParticipantsList
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
NumberOfCircles = NumberOfAvatars
}
}
}
},
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
},
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
},
},
},
}, 0)
{
RelativeSizeAxes = Axes.Both,
}
};
}
@ -231,23 +239,28 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{
base.LoadComplete();
roomCategory.BindTo(Room.Category);
roomCategory.BindValueChanged(c =>
wrapper.DelayedLoadComplete += _ =>
{
if (c.NewValue == RoomCategory.Spotlight)
specialCategoryPill.Show();
else
specialCategoryPill.Hide();
}, true);
wrapper.FadeInFromZero(200);
roomType.BindTo(Room.Type);
roomType.BindValueChanged(t =>
{
endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
}, true);
roomCategory.BindTo(Room.Category);
roomCategory.BindValueChanged(c =>
{
if (c.NewValue == RoomCategory.Spotlight)
specialCategoryPill.Show();
else
specialCategoryPill.Hide();
}, true);
hasPassword.BindTo(Room.HasPassword);
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
roomType.BindTo(Room.Type);
roomType.BindValueChanged(t =>
{
endDateInfo.Alpha = t.NewValue == MatchType.Playlists ? 1 : 0;
}, true);
hasPassword.BindTo(Room.HasPassword);
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
};
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)

View File

@ -83,12 +83,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
{
base.LoadComplete();
if (matchingFilter)
this.FadeInFromZero(transition_duration);
else
Alpha = 0;
Alpha = matchingFilter ? 1 : 0;
selectionBox.Alpha = SelectedRoom.Value == Room ? 1 : 0;
SelectedRoom.BindValueChanged(updateSelectedRoom, true);
SelectedRoom.BindValueChanged(updateSelectedRoom);
}
private void updateSelectedRoom(ValueChangedEvent<Room> selected)

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
@ -306,18 +307,18 @@ namespace osu.Game.Screens.Ranking
if (expandedPanel == null)
return base.OnKeyDown(e);
var expandedPanelIndex = flow.GetPanelIndex(expandedPanel.Score);
switch (e.Key)
{
case Key.Left:
if (expandedPanelIndex > 0)
SelectedScore.Value = flow.Children[expandedPanelIndex - 1].Panel.Score;
var previousScore = flow.GetPreviousScore(expandedPanel.Score);
if (previousScore != null)
SelectedScore.Value = previousScore;
return true;
case Key.Right:
if (expandedPanelIndex < flow.Count - 1)
SelectedScore.Value = flow.Children[expandedPanelIndex + 1].Panel.Score;
var nextScore = flow.GetNextScore(expandedPanel.Score);
if (nextScore != null)
SelectedScore.Value = nextScore;
return true;
}
@ -336,6 +337,12 @@ namespace osu.Game.Screens.Ranking
public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count();
[CanBeNull]
public ScoreInfo GetPreviousScore(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).LastOrDefault()?.Panel.Score;
[CanBeNull]
public ScoreInfo GetNextScore(ScoreInfo score) => applySorting(Children).SkipWhile(s => s.Panel.Score != score).ElementAtOrDefault(1)?.Panel.Score;
private IEnumerable<ScorePanelTrackingContainer> applySorting(IEnumerable<Drawable> drawables) => drawables.OfType<ScorePanelTrackingContainer>()
.OrderByDescending(GetLayoutPosition)
.ThenBy(s => s.Panel.Score.OnlineScoreID);

View File

@ -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);

View File

@ -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;

View File

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