Merge branch 'master' into new-chat-announce-channel

This commit is contained in:
Bartłomiej Dach 2022-05-29 18:55:43 +02:00
commit f9f8a32380
No known key found for this signature in database
GPG Key ID: BCECCD4FA41F6497
41 changed files with 294 additions and 121 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.525.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2022.529.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. --> <!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -17,11 +17,11 @@ using osu.Framework.Testing.Input;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Tests.Gameplay;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Tests namespace osu.Game.Rulesets.Osu.Tests
@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Tests
public TestSceneGameplayCursor() public TestSceneGameplayCursor()
{ {
var ruleset = new OsuRuleset(); var ruleset = new OsuRuleset();
gameplayState = new GameplayState(CreateBeatmap(ruleset.RulesetInfo), ruleset, Array.Empty<Mod>()); gameplayState = TestGameplayState.Create(ruleset);
AddStep("change background colour", () => AddStep("change background colour", () =>
{ {

View File

@ -6,7 +6,6 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Graphics.Containers;
using osu.Framework.Lists; using osu.Framework.Lists;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Timing; using osu.Framework.Timing;
@ -22,7 +21,6 @@ using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Storyboards; using osu.Game.Storyboards;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
@ -33,18 +31,6 @@ namespace osu.Game.Tests.Visual.Gameplay
[Resolved] [Resolved]
private SkinManager skinManager { get; set; } private SkinManager skinManager { get; set; }
[Cached]
private ScoreProcessor scoreProcessor = new ScoreProcessor(new OsuRuleset());
[Cached(typeof(HealthProcessor))]
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
[Cached]
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset());
[Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());
protected override bool HasCustomSteps => true; protected override bool HasCustomSteps => true;
[Test] [Test]
@ -81,11 +67,19 @@ namespace osu.Game.Tests.Visual.Gameplay
if (expectedComponentsContainer == null) if (expectedComponentsContainer == null)
return false; return false;
var expectedComponentsAdjustmentContainer = new Container var expectedComponentsAdjustmentContainer = new DependencyProvidingContainer
{ {
Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content), Position = actualComponentsContainer.Parent.ToSpaceOfOtherDrawable(actualComponentsContainer.DrawPosition, Content),
Size = actualComponentsContainer.DrawSize, Size = actualComponentsContainer.DrawSize,
Child = expectedComponentsContainer, Child = expectedComponentsContainer,
// proxy the same required dependencies that `actualComponentsContainer` is using.
CachedDependencies = new (Type, object)[]
{
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
(typeof(GameplayClock), actualComponentsContainer.Dependencies.Get<GameplayClock>())
},
}; };
Add(expectedComponentsAdjustmentContainer); Add(expectedComponentsAdjustmentContainer);

View File

@ -15,7 +15,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Gameplay;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
[Cached] [Cached]
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached] [Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());

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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -14,16 +13,15 @@ using osu.Framework.Input.Events;
using osu.Framework.Input.StateChanges; using osu.Framework.Input.StateChanges;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Gameplay;
using osu.Game.Tests.Mods; using osu.Game.Tests.Mods;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -41,7 +39,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private TestReplayRecorder recorder; private TestReplayRecorder recorder;
[Cached] [Cached]
private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty<Mod>()); private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[SetUpSteps] [SetUpSteps]
public void SetUpSteps() public void SetUpSteps()

View File

@ -11,7 +11,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Skinning.Editor; using osu.Game.Skinning.Editor;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Gameplay;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
[Cached] [Cached]
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached] [Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());

View File

@ -16,7 +16,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Gameplay;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0); private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
[Cached] [Cached]
private GameplayState gameplayState = new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()); private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset());
[Cached] [Cached]
private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock()); private readonly GameplayClock gameplayClock = new GameplayClock(new FramedClock());

View File

@ -18,8 +18,8 @@ using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Gameplay;
using osu.Game.Tests.Visual.Multiplayer; using osu.Game.Tests.Visual.Multiplayer;
using osu.Game.Tests.Visual.Spectator; using osu.Game.Tests.Visual.Spectator;
using osuTK; using osuTK;
@ -259,12 +259,15 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestFinalFramesPurgedBeforeEndingPlay() public void TestFinalFramesPurgedBeforeEndingPlay()
{ {
AddStep("begin playing", () => spectatorClient.BeginPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()), new Score())); AddStep("begin playing", () => spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), new Score()));
AddStep("send frames and finish play", () => AddStep("send frames and finish play", () =>
{ {
spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero)); spectatorClient.HandleFrame(new OsuReplayFrame(1000, Vector2.Zero));
spectatorClient.EndPlaying(new GameplayState(new TestBeatmap(new OsuRuleset().RulesetInfo), new OsuRuleset()) { HasPassed = true });
var completedGameplayState = TestGameplayState.Create(new OsuRuleset());
completedGameplayState.HasPassed = true;
spectatorClient.EndPlaying(completedGameplayState);
}); });
// We can't access API because we're an "online" test. // We can't access API because we're an "online" test.

View File

@ -20,13 +20,13 @@ using osu.Game.Online.Spectator;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Replays.Legacy; using osu.Game.Replays.Legacy;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.Replays.Types;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Tests.Gameplay;
using osu.Game.Tests.Mods; using osu.Game.Tests.Mods;
using osu.Game.Tests.Visual.Spectator; using osu.Game.Tests.Visual.Spectator;
using osuTK; using osuTK;
@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Gameplay
CachedDependencies = new[] CachedDependencies = new[]
{ {
(typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())), (typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())),
(typeof(GameplayState), new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty<Mod>())) (typeof(GameplayState), TestGameplayState.Create(new OsuRuleset()))
}, },
Children = new Drawable[] Children = new Drawable[]
{ {

View File

@ -124,13 +124,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
Status = { Value = new RoomStatusOpen() }, Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.Spotlight }, Category = { Value = RoomCategory.Spotlight },
}), }),
createLoungeRoom(new Room
{
Name = { Value = "Featured artist room" },
Status = { Value = new RoomStatusOpen() },
Category = { Value = RoomCategory.FeaturedArtist },
}),
} }
}; };
}); });
AddUntilStep("wait for panel load", () => rooms.Count == 5); AddUntilStep("wait for panel load", () => rooms.Count == 6);
AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)) == 2); AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)) == 2);
AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 3); AddUntilStep("correct status text", () => rooms.ChildrenOfType<OsuSpriteText>().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 4);
} }
[Test] [Test]

View File

@ -134,7 +134,7 @@ namespace osu.Game.Tests.Visual.Online
channelManager.CurrentChannel.Value = joinedChannel; channelManager.CurrentChannel.Value = joinedChannel;
}); });
AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible); AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
AddUntilStep("Channel is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
} }
[Test] [Test]
@ -150,10 +150,14 @@ namespace osu.Game.Tests.Visual.Online
public void TestChatHeight() public void TestChatHeight()
{ {
BindableFloat configChatHeight = new BindableFloat(); BindableFloat configChatHeight = new BindableFloat();
config.BindWith(OsuSetting.ChatDisplayHeight, configChatHeight);
float newHeight = 0; float newHeight = 0;
AddStep("Reset config chat height", () => configChatHeight.SetDefault()); AddStep("Reset config chat height", () =>
{
config.BindWith(OsuSetting.ChatDisplayHeight, configChatHeight);
configChatHeight.SetDefault();
});
AddStep("Show overlay", () => chatOverlay.Show()); AddStep("Show overlay", () => chatOverlay.Show());
AddAssert("Overlay uses config height", () => chatOverlay.Height == configChatHeight.Default); AddAssert("Overlay uses config height", () => chatOverlay.Height == configChatHeight.Default);
AddStep("Click top bar", () => AddStep("Click top bar", () =>
@ -177,7 +181,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("Listing is visible", () => listingIsVisible); AddAssert("Listing is visible", () => listingIsVisible);
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
} }
[Test] [Test]
@ -186,7 +190,7 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Show overlay", () => chatOverlay.Show()); AddStep("Show overlay", () => chatOverlay.Show());
AddAssert("Listing is visible", () => listingIsVisible); AddAssert("Listing is visible", () => listingIsVisible);
AddStep("Search for 'number 2'", () => chatOverlayTextBox.Text = "number 2"); AddStep("Search for 'number 2'", () => chatOverlayTextBox.Text = "number 2");
AddUntilStep("Only channel 2 visibile", () => AddUntilStep("Only channel 2 visible", () =>
{ {
IEnumerable<ChannelListingItem> listingItems = chatOverlay.ChildrenOfType<ChannelListingItem>() IEnumerable<ChannelListingItem> listingItems = chatOverlay.ChildrenOfType<ChannelListingItem>()
.Where(item => item.IsPresent); .Where(item => item.IsPresent);
@ -276,7 +280,7 @@ namespace osu.Game.Tests.Visual.Online
}); });
}); });
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
} }
[Test] [Test]
@ -299,7 +303,7 @@ namespace osu.Game.Tests.Visual.Online
}); });
}); });
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); waitForChannel2Visible();
} }
[Test] [Test]
@ -323,7 +327,7 @@ namespace osu.Game.Tests.Visual.Online
}); });
AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2)); AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2));
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); waitForChannel2Visible();
} }
[Test] [Test]
@ -343,7 +347,7 @@ namespace osu.Game.Tests.Visual.Online
}); });
}); });
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
} }
[Test] [Test]
@ -364,7 +368,7 @@ namespace osu.Game.Tests.Visual.Online
}); });
AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null); AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null);
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1)); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
} }
[Test] [Test]
@ -374,6 +378,7 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
waitForChannel1Visible();
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel)); AddStep("Click drawable channel", () => clickDrawable(currentDrawableChannel));
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox); AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
@ -411,11 +416,11 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Finish channel 2 load", () => chatOverlay.GetSlowLoadingChannel(testChannel2).LoadEvent.Set()); AddStep("Finish channel 2 load", () => chatOverlay.GetSlowLoadingChannel(testChannel2).LoadEvent.Set());
AddAssert("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded); AddAssert("Channel 2 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel2).IsLoaded);
AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); waitForChannel2Visible();
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1))); AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
AddAssert("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded); AddAssert("Channel 1 loaded", () => chatOverlay.GetSlowLoadingChannel(testChannel1).IsLoaded);
AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
} }
[Test] [Test]
@ -426,13 +431,12 @@ namespace osu.Game.Tests.Visual.Online
channelManager.JoinChannel(testChannel1); channelManager.JoinChannel(testChannel1);
chatOverlay.Show(); chatOverlay.Show();
}); });
AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
AddStep("Press document close keys", () => InputManager.Keys(PlatformAction.DocumentClose)); AddStep("Press document close keys", () => InputManager.Keys(PlatformAction.DocumentClose));
AddAssert("Listing is visible", () => listingIsVisible); AddAssert("Listing is visible", () => listingIsVisible);
AddStep("Press tab restore keys", () => InputManager.Keys(PlatformAction.TabRestore)); AddStep("Press tab restore keys", () => InputManager.Keys(PlatformAction.TabRestore));
AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
} }
[Test] [Test]
@ -443,8 +447,7 @@ namespace osu.Game.Tests.Visual.Online
channelManager.JoinChannel(testChannel1); channelManager.JoinChannel(testChannel1);
chatOverlay.Show(); chatOverlay.Show();
}); });
AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
AddStep("Press tab new keys", () => InputManager.Keys(PlatformAction.TabNew)); AddStep("Press tab new keys", () => InputManager.Keys(PlatformAction.TabNew));
AddAssert("Listing is visible", () => listingIsVisible); AddAssert("Listing is visible", () => listingIsVisible);
} }
@ -466,24 +469,29 @@ namespace osu.Game.Tests.Visual.Online
chatOverlay.Show(); chatOverlay.Show();
}); });
AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1); waitForChannel1Visible();
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
waitForChannel2Visible();
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
AddAssert("Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel2); AddUntilStep("PM Channel 1 displayed", () => channelIsVisible && currentDrawableChannel?.Channel == pmChannel1);
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
AddAssert("PM Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel1); AddUntilStep("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel?.Channel == pmChannel2);
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
AddAssert("PM Channel 2 displayed", () => channelIsVisible && currentDrawableChannel.Channel == pmChannel2); AddUntilStep("Announce channel displayed", () => channelIsVisible && currentDrawableChannel?.Channel == announceChannel);
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext)); AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
AddAssert("Announce channel displayed", () => channelIsVisible && currentDrawableChannel.Channel == announceChannel); waitForChannel1Visible();
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
AddAssert("Channel 1 displayed", () => channelIsVisible && currentDrawableChannel.Channel == testChannel1);
} }
private void waitForChannel1Visible() =>
AddUntilStep("Channel 1 is visible", () => channelIsVisible && currentDrawableChannel?.Channel == testChannel1);
private void waitForChannel2Visible() =>
AddUntilStep("Channel 2 is visible", () => channelIsVisible && currentDrawableChannel?.Channel == testChannel2);
private bool listingIsVisible => private bool listingIsVisible =>
chatOverlay.ChildrenOfType<ChannelListing>().Single().State.Value == Visibility.Visible; chatOverlay.ChildrenOfType<ChannelListing>().Single().State.Value == Visibility.Visible;
@ -493,8 +501,9 @@ namespace osu.Game.Tests.Visual.Online
private bool channelIsVisible => private bool channelIsVisible =>
!listingIsVisible && !loadingIsVisible; !listingIsVisible && !loadingIsVisible;
[CanBeNull]
private DrawableChannel currentDrawableChannel => private DrawableChannel currentDrawableChannel =>
chatOverlay.ChildrenOfType<DrawableChannel>().Single(); chatOverlay.ChildrenOfType<DrawableChannel>().SingleOrDefault();
private ChannelListItem getChannelListItem(Channel channel) => private ChannelListItem getChannelListItem(Channel channel) =>
chatOverlay.ChildrenOfType<ChannelListItem>().Single(item => item.Channel == channel); chatOverlay.ChildrenOfType<ChannelListItem>().Single(item => item.Channel == channel);

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -13,6 +14,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Ranking.Statistics; using osu.Game.Screens.Ranking.Statistics;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -114,10 +116,7 @@ namespace osu.Game.Tests.Visual.Ranking
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
{
throw new NotImplementedException();
}
public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap) public override DifficultyCalculator CreateDifficultyCalculator(IWorkingBeatmap beatmap)
{ {
@ -151,6 +150,24 @@ namespace osu.Game.Tests.Visual.Ranking
} }
} }
}; };
private class TestBeatmapConverter : IBeatmapConverter
{
#pragma warning disable CS0067 // The event is never used
public event Action<HitObject, IEnumerable<HitObject>> ObjectConverted;
#pragma warning restore CS0067
public IBeatmap Beatmap { get; }
public TestBeatmapConverter(IBeatmap beatmap)
{
Beatmap = beatmap;
}
public bool CanConvert() => true;
public IBeatmap Convert(CancellationToken cancellationToken = default) => Beatmap.Clone();
}
} }
private class TestRulesetAllStatsRequireHitEvents : TestRuleset private class TestRulesetAllStatsRequireHitEvents : TestRuleset

View File

@ -4,6 +4,7 @@
using System; using System;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.Rooms;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -188,6 +189,24 @@ namespace osu.Game.Graphics
} }
} }
/// <summary>
/// Retrieves the main accent colour for a <see cref="RoomCategory"/>.
/// </summary>
public Color4? ForRoomCategory(RoomCategory roomCategory)
{
switch (roomCategory)
{
case RoomCategory.Spotlight:
return SpotlightColour;
case RoomCategory.FeaturedArtist:
return FeaturedArtistColour;
default:
return null;
}
}
/// <summary> /// <summary>
/// Returns a foreground text colour that is supposed to contrast well with /// Returns a foreground text colour that is supposed to contrast well with
/// the supplied <paramref name="backgroundColour"/>. /// the supplied <paramref name="backgroundColour"/>.
@ -360,5 +379,8 @@ namespace osu.Game.Graphics
public readonly Color4 ChatBlue = Color4Extensions.FromHex(@"17292e"); public readonly Color4 ChatBlue = Color4Extensions.FromHex(@"17292e");
public readonly Color4 ContextMenuGray = Color4Extensions.FromHex(@"223034"); public readonly Color4 ContextMenuGray = Color4Extensions.FromHex(@"223034");
public Color4 SpotlightColour => Green2;
public Color4 FeaturedArtistColour => Blue2;
} }
} }

View File

@ -20,6 +20,8 @@ namespace osu.Game.Online
{ {
public class HubClientConnector : IHubClientConnector public class HubClientConnector : IHubClientConnector
{ {
public const string SERVER_SHUTDOWN_MESSAGE = "Server is shutting down.";
/// <summary> /// <summary>
/// Invoked whenever a new hub connection is built, to configure it before it's started. /// Invoked whenever a new hub connection is built, to configure it before it's started.
/// </summary> /// </summary>
@ -64,20 +66,28 @@ namespace osu.Game.Online
this.preferMessagePack = preferMessagePack; this.preferMessagePack = preferMessagePack;
apiState.BindTo(api.State); apiState.BindTo(api.State);
apiState.BindValueChanged(state => apiState.BindValueChanged(state => connectIfPossible(), true);
{ }
switch (state.NewValue)
{
case APIState.Failing:
case APIState.Offline:
Task.Run(() => disconnect(true));
break;
case APIState.Online: public void Reconnect()
Task.Run(connect); {
break; Logger.Log($"{clientName} reconnecting...", LoggingTarget.Network);
} Task.Run(connectIfPossible);
}, true); }
private void connectIfPossible()
{
switch (apiState.Value)
{
case APIState.Failing:
case APIState.Offline:
Task.Run(() => disconnect(true));
break;
case APIState.Online:
Task.Run(connect);
break;
}
} }
private async Task connect() private async Task connect()

View File

@ -30,5 +30,10 @@ namespace osu.Game.Online
/// Invoked whenever a new hub connection is built, to configure it before it's started. /// Invoked whenever a new hub connection is built, to configure it before it's started.
/// </summary> /// </summary>
public Action<HubConnection>? ConfigureConnection { get; set; } public Action<HubConnection>? ConfigureConnection { get; set; }
/// <summary>
/// Reconnect if already connected.
/// </summary>
void Reconnect();
} }
} }

View File

@ -25,12 +25,7 @@ namespace osu.Game.Online.Multiplayer
Debug.Assert(exception != null); Debug.Assert(exception != null);
string message = exception is HubException string message = exception.GetHubExceptionMessage() ?? exception.Message;
// HubExceptions arrive with additional message context added, but we want to display the human readable message:
// "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once."
// We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now.
? exception.Message.Substring(exception.Message.IndexOf(':') + 1).Trim()
: exception.Message;
Logger.Log(message, level: LogLevel.Important); Logger.Log(message, level: LogLevel.Important);
onError?.Invoke(exception); onError?.Invoke(exception);
@ -40,5 +35,16 @@ namespace osu.Game.Online.Multiplayer
onSuccess?.Invoke(); onSuccess?.Invoke();
} }
}); });
public static string? GetHubExceptionMessage(this Exception exception)
{
if (exception is HubException hubException)
// HubExceptions arrive with additional message context added, but we want to display the human readable message:
// "An unexpected error occurred invoking 'AddPlaylistItem' on the server.InvalidStateException: Can't enqueue more than 3 items at once."
// We generally use the message field for a user-parseable error (eventually to be replaced), so drop the first part for now.
return hubException.Message.Substring(exception.Message.IndexOf(':') + 1).Trim();
return null;
}
} }
} }

View File

@ -3,10 +3,12 @@
#nullable enable #nullable enable
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -71,14 +73,23 @@ namespace osu.Game.Online.Multiplayer
} }
} }
protected override Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null) protected override async Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null)
{ {
if (!IsConnected.Value) if (!IsConnected.Value)
return Task.FromCanceled<MultiplayerRoom>(new CancellationToken(true)); throw new OperationCanceledException();
Debug.Assert(connection != null); Debug.Assert(connection != null);
return connection.InvokeAsync<MultiplayerRoom>(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); try
{
return await connection.InvokeAsync<MultiplayerRoom>(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty);
}
catch (HubException exception)
{
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
connector?.Reconnect();
throw;
}
} }
protected override Task LeaveRoomInternal() protected override Task LeaveRoomInternal()

View File

@ -1,6 +1,8 @@
// 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.ComponentModel;
namespace osu.Game.Online.Rooms namespace osu.Game.Online.Rooms
{ {
public enum RoomCategory public enum RoomCategory
@ -8,5 +10,8 @@ namespace osu.Game.Online.Rooms
// used for osu-web deserialization so names shouldn't be changed. // used for osu-web deserialization so names shouldn't be changed.
Normal, Normal,
Spotlight, Spotlight,
[Description("Featured Artist")]
FeaturedArtist,
} }
} }

View File

@ -5,10 +5,12 @@
using System.Diagnostics; using System.Diagnostics;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Online.Spectator namespace osu.Game.Online.Spectator
{ {
@ -47,14 +49,23 @@ namespace osu.Game.Online.Spectator
} }
} }
protected override Task BeginPlayingInternal(SpectatorState state) protected override async Task BeginPlayingInternal(SpectatorState state)
{ {
if (!IsConnected.Value) if (!IsConnected.Value)
return Task.CompletedTask; return;
Debug.Assert(connection != null); Debug.Assert(connection != null);
return connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); try
{
await connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state);
}
catch (HubException exception)
{
if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE)
connector?.Reconnect();
throw;
}
} }
protected override Task SendFramesInternal(FrameDataBundle bundle) protected override Task SendFramesInternal(FrameDataBundle bundle)

View File

@ -15,7 +15,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
BadgeText = BeatmapsetsStrings.FeaturedArtistBadgeLabel; BadgeText = BeatmapsetsStrings.FeaturedArtistBadgeLabel;
BadgeColour = colours.Blue1; BadgeColour = colours.FeaturedArtistColour;
// todo: add linking support to allow redirecting featured artist badge to corresponding track. // todo: add linking support to allow redirecting featured artist badge to corresponding track.
} }
} }

View File

@ -15,7 +15,7 @@ namespace osu.Game.Overlays.BeatmapSet
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
BadgeText = BeatmapsetsStrings.SpotlightBadgeLabel; BadgeText = BeatmapsetsStrings.SpotlightBadgeLabel;
BadgeColour = colours.Pink1; BadgeColour = colours.SpotlightColour;
// todo: add linking support to allow redirecting spotlight badge to https://osu.ppy.sh/wiki/en/Beatmap_Spotlights. // todo: add linking support to allow redirecting spotlight badge to https://osu.ppy.sh/wiki/en/Beatmap_Spotlights.
} }
} }

View File

@ -127,9 +127,12 @@ namespace osu.Game.Overlays.Settings.Sections
dropdownItems.Add(skin.ToLive(realm)); dropdownItems.Add(skin.ToLive(realm));
dropdownItems.Insert(protectedCount, random_skin_info); dropdownItems.Insert(protectedCount, random_skin_info);
skinDropdown.Items = dropdownItems; Schedule(() =>
{
skinDropdown.Items = dropdownItems;
updateSelectedSkinFromConfig(); updateSelectedSkinFromConfig();
});
} }
private void updateSelectedSkinFromConfig() private void updateSelectedSkinFromConfig()

View File

@ -329,7 +329,7 @@ namespace osu.Game.Overlays.Volume
if (isPrecise) if (isPrecise)
{ {
scrollAccumulation += delta * adjust_step * 0.1; scrollAccumulation += delta * adjust_step;
while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision)) while (Precision.AlmostBigger(Math.Abs(scrollAccumulation), precision))
{ {

View File

@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Edit
public abstract class DistancedHitObjectComposer<TObject> : HitObjectComposer<TObject>, IDistanceSnapProvider, IScrollBindingHandler<GlobalAction> public abstract class DistancedHitObjectComposer<TObject> : HitObjectComposer<TObject>, IDistanceSnapProvider, IScrollBindingHandler<GlobalAction>
where TObject : HitObject where TObject : HitObject
{ {
private const float adjust_step = 0.1f;
public Bindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1.0) public Bindable<double> DistanceSpacingMultiplier { get; } = new BindableDouble(1.0)
{ {
MinValue = 0.1, MinValue = 0.1,
@ -61,7 +63,7 @@ namespace osu.Game.Rulesets.Edit
Child = distanceSpacingSlider = new ExpandableSlider<double, SizeSlider<double>> Child = distanceSpacingSlider = new ExpandableSlider<double, SizeSlider<double>>
{ {
Current = { BindTarget = DistanceSpacingMultiplier }, Current = { BindTarget = DistanceSpacingMultiplier },
KeyboardStep = 0.1f, KeyboardStep = adjust_step,
} }
} }
}); });
@ -93,7 +95,7 @@ namespace osu.Game.Rulesets.Edit
{ {
case GlobalAction.EditorIncreaseDistanceSpacing: case GlobalAction.EditorIncreaseDistanceSpacing:
case GlobalAction.EditorDecreaseDistanceSpacing: case GlobalAction.EditorDecreaseDistanceSpacing:
return adjustDistanceSpacing(e.Action, 0.1f); return adjustDistanceSpacing(e.Action, adjust_step);
} }
return false; return false;
@ -109,7 +111,7 @@ namespace osu.Game.Rulesets.Edit
{ {
case GlobalAction.EditorIncreaseDistanceSpacing: case GlobalAction.EditorIncreaseDistanceSpacing:
case GlobalAction.EditorDecreaseDistanceSpacing: case GlobalAction.EditorDecreaseDistanceSpacing:
return adjustDistanceSpacing(e.Action, e.ScrollAmount * (e.IsPrecise ? 0.01f : 0.1f)); return adjustDistanceSpacing(e.Action, e.ScrollAmount * adjust_step);
} }
return false; return false;

View File

@ -493,7 +493,7 @@ namespace osu.Game.Screens.Edit
if (scrollAccumulation != 0 && Math.Sign(scrollAccumulation) != scrollDirection) if (scrollAccumulation != 0 && Math.Sign(scrollAccumulation) != scrollDirection)
scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation)); scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation));
scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1); scrollAccumulation += scrollComponent;
// because we are doing snapped seeking, we need to add up precise scrolls until they accumulate to an arbitrary cut-off. // because we are doing snapped seeking, we need to add up precise scrolls until they accumulate to an arbitrary cut-off.
while (Math.Abs(scrollAccumulation) >= precision) while (Math.Abs(scrollAccumulation) >= precision)

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Setup
Add(new Box Add(new Box
{ {
Colour = colourProvider.Background2, Colour = colourProvider.Background3,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}); });

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -28,6 +29,8 @@ namespace osu.Game.Screens.Edit.Timing
private Drawable weight; private Drawable weight;
private Drawable stick; private Drawable stick;
private IAdjustableClock metronomeClock;
[Resolved] [Resolved]
private OverlayColourProvider overlayColourProvider { get; set; } private OverlayColourProvider overlayColourProvider { get; set; }
@ -192,6 +195,8 @@ namespace osu.Game.Screens.Edit.Timing
Y = -3, Y = -3,
}, },
}; };
Clock = new FramedClock(metronomeClock = new StopwatchClock(true));
} }
private double beatLength; private double beatLength;
@ -216,6 +221,8 @@ namespace osu.Game.Screens.Edit.Timing
if (BeatSyncSource.ControlPoints == null || BeatSyncSource.Clock == null) if (BeatSyncSource.ControlPoints == null || BeatSyncSource.Clock == null)
return; return;
metronomeClock.Rate = IsBeatSyncedWithTrack ? BeatSyncSource.Clock.Rate : 1;
timingPoint = BeatSyncSource.ControlPoints.TimingPointAt(BeatSyncSource.Clock.CurrentTime); timingPoint = BeatSyncSource.ControlPoints.TimingPointAt(BeatSyncSource.Clock.CurrentTime);
if (beatLength != timingPoint.BeatLength) if (beatLength != timingPoint.BeatLength)

View File

@ -19,6 +19,8 @@ namespace osu.Game.Screens.Edit.Timing
private readonly string label; private readonly string label;
protected Drawable Background { get; private set; }
protected FillFlowContainer Content { get; private set; } protected FillFlowContainer Content { get; private set; }
public RowAttribute(ControlPoint point, string label) public RowAttribute(ControlPoint point, string label)
@ -41,11 +43,11 @@ namespace osu.Game.Screens.Edit.Timing
Masking = true; Masking = true;
CornerRadius = 3; CornerRadius = 3;
InternalChildren = new Drawable[] InternalChildren = new[]
{ {
new Box Background = new Box
{ {
Colour = overlayColours.Background4, Colour = overlayColours.Background5,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
Content = new FillFlowContainer Content = new FillFlowContainer

View File

@ -7,6 +7,7 @@ using osu.Framework.Extensions;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
namespace osu.Game.Screens.Edit.Timing.RowAttributes namespace osu.Game.Screens.Edit.Timing.RowAttributes
{ {
@ -24,10 +25,12 @@ namespace osu.Game.Screens.Edit.Timing.RowAttributes
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(OverlayColourProvider colourProvider)
{ {
Content.Add(text = new AttributeText(Point)); Content.Add(text = new AttributeText(Point));
Background.Colour = colourProvider.Background4;
timeSignature.BindValueChanged(_ => updateText()); timeSignature.BindValueChanged(_ => updateText());
beatLength.BindValueChanged(_ => updateText(), true); beatLength.BindValueChanged(_ => updateText(), true);
} }

View File

@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Verify
{ {
new Box new Box
{ {
Colour = colours.Background2, Colour = colours.Background3,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
new OsuScrollContainer new OsuScrollContainer

View File

@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Verify
ColumnDimensions = new[] ColumnDimensions = new[]
{ {
new Dimension(), new Dimension(),
new Dimension(GridSizeMode.Absolute, 200), new Dimension(GridSizeMode.Absolute, 250),
}, },
Content = new[] Content = new[]
{ {

View File

@ -30,8 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
{ {
status.BindValueChanged(s => status.BindValueChanged(s =>
{ {
this.FadeColour(category.Value == RoomCategory.Spotlight ? colours.Pink : s.NewValue.GetAppropriateColour(colours) this.FadeColour(colours.ForRoomCategory(category.Value) ?? s.NewValue.GetAppropriateColour(colours), transitionDuration);
, transitionDuration);
}, true); }, true);
} }
} }

View File

@ -237,7 +237,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
roomCategory.BindTo(Room.Category); roomCategory.BindTo(Room.Category);
roomCategory.BindValueChanged(c => roomCategory.BindValueChanged(c =>
{ {
if (c.NewValue == RoomCategory.Spotlight) if (c.NewValue > RoomCategory.Normal)
specialCategoryPill.Show(); specialCategoryPill.Show();
else else
specialCategoryPill.Hide(); specialCategoryPill.Hide();

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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -13,6 +14,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
public class RoomSpecialCategoryPill : OnlinePlayComposite public class RoomSpecialCategoryPill : OnlinePlayComposite
{ {
private SpriteText text; private SpriteText text;
private PillContainer pill;
[Resolved]
private OsuColour colours { get; set; }
public RoomSpecialCategoryPill() public RoomSpecialCategoryPill()
{ {
@ -20,9 +25,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
InternalChild = new PillContainer InternalChild = pill = new PillContainer
{ {
Background = Background =
{ {
@ -43,7 +48,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{ {
base.LoadComplete(); base.LoadComplete();
Category.BindValueChanged(c => text.Text = c.NewValue.ToString(), true); Category.BindValueChanged(c =>
{
text.Text = c.NewValue.GetLocalisableDescription();
var backgroundColour = colours.ForRoomCategory(Category.Value);
if (backgroundColour != null)
pill.Background.Colour = backgroundColour.Value;
}, true);
} }
} }
} }

View File

@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
{ {
foreach (var room in roomFlow) foreach (var room in roomFlow)
{ {
roomFlow.SetLayoutPosition(room, room.Room.Category.Value == RoomCategory.Spotlight roomFlow.SetLayoutPosition(room, room.Room.Category.Value > RoomCategory.Normal
// Always show spotlight playlists at the top of the listing. // Always show spotlight playlists at the top of the listing.
? float.MinValue ? float.MinValue
: -(room.Room.RoomID.Value ?? 0)); : -(room.Room.RoomID.Value ?? 0));

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.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -49,6 +50,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
case PlaylistsCategory.Spotlight: case PlaylistsCategory.Spotlight:
criteria.Category = @"spotlight"; criteria.Category = @"spotlight";
break; break;
case PlaylistsCategory.FeaturedArtist:
criteria.Category = @"featured_artist";
break;
} }
return criteria; return criteria;
@ -73,7 +78,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{ {
Any, Any,
Normal, Normal,
Spotlight Spotlight,
[Description("Featured Artist")]
FeaturedArtist,
} }
} }
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
@ -80,16 +81,13 @@ namespace osu.Game.Screens.Play.HUD
difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token)
.ContinueWith(task => Schedule(() => .ContinueWith(task => Schedule(() =>
{ {
if (task.Exception != null)
return;
timedAttributes = task.GetResultSafely(); timedAttributes = task.GetResultSafely();
IsValid = true; IsValid = true;
if (lastJudgement != null) if (lastJudgement != null)
onJudgementChanged(lastJudgement); onJudgementChanged(lastJudgement);
})); }), TaskContinuationOptions.OnlyOnRanToCompletion);
} }
} }

View File

@ -0,0 +1,32 @@
// 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.
#nullable enable
using System.Collections.Generic;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Gameplay
{
/// <summary>
/// Static class providing a <see cref="Create"/> convenience method to retrieve a correctly-initialised <see cref="GameplayState"/> instance in testing scenarios.
/// </summary>
public static class TestGameplayState
{
/// <summary>
/// Creates a correctly-initialised <see cref="GameplayState"/> instance for use in testing.
/// </summary>
public static GameplayState Create(Ruleset ruleset, IReadOnlyList<Mod>? mods = null, Score? score = null)
{
var beatmap = new TestBeatmap(ruleset.RulesetInfo);
var workingBeatmap = new TestWorkingBeatmap(beatmap);
var playableBeatmap = workingBeatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
return new GameplayState(playableBeatmap, ruleset, mods, score);
}
}
}

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 osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -24,6 +25,8 @@ namespace osu.Game.Tests.Visual
[Cached] [Cached]
protected new readonly EditorClock Clock; protected new readonly EditorClock Clock;
private readonly Bindable<double> frequencyAdjustment = new BindableDouble(1);
protected virtual bool ScrollUsingMouseWheel => true; protected virtual bool ScrollUsingMouseWheel => true;
protected EditorClockTestScene() protected EditorClockTestScene()
@ -44,14 +47,21 @@ namespace osu.Game.Tests.Visual
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
Beatmap.BindValueChanged(beatmapChanged, true); Beatmap.BindValueChanged(beatmapChanged, true);
AddSliderStep("editor clock rate", 0.0, 2.0, 1.0, v => frequencyAdjustment.Value = v);
} }
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e) private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> e)
{ {
e.OldValue?.Track.RemoveAdjustment(AdjustableProperty.Frequency, frequencyAdjustment);
Clock.Beatmap = e.NewValue.Beatmap; Clock.Beatmap = e.NewValue.Beatmap;
Clock.ChangeSource(e.NewValue.Track); Clock.ChangeSource(e.NewValue.Track);
Clock.ProcessFrame(); Clock.ProcessFrame();
e.NewValue.Track.AddAdjustment(AdjustableProperty.Frequency, frequencyAdjustment);
} }
protected override void Update() protected override void Update()

View File

@ -35,7 +35,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="Realm" Version="10.11.2" /> <PackageReference Include="Realm" Version="10.11.2" />
<PackageReference Include="ppy.osu.Framework" Version="2022.525.0" /> <PackageReference Include="ppy.osu.Framework" Version="2022.529.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
<PackageReference Include="Sentry" Version="3.17.1" /> <PackageReference Include="Sentry" Version="3.17.1" />
<PackageReference Include="SharpCompress" Version="0.31.0" /> <PackageReference Include="SharpCompress" Version="0.31.0" />

View File

@ -61,7 +61,7 @@
<Reference Include="System.Net.Http" /> <Reference Include="System.Net.Http" />
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2022.525.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2022.529.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
</ItemGroup> </ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) --> <!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net6.0) -->
@ -84,7 +84,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.14" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.14" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2022.525.0" /> <PackageReference Include="ppy.osu.Framework" Version="2022.529.0" />
<PackageReference Include="SharpCompress" Version="0.31.0" /> <PackageReference Include="SharpCompress" Version="0.31.0" />
<PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" /> <PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />