1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-30 06:52:53 +08:00

Merge branch 'master' into triangles-v2

This commit is contained in:
Dean Herbert 2022-11-21 14:55:01 +09:00 committed by GitHub
commit a07b033d22
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 616 additions and 225 deletions

View File

@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddAssert("player score matching expected bonus score", () => AddAssert("player score matching expected bonus score", () =>
{ {
// multipled by 2 to nullify the score multiplier. (autoplay mod selected) // multipled by 2 to nullify the score multiplier. (autoplay mod selected)
double totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2; long totalScore = ((ScoreExposedPlayer)Player).ScoreProcessor.TotalScore.Value * 2;
return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult; return totalScore == (int)(drawableSpinner.Result.RateAdjustedRotation / 360) * new SpinnerTick().CreateJudgement().MaxNumericResult;
}); });

View File

@ -248,6 +248,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
break; break;
} }
slider.Path.ExpectedDistance.Value = null;
piece.ControlPoint.Type = type; piece.ControlPoint.Type = type;
} }

View File

@ -52,11 +52,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
protected Texture? Texture { get; set; } protected Texture? Texture { get; set; }
private float radius => Texture?.DisplayWidth * 0.165f ?? 3; private float height => Texture?.DisplayHeight * 0.165f ?? 3;
private float width => Texture?.DisplayWidth * 0.165f ?? 3;
protected readonly List<SmokePoint> SmokePoints = new List<SmokePoint>(); protected readonly List<SmokePoint> SmokePoints = new List<SmokePoint>();
private float pointInterval => radius * 7f / 8; private float pointInterval => width * 7f / 8;
private double smokeStartTime { get; set; } = double.MinValue; private double smokeStartTime { get; set; } = double.MinValue;
@ -179,7 +181,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
private readonly List<SmokePoint> points = new List<SmokePoint>(); private readonly List<SmokePoint> points = new List<SmokePoint>();
private IVertexBatch<TexturedVertex2D>? quadBatch; private IVertexBatch<TexturedVertex2D>? quadBatch;
private float radius; private float width;
private float height;
private Vector2 drawSize; private Vector2 drawSize;
private Texture? texture; private Texture? texture;
private int rotationSeed; private int rotationSeed;
@ -202,7 +205,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
base.ApplyState(); base.ApplyState();
radius = Source.radius; width = Source.width;
height = Source.height;
drawSize = Source.DrawSize; drawSize = Source.DrawSize;
texture = Source.Texture; texture = Source.Texture;
@ -334,11 +338,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
var dir = PointDirection(point, index); var dir = PointDirection(point, index);
var ortho = dir.PerpendicularLeft; var ortho = dir.PerpendicularLeft;
dir *= scale * width;
ortho *= scale * height;
var localTopLeft = point.Position + (radius * scale * (-ortho - dir)); var localTopLeft = point.Position - ortho - dir;
var localTopRight = point.Position + (radius * scale * (-ortho + dir)); var localTopRight = point.Position - ortho + dir;
var localBotLeft = point.Position + (radius * scale * (ortho - dir)); var localBotLeft = point.Position + ortho - dir;
var localBotRight = point.Position + (radius * scale * (ortho + dir)); var localBotRight = point.Position + ortho + dir;
quadBatch.Add(new TexturedVertex2D quadBatch.Add(new TexturedVertex2D
{ {

View File

@ -0,0 +1,39 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{
[TestFixture]
public class TestSceneDrawableSwell : TaikoSkinnableTestScene
{
[Test]
public void TestHits()
{
AddStep("Centre hit", () => SetContents(_ => new DrawableSwell(createHitAtCurrentTime())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}));
}
private Swell createHitAtCurrentTime()
{
var hit = new Swell
{
StartTime = Time.Current + 3000,
EndTime = Time.Current + 6000,
};
hit.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
return hit;
}
}
}

View File

@ -0,0 +1,34 @@
// 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.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.Skinning.Argon
{
public class ArgonSwellCirclePiece : ArgonCirclePiece
{
[BackgroundDependencyLoader]
private void load()
{
AccentColour = ColourInfo.GradientVertical(
new Color4(240, 201, 0, 255),
new Color4(167, 139, 0, 255)
);
AddInternal(new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.Solid.Asterisk,
Size = new Vector2(ICON_SIZE),
Scale = new Vector2(0.8f, 1)
});
}
}
}

View File

@ -60,6 +60,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
case TaikoSkinComponents.TaikoExplosionMiss: case TaikoSkinComponents.TaikoExplosionMiss:
case TaikoSkinComponents.TaikoExplosionOk: case TaikoSkinComponents.TaikoExplosionOk:
return new ArgonHitExplosion(taikoComponent.Component); return new ArgonHitExplosion(taikoComponent.Component);
case TaikoSkinComponents.Swell:
return new ArgonSwellCirclePiece();
} }
break; break;

View File

@ -65,7 +65,7 @@ namespace osu.Game.Tests.Database
private class TestLegacyBeatmapImporter : LegacyBeatmapImporter private class TestLegacyBeatmapImporter : LegacyBeatmapImporter
{ {
public TestLegacyBeatmapImporter() public TestLegacyBeatmapImporter()
: base(null) : base(null!)
{ {
} }

View File

@ -35,7 +35,7 @@ namespace osu.Game.Tests.Gameplay
// Apply a miss judgement // Apply a miss judgement
scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement()) { Type = HitResult.Miss }); scoreProcessor.ApplyResult(new JudgementResult(new HitObject(), new TestJudgement()) { Type = HitResult.Miss });
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0.0)); Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(0));
} }
[Test] [Test]

View File

@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Editing
} }
[Test] [Test]
public void TestSharedClockState() public void TestClockTimeTransferIsOneDirectional()
{ {
AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000)); AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000));
AddStep("click test gameplay button", () => AddStep("click test gameplay button", () =>
@ -195,15 +195,15 @@ namespace osu.Game.Tests.Visual.Editing
GameplayClockContainer gameplayClockContainer = null; GameplayClockContainer gameplayClockContainer = null;
AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType<GameplayClockContainer>().First()); AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType<GameplayClockContainer>().First());
AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning); AddUntilStep("gameplay clock running", () => gameplayClockContainer.IsRunning);
// when the gameplay test is entered, the clock is expected to continue from where it was in the main editor...
AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000); AddAssert("gameplay time past 00:01:00", () => gameplayClockContainer.CurrentTime >= 60_000);
double timeAtPlayerExit = 0;
AddWaitStep("wait some", 5); AddWaitStep("wait some", 5);
AddStep("store time before exit", () => timeAtPlayerExit = gameplayClockContainer.CurrentTime);
AddStep("exit player", () => editorPlayer.Exit()); AddStep("exit player", () => editorPlayer.Exit());
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddAssert("time is past player exit", () => EditorClock.CurrentTime >= timeAtPlayerExit); // but when exiting from gameplay test back to editor, the expectation is that the editor time should revert to what it was at the point of initiating the gameplay test.
AddAssert("time reverted to 00:01:00", () => EditorClock.CurrentTime, () => Is.EqualTo(60_000));
} }
public override void TearDownSteps() public override void TearDownSteps()

View File

@ -3,14 +3,15 @@
#nullable disable #nullable disable
using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Timing;
using osu.Game.Screens.Edit.Timing.RowAttributes; using osu.Game.Screens.Edit.Timing.RowAttributes;
@ -21,10 +22,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture] [TestFixture]
public class TestSceneTimingScreen : EditorClockTestScene public class TestSceneTimingScreen : EditorClockTestScene
{ {
[Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
[Cached] [Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
@ -32,21 +29,27 @@ namespace osu.Game.Tests.Visual.Editing
protected override bool ScrollUsingMouseWheel => false; protected override bool ScrollUsingMouseWheel => false;
public TestSceneTimingScreen()
{
editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
}
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap); Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
Beatmap.Disabled = true; Beatmap.Disabled = true;
Child = timingScreen = new TimingScreen var editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(Ruleset.Value));
Child = new DependencyProvidingContainer
{ {
State = { Value = Visibility.Visible }, RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(EditorBeatmap), editorBeatmap),
(typeof(IBeatSnapProvider), editorBeatmap)
},
Child = timingScreen = new TimingScreen
{
State = { Value = Visibility.Visible },
},
}; };
} }

View File

@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
private TestGameplayLeaderboard leaderboard; private TestGameplayLeaderboard leaderboard;
private readonly BindableDouble playerScore = new BindableDouble(); private readonly BindableLong playerScore = new BindableLong();
public TestSceneGameplayLeaderboard() public TestSceneGameplayLeaderboard()
{ {
@ -76,8 +76,8 @@ namespace osu.Game.Tests.Visual.Gameplay
createLeaderboard(); createLeaderboard();
addLocalPlayer(); addLocalPlayer();
var player2Score = new BindableDouble(1234567); var player2Score = new BindableLong(1234567);
var player3Score = new BindableDouble(1111111); var player3Score = new BindableLong(1111111);
AddStep("add player 2", () => createLeaderboardScore(player2Score, new APIUser { Username = "Player 2" })); AddStep("add player 2", () => createLeaderboardScore(player2Score, new APIUser { Username = "Player 2" }));
AddStep("add player 3", () => createLeaderboardScore(player3Score, new APIUser { Username = "Player 3" })); AddStep("add player 3", () => createLeaderboardScore(player3Score, new APIUser { Username = "Player 3" }));
@ -161,9 +161,9 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
} }
private void createRandomScore(APIUser user) => createLeaderboardScore(new BindableDouble(RNG.Next(0, 5_000_000)), user); private void createRandomScore(APIUser user) => createLeaderboardScore(new BindableLong(RNG.Next(0, 5_000_000)), user);
private void createLeaderboardScore(BindableDouble score, APIUser user, bool isTracked = false) private void createLeaderboardScore(BindableLong score, APIUser user, bool isTracked = false)
{ {
var leaderboardScore = leaderboard.Add(user, isTracked); var leaderboardScore = leaderboard.Add(user, isTracked);
leaderboardScore.TotalScore.BindTo(score); leaderboardScore.TotalScore.BindTo(score);

View File

@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
@ -26,6 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly BindableList<ScoreInfo> scores = new BindableList<ScoreInfo>(); private readonly BindableList<ScoreInfo> scores = new BindableList<ScoreInfo>();
private readonly Bindable<bool> configVisibility = new Bindable<bool>(); private readonly Bindable<bool> configVisibility = new Bindable<bool>();
private readonly Bindable<PlayBeatmapDetailArea.TabType> beatmapTabType = new Bindable<PlayBeatmapDetailArea.TabType>();
private SoloGameplayLeaderboard leaderboard = null!; private SoloGameplayLeaderboard leaderboard = null!;
@ -33,6 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
config.BindWith(OsuSetting.BeatmapDetailTab, beatmapTabType);
} }
[SetUpSteps] [SetUpSteps]
@ -70,6 +73,25 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value); AddStep("toggle expanded", () => leaderboard.Expanded.Value = !leaderboard.Expanded.Value);
} }
[TestCase(PlayBeatmapDetailArea.TabType.Local, 51)]
[TestCase(PlayBeatmapDetailArea.TabType.Global, null)]
[TestCase(PlayBeatmapDetailArea.TabType.Country, null)]
[TestCase(PlayBeatmapDetailArea.TabType.Friends, null)]
public void TestTrackedScorePosition(PlayBeatmapDetailArea.TabType tabType, int? expectedOverflowIndex)
{
AddStep($"change TabType to {tabType}", () => beatmapTabType.Value = tabType);
AddUntilStep("tracked player is #50", () => leaderboard.TrackedScore?.ScorePosition, () => Is.EqualTo(50));
AddStep("add one more score", () => scores.Add(new ScoreInfo { User = new APIUser { Username = "New player 1" }, TotalScore = RNG.Next(600000, 1000000) }));
AddUntilStep("wait for sort", () => leaderboard.ChildrenOfType<GameplayLeaderboardScore>().First().ScorePosition != null);
if (expectedOverflowIndex == null)
AddUntilStep("tracked player has null position", () => leaderboard.TrackedScore?.ScorePosition, () => Is.Null);
else
AddUntilStep($"tracked player is #{expectedOverflowIndex}", () => leaderboard.TrackedScore?.ScorePosition, () => Is.EqualTo(expectedOverflowIndex));
}
[Test] [Test]
public void TestVisibility() public void TestVisibility()
{ {
@ -95,7 +117,7 @@ namespace osu.Game.Tests.Visual.Gameplay
new ScoreInfo { User = new APIUser { Username = @"spaceman_atlas" }, TotalScore = RNG.Next(500000, 1000000) }, new ScoreInfo { User = new APIUser { Username = @"spaceman_atlas" }, TotalScore = RNG.Next(500000, 1000000) },
new ScoreInfo { User = new APIUser { Username = @"frenzibyte" }, TotalScore = RNG.Next(500000, 1000000) }, new ScoreInfo { User = new APIUser { Username = @"frenzibyte" }, TotalScore = RNG.Next(500000, 1000000) },
new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) }, new ScoreInfo { User = new APIUser { Username = @"Susko3" }, TotalScore = RNG.Next(500000, 1000000) },
}.Concat(Enumerable.Range(0, 50).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList(); }.Concat(Enumerable.Range(0, 44).Select(i => new ScoreInfo { User = new APIUser { Username = $"User {i + 1}" }, TotalScore = 1000000 - i * 10000 })).ToList();
} }
} }
} }

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using System.Linq; using System.Linq;
using System.Threading;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions; using osu.Framework.Extensions;
@ -85,6 +86,19 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("did perform", () => actionPerformed); AddAssert("did perform", () => actionPerformed);
} }
[Test]
public void TestPerformEnsuresScreenIsLoaded()
{
TestLoadBlockingScreen screen = null;
AddStep("push blocking screen", () => Game.ScreenStack.Push(screen = new TestLoadBlockingScreen()));
AddStep("perform", () => Game.PerformFromScreen(_ => actionPerformed = true, new[] { typeof(TestLoadBlockingScreen) }));
AddAssert("action not performed", () => !actionPerformed);
AddStep("allow load", () => screen.LoadEvent.Set());
AddUntilStep("action performed", () => actionPerformed);
}
[Test] [Test]
public void TestOverlaysAlwaysClosed() public void TestOverlaysAlwaysClosed()
{ {
@ -270,5 +284,16 @@ namespace osu.Game.Tests.Visual.Navigation
return base.OnExiting(e); return base.OnExiting(e);
} }
} }
public class TestLoadBlockingScreen : OsuScreen
{
public readonly ManualResetEventSlim LoadEvent = new ManualResetEventSlim();
[BackgroundDependencyLoader]
private void load()
{
LoadEvent.Wait(10000);
}
}
} }
} }

View File

@ -7,12 +7,14 @@ using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.Navigation namespace osu.Game.Tests.Visual.Navigation
{ {
@ -55,6 +57,7 @@ namespace osu.Game.Tests.Visual.Navigation
presentAndConfirm(firstImport); presentAndConfirm(firstImport);
var secondImport = importBeatmap(3); var secondImport = importBeatmap(3);
confirmBeatmapInSongSelect(secondImport);
presentAndConfirm(secondImport); presentAndConfirm(secondImport);
// Test presenting same beatmap more than once // Test presenting same beatmap more than once
@ -74,6 +77,7 @@ namespace osu.Game.Tests.Visual.Navigation
presentAndConfirm(firstImport); presentAndConfirm(firstImport);
var secondImport = importBeatmap(3, new ManiaRuleset().RulesetInfo); var secondImport = importBeatmap(3, new ManiaRuleset().RulesetInfo);
confirmBeatmapInSongSelect(secondImport);
presentAndConfirm(secondImport); presentAndConfirm(secondImport);
presentSecondDifficultyAndConfirm(firstImport, 1); presentSecondDifficultyAndConfirm(firstImport, 1);
@ -134,13 +138,22 @@ namespace osu.Game.Tests.Visual.Navigation
return () => imported; return () => imported;
} }
private void confirmBeatmapInSongSelect(Func<BeatmapSetInfo> getImport)
{
AddUntilStep("beatmap in song select", () =>
{
var songSelect = (Screens.Select.SongSelect)Game.ScreenStack.CurrentScreen;
return songSelect.ChildrenOfType<BeatmapCarousel>().Single().BeatmapSets.Any(b => b.MatchesOnlineID(getImport()));
});
}
private void presentAndConfirm(Func<BeatmapSetInfo> getImport) private void presentAndConfirm(Func<BeatmapSetInfo> getImport)
{ {
AddStep("present beatmap", () => Game.PresentBeatmap(getImport())); AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.MatchesOnlineID(getImport())); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID));
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.Equals(getImport().Beatmaps.First().Ruleset)); AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset));
} }
private void presentSecondDifficultyAndConfirm(Func<BeatmapSetInfo> getImport, int importedID) private void presentSecondDifficultyAndConfirm(Func<BeatmapSetInfo> getImport, int importedID)
@ -148,9 +161,9 @@ namespace osu.Game.Tests.Visual.Navigation
Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 2048; Predicate<BeatmapInfo> pred = b => b.OnlineID == importedID * 2048;
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred)); AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID == importedID * 2048); AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 2048));
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.Equals(getImport().Beatmaps.First().Ruleset)); AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset));
} }
} }
} }

View File

@ -436,6 +436,8 @@ namespace osu.Game.Tests.Visual.Navigation
{ {
AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded); AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded);
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
TestPlaySongSelect songSelect = null; TestPlaySongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestPlaySongSelect()); PushAndConfirm(() => songSelect = new TestPlaySongSelect());

View File

@ -40,6 +40,43 @@ namespace osu.Game.Tests.Visual.Settings
AddWaitStep("wait for scroll", 5); AddWaitStep("wait for scroll", 5);
} }
[Test]
public void TestBindingTwoNonModifiers()
{
AddStep("press j", () => InputManager.PressKey(Key.J));
scrollToAndStartBinding("Increase volume");
AddStep("press k", () => InputManager.Key(Key.K));
AddStep("release j", () => InputManager.ReleaseKey(Key.J));
checkBinding("Increase volume", "K");
}
[Test]
public void TestBindingSingleKey()
{
scrollToAndStartBinding("Increase volume");
AddStep("press k", () => InputManager.Key(Key.K));
checkBinding("Increase volume", "K");
}
[Test]
public void TestBindingSingleModifier()
{
scrollToAndStartBinding("Increase volume");
AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
checkBinding("Increase volume", "LShift");
}
[Test]
public void TestBindingSingleKeyWithModifier()
{
scrollToAndStartBinding("Increase volume");
AddStep("press shift", () => InputManager.PressKey(Key.ShiftLeft));
AddStep("press k", () => InputManager.Key(Key.K));
AddStep("release shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
checkBinding("Increase volume", "LShift-K");
}
[Test] [Test]
public void TestBindingMouseWheelToNonGameplay() public void TestBindingMouseWheelToNonGameplay()
{ {
@ -169,7 +206,8 @@ namespace osu.Game.Tests.Visual.Settings
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0); AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); AddAssert("binding cleared",
() => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
} }
[Test] [Test]
@ -198,7 +236,8 @@ namespace osu.Game.Tests.Visual.Settings
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0); AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RestoreDefaultValueButton<bool>>().First().Alpha == 0);
AddAssert("binding cleared", () => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0))); AddAssert("binding cleared",
() => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
} }
[Test] [Test]
@ -256,8 +295,8 @@ namespace osu.Game.Tests.Visual.Settings
var firstRow = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == name)); var firstRow = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == name));
var firstButton = firstRow.ChildrenOfType<KeyBindingRow.KeyButton>().First(); var firstButton = firstRow.ChildrenOfType<KeyBindingRow.KeyButton>().First();
return firstButton.Text.Text == keyName; return firstButton.Text.Text.ToString();
}); }, () => Is.EqualTo(keyName));
} }
private void scrollToAndStartBinding(string name) private void scrollToAndStartBinding(string name)

View File

@ -11,6 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
@ -118,6 +119,15 @@ namespace osu.Game.Tests.Visual.SongSelect
} }
} }
[Test]
public void TestDeletion()
{
loadBeatmaps(count: 5, randomDifficulties: true);
AddStep("remove first set", () => carousel.RemoveBeatmapSet(carousel.Items.Select(item => item.Item).OfType<CarouselBeatmapSet>().First().BeatmapSet));
AddUntilStep("4 beatmap sets visible", () => this.ChildrenOfType<DrawableCarouselBeatmapSet>().Count(set => set.Alpha > 0) == 4);
}
[Test] [Test]
public void TestScrollPositionMaintainedOnDelete() public void TestScrollPositionMaintainedOnDelete()
{ {

View File

@ -1055,6 +1055,18 @@ namespace osu.Game.Tests.Visual.SongSelect
AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden); AddUntilStep("mod overlay hidden", () => songSelect!.ModSelect.State.Value == Visibility.Hidden);
} }
[Test]
public void TestBeatmapOptionsDisabled()
{
createSongSelect();
addRulesetImportStep(0);
AddAssert("options enabled", () => songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
AddStep("delete all beatmaps", () => manager.Delete());
AddAssert("options disabled", () => !songSelect.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
}
private void waitForInitialSelection() private void waitForInitialSelection()
{ {
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault); AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);

View File

@ -3,8 +3,10 @@
#nullable disable #nullable disable
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osuTK; using osuTK;
using osuTK.Input; using osuTK.Input;
@ -43,6 +45,12 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.MoveMouseTo(Vector2.Zero); InputManager.MoveMouseTo(Vector2.Zero);
}); });
[Test]
public void TestState()
{
AddRepeatStep("toggle options state", () => this.ChildrenOfType<FooterButton>().Last().Enabled.Toggle(), 20);
}
[Test] [Test]
public void TestFooterRandom() public void TestFooterRandom()
{ {

View File

@ -8,7 +8,7 @@ namespace osu.Game.Configuration
{ {
/// <summary> /// <summary>
/// A settings provider which generally sources from <see cref="OsuConfigManager"/> (global user settings) /// A settings provider which generally sources from <see cref="OsuConfigManager"/> (global user settings)
/// but can allow overriding settings by caching more locally. For instance, in the editor. /// but can allow overriding settings by caching more locally. For instance, in the editor compose screen.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// More settings can be moved into this interface as required. /// More settings can be moved into this interface as required.

View File

@ -1,11 +1,11 @@
// 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.
#nullable disable using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.IO; using osu.Game.IO;
@ -22,22 +22,42 @@ namespace osu.Game.Database
{ {
// make sure the directory exists // make sure the directory exists
if (!storage.ExistsDirectory(string.Empty)) if (!storage.ExistsDirectory(string.Empty))
yield break; return Array.Empty<string>();
foreach (string directory in storage.GetDirectories(string.Empty)) List<string> paths = new List<string>();
try
{ {
var directoryStorage = storage.GetStorageForDirectory(directory); foreach (string directory in storage.GetDirectories(string.Empty))
if (!directoryStorage.GetFiles(string.Empty).ExcludeSystemFileNames().Any())
{ {
// if a directory doesn't contain files, attempt looking for beatmaps inside of that directory. var directoryStorage = storage.GetStorageForDirectory(directory);
// this is a special behaviour in stable for beatmaps only, see https://github.com/ppy/osu/issues/18615.
foreach (string subDirectory in GetStableImportPaths(directoryStorage)) try
yield return subDirectory; {
if (!directoryStorage.GetFiles(string.Empty).ExcludeSystemFileNames().Any())
{
// if a directory doesn't contain files, attempt looking for beatmaps inside of that directory.
// this is a special behaviour in stable for beatmaps only, see https://github.com/ppy/osu/issues/18615.
foreach (string subDirectory in GetStableImportPaths(directoryStorage))
paths.Add(subDirectory);
}
else
paths.Add(storage.GetFullPath(directory));
}
catch (Exception e)
{
// Catch any errors when enumerating files
Logger.Log($"Error when enumerating files in {directoryStorage.GetFullPath(string.Empty)}: {e}");
}
} }
else
yield return storage.GetFullPath(directory);
} }
catch (Exception e)
{
// Catch any errors when enumerating directories
Logger.Log($"Error when enumerating directories in {storage.GetFullPath(string.Empty)}: {e}");
}
return paths;
} }
public LegacyBeatmapImporter(IModelImporter<BeatmapSetInfo> importer) public LegacyBeatmapImporter(IModelImporter<BeatmapSetInfo> importer)

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -69,6 +70,8 @@ namespace osu.Game.Graphics.UserInterface
protected Box Background; protected Box Background;
protected SpriteText SpriteText; protected SpriteText SpriteText;
private readonly Box flashLayer;
public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button) public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button)
{ {
Height = 40; Height = 40;
@ -99,6 +102,14 @@ namespace osu.Game.Graphics.UserInterface
Depth = float.MinValue Depth = float.MinValue
}, },
SpriteText = CreateText(), SpriteText = CreateText(),
flashLayer = new Box
{
RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive,
Depth = float.MinValue,
Colour = Color4.White.Opacity(0.5f),
Alpha = 0,
},
} }
}); });
@ -125,7 +136,7 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
if (Enabled.Value) if (Enabled.Value)
Background.FlashColour(Color4.White, 800, Easing.OutQuint); flashLayer.FadeOutFromOne(800, Easing.OutQuint);
return base.OnClick(e); return base.OnClick(e);
} }

View File

@ -11,7 +11,7 @@ using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public abstract class ScoreCounter : RollingCounter<double> public abstract class ScoreCounter : RollingCounter<long>
{ {
protected override double RollingDuration => 1000; protected override double RollingDuration => 1000;
protected override Easing RollingEasing => Easing.Out; protected override Easing RollingEasing => Easing.Out;
@ -36,10 +36,10 @@ namespace osu.Game.Graphics.UserInterface
UpdateDisplay(); UpdateDisplay();
} }
protected override double GetProportionalDuration(double currentValue, double newValue) => protected override double GetProportionalDuration(long currentValue, long newValue) =>
currentValue > newValue ? currentValue - newValue : newValue - currentValue; currentValue > newValue ? currentValue - newValue : newValue - currentValue;
protected override LocalisableString FormatCount(double count) => ((long)count).ToLocalisableString(formatString); protected override LocalisableString FormatCount(long count) => count.ToLocalisableString(formatString);
protected override OsuSpriteText CreateSpriteText() protected override OsuSpriteText CreateSpriteText()
=> base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true)); => base.CreateSpriteText().With(s => s.Font = s.Font.With(fixedWidth: true));

View File

@ -419,7 +419,7 @@ namespace osu.Game.Online.API
failureCount++; failureCount++;
log.Add($@"API failure count is now {failureCount}"); log.Add($@"API failure count is now {failureCount}");
if (failureCount >= 3 && State.Value == APIState.Online) if (failureCount >= 3)
{ {
state.Value = APIState.Failing; state.Value = APIState.Failing;
flushQueue(); flushQueue();

View File

@ -16,6 +16,8 @@ namespace osu.Game.Online.API.Requests
{ {
public class GetScoresRequest : APIRequest<APIScoresCollection> public class GetScoresRequest : APIRequest<APIScoresCollection>
{ {
public const int MAX_SCORES_PER_REQUEST = 50;
private readonly IBeatmapInfo beatmapInfo; private readonly IBeatmapInfo beatmapInfo;
private readonly BeatmapLeaderboardScope scope; private readonly BeatmapLeaderboardScope scope;
private readonly IRulesetInfo ruleset; private readonly IRulesetInfo ruleset;

View File

@ -31,7 +31,7 @@ namespace osu.Game.Online.API.Requests.Responses
public bool Passed { get; set; } public bool Passed { get; set; }
[JsonProperty("total_score")] [JsonProperty("total_score")]
public int TotalScore { get; set; } public long TotalScore { get; set; }
[JsonProperty("accuracy")] [JsonProperty("accuracy")]
public double Accuracy { get; set; } public double Accuracy { get; set; }
@ -213,7 +213,7 @@ namespace osu.Game.Online.API.Requests.Responses
public static SoloScoreInfo ForSubmission(ScoreInfo score) => new SoloScoreInfo public static SoloScoreInfo ForSubmission(ScoreInfo score) => new SoloScoreInfo
{ {
Rank = score.Rank, Rank = score.Rank,
TotalScore = (int)score.TotalScore, TotalScore = score.TotalScore,
Accuracy = score.Accuracy, Accuracy = score.Accuracy,
PP = score.PP, PP = score.PP,
MaxCombo = score.MaxCombo, MaxCombo = score.MaxCombo,

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Newtonsoft.Json;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Development; using osu.Framework.Development;
@ -727,13 +728,20 @@ namespace osu.Game.Online.Multiplayer
if (Room == null) if (Room == null)
return; return;
Debug.Assert(APIRoom != null); try
{
Debug.Assert(APIRoom != null);
Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item; Room.Playlist[Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID))] = item;
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
APIRoom.Playlist.RemoveAt(existingIndex); APIRoom.Playlist.RemoveAt(existingIndex);
APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item)); APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
}
catch (Exception ex)
{
throw new AggregateException($"Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex);
}
ItemChanged?.Invoke(item); ItemChanged?.Invoke(item);
RoomUpdated?.Invoke(); RoomUpdated?.Invoke();

View File

@ -145,7 +145,9 @@ namespace osu.Game.Online
private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken) private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken)
{ {
isConnected.Value = false; bool hasBeenCancelled = cancellationToken.IsCancellationRequested;
await disconnect(true);
if (ex != null) if (ex != null)
await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false); await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false);
@ -153,7 +155,7 @@ namespace osu.Game.Online
Logger.Log($"{ClientName} disconnected", LoggingTarget.Network); Logger.Log($"{ClientName} disconnected", LoggingTarget.Network);
// make sure a disconnect wasn't triggered (and this is still the active connection). // make sure a disconnect wasn't triggered (and this is still the active connection).
if (!cancellationToken.IsCancellationRequested) if (!hasBeenCancelled)
await Task.Run(connect, default).ConfigureAwait(false); await Task.Run(connect, default).ConfigureAwait(false);
} }
@ -174,7 +176,9 @@ namespace osu.Game.Online
} }
finally finally
{ {
isConnected.Value = false;
CurrentConnection = null; CurrentConnection = null;
if (takeLock) if (takeLock)
connectionLock.Release(); connectionLock.Release();
} }

View File

@ -17,7 +17,7 @@ namespace osu.Game.Online.Rooms
/// The total scores in the playlist item. /// The total scores in the playlist item.
/// </summary> /// </summary>
[JsonProperty("total")] [JsonProperty("total")]
public int? TotalScores { get; set; } public long? TotalScores { get; set; }
/// <summary> /// <summary>
/// The user's score, if any. /// The user's score, if any.

View File

@ -26,7 +26,7 @@ namespace osu.Game.Online.Spectator
/// <summary> /// <summary>
/// The current total score. /// The current total score.
/// </summary> /// </summary>
public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 }; public readonly BindableLong TotalScore = new BindableLong { MinValue = 0 };
/// <summary> /// <summary>
/// The current accuracy. /// The current accuracy.

View File

@ -41,11 +41,8 @@ namespace osu.Game.Overlays
private IBindable<APIUser> apiUser; private IBindable<APIUser> apiUser;
private Drawable currentContent;
private Container panelTarget; private Container panelTarget;
private FillFlowContainer<BeatmapCard> foundContent; private FillFlowContainer<BeatmapCard> foundContent;
private NotFoundDrawable notFoundContent;
private SupporterRequiredDrawable supporterRequiredContent;
private BeatmapListingFilterControl filterControl; private BeatmapListingFilterControl filterControl;
public BeatmapListingOverlay() public BeatmapListingOverlay()
@ -86,11 +83,6 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Masking = true, Masking = true,
Padding = new MarginPadding { Horizontal = 20 }, Padding = new MarginPadding { Horizontal = 20 },
Children = new Drawable[]
{
notFoundContent = new NotFoundDrawable(),
supporterRequiredContent = new SupporterRequiredDrawable(),
}
} }
}, },
}, },
@ -107,7 +99,7 @@ namespace osu.Game.Overlays
apiUser.BindValueChanged(_ => Schedule(() => apiUser.BindValueChanged(_ => Schedule(() =>
{ {
if (api.IsLoggedIn) if (api.IsLoggedIn)
addContentToResultsArea(Drawable.Empty()); replaceResultsAreaContent(Drawable.Empty());
})); }));
} }
@ -155,8 +147,8 @@ namespace osu.Game.Overlays
if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters) if (searchResult.Type == BeatmapListingFilterControl.SearchResultType.SupporterOnlyFilters)
{ {
supporterRequiredContent.UpdateText(searchResult.SupporterOnlyFiltersUsed); var supporterOnly = new SupporterRequiredDrawable(searchResult.SupporterOnlyFiltersUsed);
addContentToResultsArea(supporterRequiredContent); replaceResultsAreaContent(supporterOnly);
return; return;
} }
@ -167,13 +159,13 @@ namespace osu.Game.Overlays
//No matches case //No matches case
if (!newCards.Any()) if (!newCards.Any())
{ {
addContentToResultsArea(notFoundContent); replaceResultsAreaContent(new NotFoundDrawable());
return; return;
} }
var content = createCardContainerFor(newCards); var content = createCardContainerFor(newCards);
panelLoadTask = LoadComponentAsync(foundContent = content, addContentToResultsArea, (cancellationToken = new CancellationTokenSource()).Token); panelLoadTask = LoadComponentAsync(foundContent = content, replaceResultsAreaContent, (cancellationToken = new CancellationTokenSource()).Token);
} }
else else
{ {
@ -221,36 +213,16 @@ namespace osu.Game.Overlays
return content; return content;
} }
private void addContentToResultsArea(Drawable content) private void replaceResultsAreaContent(Drawable content)
{ {
Loading.Hide(); Loading.Hide();
lastFetchDisplayedTime = Time.Current; lastFetchDisplayedTime = Time.Current;
if (content == currentContent) panelTarget.Child = content;
return;
var lastContent = currentContent;
if (lastContent != null)
{
lastContent.FadeOut();
if (!isPlaceholderContent(lastContent))
lastContent.Expire();
}
if (!content.IsAlive)
panelTarget.Add(content);
content.FadeInFromZero(); content.FadeInFromZero();
currentContent = content;
} }
/// <summary>
/// Whether <paramref name="drawable"/> is a static placeholder reused multiple times by this overlay.
/// </summary>
private bool isPlaceholderContent(Drawable drawable)
=> drawable == notFoundContent || drawable == supporterRequiredContent;
private void onCardSizeChanged() private void onCardSizeChanged()
{ {
if (foundContent?.IsAlive != true || !foundContent.Any()) if (foundContent?.IsAlive != true || !foundContent.Any())
@ -287,7 +259,7 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
AddInternal(new FillFlowContainer AddInternal(new FillFlowContainer
{ {
@ -324,15 +296,19 @@ namespace osu.Game.Overlays
{ {
private LinkFlowContainer supporterRequiredText; private LinkFlowContainer supporterRequiredText;
public SupporterRequiredDrawable() private readonly List<LocalisableString> filtersUsed;
public SupporterRequiredDrawable(List<LocalisableString> filtersUsed)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 225; Height = 225;
Alpha = 0; Alpha = 0;
this.filtersUsed = filtersUsed;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
AddInternal(new FillFlowContainer AddInternal(new FillFlowContainer
{ {
@ -360,14 +336,9 @@ namespace osu.Game.Overlays
}, },
} }
}); });
}
public void UpdateText(List<LocalisableString> filters)
{
supporterRequiredText.Clear();
supporterRequiredText.AddText( supporterRequiredText.AddText(
BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filters), "").ToString(), BeatmapsStrings.ListingSearchSupporterFilterQuoteDefault(string.Join(" and ", filtersUsed), "").ToString(),
t => t =>
{ {
t.Font = OsuFont.GetFont(size: 16); t.Font = OsuFont.GetFont(size: 16);

View File

@ -119,22 +119,17 @@ namespace osu.Game.Overlays.Dashboard.Home.News
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(GameHost host) private void load(GameHost host)
{ {
NewsPostBackground bg; Child = new DelayedLoadUnloadWrapper(() => new NewsPostBackground(post.FirstImage)
Child = new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Alpha = 0
}) })
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}; };
bg.OnLoadComplete += d => d.FadeIn(250, Easing.In);
TooltipText = "view in browser"; TooltipText = "view in browser";
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);

View File

@ -18,6 +18,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Localisation; using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osuTK; using osuTK;
namespace osu.Game.Overlays.FirstRunSetup namespace osu.Game.Overlays.FirstRunSetup
@ -26,7 +27,7 @@ namespace osu.Game.Overlays.FirstRunSetup
public class ScreenWelcome : FirstRunSetupScreen public class ScreenWelcome : FirstRunSetupScreen
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load(FrameworkConfigManager frameworkConfig)
{ {
Content.Children = new Drawable[] Content.Children = new Drawable[]
{ {
@ -52,6 +53,11 @@ namespace osu.Game.Overlays.FirstRunSetup
}, },
} }
}, },
new SettingsCheckbox
{
LabelText = GeneralSettingsStrings.PreferOriginalMetadataLanguage,
Current = frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowUnicode)
},
new LanguageSelectionFlow new LanguageSelectionFlow
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -49,7 +49,6 @@ namespace osu.Game.Overlays.News
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug); Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
} }
NewsPostBackground bg;
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
background = new Box background = new Box
@ -71,14 +70,14 @@ namespace osu.Game.Overlays.News
CornerRadius = 6, CornerRadius = 6,
Children = new Drawable[] Children = new Drawable[]
{ {
new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage) new DelayedLoadUnloadWrapper(() => new NewsPostBackground(post.FirstImage)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Alpha = 0 Alpha = 0
}) }, timeBeforeUnload: 5000)
{ {
RelativeSizeAxes = Axes.Both RelativeSizeAxes = Axes.Both
}, },
@ -116,8 +115,6 @@ namespace osu.Game.Overlays.News
IdleColour = colourProvider.Background4; IdleColour = colourProvider.Background4;
HoverColour = colourProvider.Background3; HoverColour = colourProvider.Background3;
bg.OnLoadComplete += d => d.FadeIn(250, Easing.In);
main.AddParagraph(post.Title, t => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold)); main.AddParagraph(post.Title, t => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold));
main.AddParagraph(post.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font main.AddParagraph(post.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font
main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12)); main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12));

View File

@ -4,6 +4,7 @@
#nullable disable #nullable disable
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
@ -25,6 +26,12 @@ namespace osu.Game.Overlays.News
Texture = store.Get(createUrl(sourceUrl)); Texture = store.Get(createUrl(sourceUrl));
} }
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(500, Easing.OutQuint);
}
private string createUrl(string source) private string createUrl(string source)
{ {
if (string.IsNullOrEmpty(source)) if (string.IsNullOrEmpty(source))

View File

@ -100,7 +100,7 @@ namespace osu.Game.Overlays
}, },
Children = new[] Children = new[]
{ {
background = new Background(), background = Empty(),
title = new OsuSpriteText title = new OsuSpriteText
{ {
Origin = Anchor.BottomCentre, Origin = Anchor.BottomCentre,
@ -413,7 +413,7 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Overlays
Height = 80; Height = 80;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Masking = true; Masking = true;
InternalChild = new Background(textureName); InternalChild = new DelayedLoadWrapper(() => new Background(textureName));
} }
private class Background : Sprite private class Background : Sprite
@ -36,10 +36,16 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
Texture = textures.Get(textureName); Texture = textures.Get(textureName);
} }
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(500, Easing.OutQuint);
}
} }
} }
} }

View File

@ -3,6 +3,8 @@
#nullable disable #nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Localisation; using osu.Game.Localisation;
@ -13,6 +15,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings; protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings;
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "keybindings" });
public BindingSettings(KeyBindingPanel keyConfig) public BindingSettings(KeyBindingPanel keyConfig)
{ {
Children = new Drawable[] Children = new Drawable[]

View File

@ -33,6 +33,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
public class KeyBindingRow : Container, IFilterable public class KeyBindingRow : Container, IFilterable
{ {
/// <summary>
/// Invoked when the binding of this row is updated with a change being written.
/// </summary>
public Action<KeyBindingRow> BindingUpdated { get; set; }
private readonly object action; private readonly object action;
private readonly IEnumerable<RealmKeyBinding> bindings; private readonly IEnumerable<RealmKeyBinding> bindings;
@ -153,7 +158,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Spacing = new Vector2(5), Spacing = new Vector2(5),
Children = new Drawable[] Children = new Drawable[]
{ {
new CancelButton { Action = finalise }, new CancelButton { Action = () => finalise(false) },
new ClearButton { Action = clear }, new ClearButton { Action = clear },
}, },
}, },
@ -226,7 +231,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
} }
} }
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromMouseButton(e.Button));
return true; return true;
} }
@ -240,7 +245,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
} }
if (bindTarget.IsHovered) if (bindTarget.IsHovered)
finalise(); finalise(false);
// prevent updating bind target before clear button's action // prevent updating bind target before clear button's action
else if (!cancelAndClearButtons.Any(b => b.IsHovered)) else if (!cancelAndClearButtons.Any(b => b.IsHovered))
updateBindTarget(); updateBindTarget();
@ -252,7 +257,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
if (bindTarget.IsHovered) if (bindTarget.IsHovered)
{ {
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta)); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState, e.ScrollDelta), KeyCombination.FromScrollDelta(e.ScrollDelta).First());
finalise(); finalise();
return true; return true;
} }
@ -263,10 +268,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (!HasFocus) if (!HasFocus || e.Repeat)
return false; return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromKey(e.Key));
if (!isModifier(e.Key)) finalise(); if (!isModifier(e.Key)) finalise();
return true; return true;
@ -288,7 +293,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus) if (!HasFocus)
return false; return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromJoystickButton(e.Button));
finalise(); finalise();
return true; return true;
@ -310,7 +315,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus) if (!HasFocus)
return false; return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromMidiKey(e.Key));
finalise(); finalise();
return true; return true;
@ -332,7 +337,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus) if (!HasFocus)
return false; return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromTabletAuxiliaryButton(e.Button));
finalise(); finalise();
return true; return true;
@ -354,7 +359,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus) if (!HasFocus)
return false; return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState)); bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromTabletPenButton(e.Button));
finalise(); finalise();
return true; return true;
@ -377,10 +382,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
return; return;
bindTarget.UpdateKeyCombination(InputKey.None); bindTarget.UpdateKeyCombination(InputKey.None);
finalise(); finalise(false);
} }
private void finalise() private void finalise(bool hasChanged = true)
{ {
if (bindTarget != null) if (bindTarget != null)
{ {
@ -393,6 +398,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
// schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.) // schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.)
bindTarget = null; bindTarget = null;
if (hasChanged)
BindingUpdated?.Invoke(this);
}); });
} }
@ -417,7 +424,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
protected override void OnFocusLost(FocusLostEvent e) protected override void OnFocusLost(FocusLostEvent e)
{ {
finalise(); finalise(false);
base.OnFocusLost(e); base.OnFocusLost(e);
} }
@ -563,6 +570,14 @@ namespace osu.Game.Overlays.Settings.Sections.Input
} }
} }
/// <summary>
/// Update from a key combination, only allowing a single non-modifier key to be specified.
/// </summary>
/// <param name="fullState">A <see cref="KeyCombination"/> generated from the full input state.</param>
/// <param name="triggerKey">The key which triggered this update, and should be used as the binding.</param>
public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) =>
UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey)));
public void UpdateKeyCombination(KeyCombination newCombination) public void UpdateKeyCombination(KeyCombination newCombination)
{ {
if (KeyBinding.RulesetName != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination)) if (KeyBinding.RulesetName != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination))

View File

@ -19,6 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
public abstract class KeyBindingsSubsection : SettingsSubsection public abstract class KeyBindingsSubsection : SettingsSubsection
{ {
/// <summary>
/// After a successful binding, automatically select the next binding row to make quickly
/// binding a large set of keys easier on the user.
/// </summary>
protected virtual bool AutoAdvanceTarget => false;
protected IEnumerable<Framework.Input.Bindings.KeyBinding> Defaults; protected IEnumerable<Framework.Input.Bindings.KeyBinding> Defaults;
public RulesetInfo Ruleset { get; protected set; } public RulesetInfo Ruleset { get; protected set; }
@ -49,7 +55,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.ActionInt.Equals(intKey)).ToList()) Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.ActionInt.Equals(intKey)).ToList())
{ {
AllowMainMouseButtons = Ruleset != null, AllowMainMouseButtons = Ruleset != null,
Defaults = defaultGroup.Select(d => d.KeyCombination) Defaults = defaultGroup.Select(d => d.KeyCombination),
BindingUpdated = onBindingUpdated
}); });
} }
@ -58,6 +65,16 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Action = () => Children.OfType<KeyBindingRow>().ForEach(k => k.RestoreDefaults()) Action = () => Children.OfType<KeyBindingRow>().ForEach(k => k.RestoreDefaults())
}); });
} }
private void onBindingUpdated(KeyBindingRow sender)
{
if (AutoAdvanceTarget)
{
var next = Children.SkipWhile(c => c != sender).Skip(1).FirstOrDefault();
if (next != null)
GetContainingInputManager().ChangeFocus(next);
}
}
} }
public class ResetButton : DangerousSettingsButton public class ResetButton : DangerousSettingsButton

View File

@ -125,11 +125,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint); usableAreaContainer.ResizeTo(val.NewValue, 100, Easing.OutQuint);
int x = (int)val.NewValue.X; int x = (int)Math.Round(val.NewValue.X);
int y = (int)val.NewValue.Y; int y = (int)Math.Round(val.NewValue.Y);
int commonDivider = greatestCommonDivider(x, y); int commonDivider = greatestCommonDivider(x, y);
usableAreaText.Text = $"{(float)x / commonDivider}:{(float)y / commonDivider}"; usableAreaText.Text = $"{x / commonDivider}:{y / commonDivider}";
checkBounds(); checkBounds();
}, true); }, true);

View File

@ -3,7 +3,6 @@
#nullable disable #nullable disable
using System;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -111,9 +110,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
{ {
t.NewLine(); t.NewLine();
var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(
? @"https://opentabletdriver.net/Wiki/FAQ/Windows" RuntimeInfo.OS == RuntimeInfo.Platform.Windows
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value); ? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value);
t.AddLinks(formattedSource.Text, formattedSource.Links); t.AddLinks(formattedSource.Text, formattedSource.Links);
} }
}), }),
@ -274,6 +274,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
sizeY.Default = sizeY.MaxValue = tab.Size.Y; sizeY.Default = sizeY.MaxValue = tab.Size.Y;
areaSize.Default = new Vector2(sizeX.Default, sizeY.Default); areaSize.Default = new Vector2(sizeX.Default, sizeY.Default);
areaOffset.Default = new Vector2(offsetX.Default, offsetY.Default);
}), true); }), true);
} }
@ -325,7 +326,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
aspectLock.Value = false; aspectLock.Value = false;
int proposedHeight = getHeight(sizeX.Value, aspectRatio); float proposedHeight = getHeight(sizeX.Value, aspectRatio);
if (proposedHeight < sizeY.MaxValue) if (proposedHeight < sizeY.MaxValue)
sizeY.Value = proposedHeight; sizeY.Value = proposedHeight;
@ -342,8 +343,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
private float currentAspectRatio => sizeX.Value / sizeY.Value; private float currentAspectRatio => sizeX.Value / sizeY.Value;
private static int getHeight(float width, float aspectRatio) => (int)Math.Round(width / aspectRatio); private static float getHeight(float width, float aspectRatio) => width / aspectRatio;
private static int getWidth(float height, float aspectRatio) => (int)Math.Round(height * aspectRatio); private static float getWidth(float height, float aspectRatio) => height * aspectRatio;
} }
} }

View File

@ -8,6 +8,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{ {
public class VariantBindingsSubsection : KeyBindingsSubsection public class VariantBindingsSubsection : KeyBindingsSubsection
{ {
protected override bool AutoAdvanceTarget => true;
protected override LocalisableString Header { get; } protected override LocalisableString Header { get; }
public VariantBindingsSubsection(RulesetInfo ruleset, int variant) public VariantBindingsSubsection(RulesetInfo ruleset, int variant)

View File

@ -3,8 +3,6 @@
#nullable disable #nullable disable
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
@ -22,8 +20,6 @@ namespace osu.Game.Overlays.Settings.Sections
public override LocalisableString Header => InputSettingsStrings.InputSectionHeader; public override LocalisableString Header => InputSettingsStrings.InputSectionHeader;
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "keybindings" });
public override Drawable CreateIcon() => new SpriteIcon public override Drawable CreateIcon() => new SpriteIcon
{ {
Icon = FontAwesome.Solid.Keyboard Icon = FontAwesome.Solid.Keyboard

View File

@ -89,6 +89,10 @@ namespace osu.Game
// check if we are already at a valid target screen. // check if we are already at a valid target screen.
if (validScreens.Any(t => t.IsAssignableFrom(type))) if (validScreens.Any(t => t.IsAssignableFrom(type)))
{ {
if (!((Drawable)current).IsLoaded)
// wait until screen is loaded before invoking action.
return true;
finalAction(current); finalAction(current);
Cancel(); Cancel();
return true; return true;

View File

@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Difficulty
// calculate total score // calculate total score
ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor scoreProcessor = ruleset.CreateScoreProcessor();
scoreProcessor.Mods.Value = perfectPlay.Mods; scoreProcessor.Mods.Value = perfectPlay.Mods;
perfectPlay.TotalScore = (long)scoreProcessor.ComputeScore(ScoringMode.Standardised, perfectPlay); perfectPlay.TotalScore = scoreProcessor.ComputeScore(ScoringMode.Standardised, perfectPlay);
// compute rank achieved // compute rank achieved
// default to SS, then adjust the rank with mods // default to SS, then adjust the rank with mods

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Scoring
/// <summary> /// <summary>
/// The current total score. /// The current total score.
/// </summary> /// </summary>
public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 }; public readonly BindableLong TotalScore = new BindableLong { MinValue = 0 };
/// <summary> /// <summary>
/// The current accuracy. /// The current accuracy.
@ -267,7 +267,7 @@ namespace osu.Game.Rulesets.Scoring
private void updateScore() private void updateScore()
{ {
Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1;
TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues); TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues);
} }
@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Scoring
/// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param> /// <param name="scoreInfo">The <see cref="ScoreInfo"/> to compute the total score of.</param>
/// <returns>The total score in the given <see cref="ScoringMode"/>.</returns> /// <returns>The total score in the given <see cref="ScoringMode"/>.</returns>
[Pure] [Pure]
public double ComputeScore(ScoringMode mode, ScoreInfo scoreInfo) public long ComputeScore(ScoringMode mode, ScoreInfo scoreInfo)
{ {
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\".");
@ -316,9 +316,9 @@ namespace osu.Game.Rulesets.Scoring
/// <param name="maximum">The maximum scoring values.</param> /// <param name="maximum">The maximum scoring values.</param>
/// <returns>The total score computed from the given scoring values.</returns> /// <returns>The total score computed from the given scoring values.</returns>
[Pure] [Pure]
public double ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) public long ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum)
{ {
double accuracyRatio = maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; double accuracyRatio = maximum.BaseScore > 0 ? (double)current.BaseScore / maximum.BaseScore : 1;
double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1; double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1;
return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.CountBasicHitObjects); return ComputeScore(mode, accuracyRatio, comboRatio, current.BonusScore, maximum.CountBasicHitObjects);
} }
@ -333,21 +333,23 @@ namespace osu.Game.Rulesets.Scoring
/// <param name="totalBasicHitObjects">The total number of basic (non-tick and non-bonus) hitobjects in the beatmap.</param> /// <param name="totalBasicHitObjects">The total number of basic (non-tick and non-bonus) hitobjects in the beatmap.</param>
/// <returns>The total score computed from the given scoring component ratios.</returns> /// <returns>The total score computed from the given scoring component ratios.</returns>
[Pure] [Pure]
public double ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, double bonusScore, int totalBasicHitObjects) public long ComputeScore(ScoringMode mode, double accuracyRatio, double comboRatio, long bonusScore, int totalBasicHitObjects)
{ {
double accuracyScore = accuracyPortion * accuracyRatio;
double comboScore = comboPortion * comboRatio;
double rawScore = (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier;
switch (mode) switch (mode)
{ {
default: default:
case ScoringMode.Standardised: case ScoringMode.Standardised:
double accuracyScore = accuracyPortion * accuracyRatio; return (long)Math.Round(rawScore);
double comboScore = comboPortion * comboRatio;
return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier;
case ScoringMode.Classic: case ScoringMode.Classic:
// This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring. // This gives a similar feeling to osu!stable scoring (ScoreV1) while keeping classic scoring as only a constant multiple of standardised scoring.
// The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes. // The invariant is important to ensure that scores don't get re-ordered on leaderboards between the two scoring modes.
double scaledStandardised = ComputeScore(ScoringMode.Standardised, accuracyRatio, comboRatio, bonusScore, totalBasicHitObjects) / max_score; double scaledRawScore = rawScore / max_score;
return Math.Pow(scaledStandardised * Math.Max(1, totalBasicHitObjects), 2) * ClassicScoreMultiplier; return (long)Math.Round(Math.Pow(scaledRawScore * Math.Max(1, totalBasicHitObjects), 2) * ClassicScoreMultiplier);
} }
} }
@ -417,7 +419,7 @@ namespace osu.Game.Rulesets.Scoring
score.MaximumStatistics[result] = maximumResultCounts.GetValueOrDefault(result); score.MaximumStatistics[result] = maximumResultCounts.GetValueOrDefault(result);
// Populate total score after everything else. // Populate total score after everything else.
score.TotalScore = (long)Math.Round(ComputeScore(ScoringMode.Standardised, score)); score.TotalScore = ComputeScore(ScoringMode.Standardised, score);
} }
/// <summary> /// <summary>

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI.Scrolling
/// A type of <see cref="DrawableRuleset{TObject}"/> that supports a <see cref="ScrollingPlayfield"/>. /// A type of <see cref="DrawableRuleset{TObject}"/> that supports a <see cref="ScrollingPlayfield"/>.
/// <see cref="HitObject"/>s inside this <see cref="DrawableRuleset{TObject}"/> will scroll within the playfield. /// <see cref="HitObject"/>s inside this <see cref="DrawableRuleset{TObject}"/> will scroll within the playfield.
/// </summary> /// </summary>
public abstract class DrawableScrollingRuleset<TObject> : DrawableRuleset<TObject>, IKeyBindingHandler<GlobalAction> public abstract class DrawableScrollingRuleset<TObject> : DrawableRuleset<TObject>, IDrawableScrollingRuleset, IKeyBindingHandler<GlobalAction>
where TObject : HitObject where TObject : HitObject
{ {
/// <summary> /// <summary>
@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential; protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential;
ScrollVisualisationMethod IDrawableScrollingRuleset.VisualisationMethod => VisualisationMethod;
/// <summary> /// <summary>
/// Whether the player can change <see cref="TimeRange"/>. /// Whether the player can change <see cref="TimeRange"/>.
/// </summary> /// </summary>

View File

@ -0,0 +1,15 @@
// 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.Game.Configuration;
namespace osu.Game.Rulesets.UI.Scrolling
{
/// <summary>
/// An interface for scrolling-based <see cref="DrawableRuleset{TObject}"/>s.
/// </summary>
public interface IDrawableScrollingRuleset
{
ScrollVisualisationMethod VisualisationMethod { get; }
}
}

View File

@ -99,7 +99,7 @@ namespace osu.Game.Scoring
var scoreProcessor = ruleset.CreateScoreProcessor(); var scoreProcessor = ruleset.CreateScoreProcessor();
scoreProcessor.Mods.Value = score.Mods; scoreProcessor.Mods.Value = score.Mods;
return (long)Math.Round(scoreProcessor.ComputeScore(mode, score)); return scoreProcessor.ComputeScore(mode, score);
} }
/// <summary> /// <summary>

View File

@ -20,13 +20,13 @@ namespace osu.Game.Scoring
/// The sum of all "basic" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>. /// The sum of all "basic" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBasic"/> and <see cref="Judgement.ToNumericResult"/>.
/// </summary> /// </summary>
[Key(0)] [Key(0)]
public double BaseScore; public long BaseScore;
/// <summary> /// <summary>
/// The sum of all "bonus" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBonus"/> and <see cref="Judgement.ToNumericResult"/>. /// The sum of all "bonus" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBonus"/> and <see cref="Judgement.ToNumericResult"/>.
/// </summary> /// </summary>
[Key(1)] [Key(1)]
public double BonusScore; public long BonusScore;
/// <summary> /// <summary>
/// The highest achieved combo. /// The highest achieved combo.

View File

@ -436,8 +436,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{ {
base.OnDragEnd(e); base.OnDragEnd(e);
OnDragHandled?.Invoke(null); dragOperation?.Cancel();
dragOperation = null;
changeHandler?.EndChange(); changeHandler?.EndChange();
OnDragHandled?.Invoke(null);
} }
} }

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO.Serialization; using osu.Game.IO.Serialization;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -19,7 +20,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Screens.Edit.Compose namespace osu.Game.Screens.Edit.Compose
{ {
public class ComposeScreen : EditorScreenWithTimeline public class ComposeScreen : EditorScreenWithTimeline, IGameplaySettings
{ {
[Resolved] [Resolved]
private GameHost host { get; set; } private GameHost host { get; set; }
@ -27,6 +28,9 @@ namespace osu.Game.Screens.Edit.Compose
[Resolved] [Resolved]
private EditorClock clock { get; set; } private EditorClock clock { get; set; }
[Resolved]
private IGameplaySettings globalGameplaySettings { get; set; }
private Bindable<string> clipboard { get; set; } private Bindable<string> clipboard { get; set; }
private HitObjectComposer composer; private HitObjectComposer composer;
@ -157,5 +161,12 @@ namespace osu.Game.Screens.Edit.Compose
} }
#endregion #endregion
// Combo colour normalisation should not be applied in the editor.
// Note this doesn't affect editor test mode.
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => new Bindable<float>();
// Arguable.
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => globalGameplaySettings.PositionalHitsoundsLevel;
} }
} }

View File

@ -58,8 +58,7 @@ namespace osu.Game.Screens.Edit
{ {
[Cached(typeof(IBeatSnapProvider))] [Cached(typeof(IBeatSnapProvider))]
[Cached] [Cached]
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider, public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider
IGameplaySettings
{ {
public override float BackgroundParallaxAmount => 0.1f; public override float BackgroundParallaxAmount => 0.1f;
@ -99,9 +98,6 @@ namespace osu.Game.Screens.Edit
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private INotificationOverlay notifications { get; set; } private INotificationOverlay notifications { get; set; }
[Resolved]
private IGameplaySettings globalGameplaySettings { get; set; }
public readonly Bindable<EditorScreenMode> Mode = new Bindable<EditorScreenMode>(); public readonly Bindable<EditorScreenMode> Mode = new Bindable<EditorScreenMode>();
public IBindable<bool> SamplePlaybackDisabled => samplePlaybackDisabled; public IBindable<bool> SamplePlaybackDisabled => samplePlaybackDisabled;
@ -1045,11 +1041,5 @@ namespace osu.Game.Screens.Edit
{ {
} }
} }
// Combo colour normalisation should not be applied in the editor.
IBindable<float> IGameplaySettings.ComboColourNormalisationAmount => new Bindable<float>();
// Arguable.
IBindable<float> IGameplaySettings.PositionalHitsoundsLevel => globalGameplaySettings.PositionalHitsoundsLevel;
} }
} }

View File

@ -270,7 +270,7 @@ namespace osu.Game.Screens.Edit
{ {
IsSeeking &= Transforms.Any(); IsSeeking &= Transforms.Any();
if (track.Value?.IsRunning != true) if (!IsRunning)
{ {
// seeking in the editor can happen while the track isn't running. // seeking in the editor can happen while the track isn't running.
// in this case we always want to expose ourselves as seeking (to avoid sample playback). // in this case we always want to expose ourselves as seeking (to avoid sample playback).

View File

@ -1,8 +1,7 @@
// 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.
#nullable disable using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -17,7 +16,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
private readonly EditorState editorState; private readonly EditorState editorState;
[Resolved] [Resolved]
private MusicController musicController { get; set; } private MusicController musicController { get; set; } = null!;
public EditorPlayer(Editor editor) public EditorPlayer(Editor editor)
: base(new PlayerConfiguration { ShowResults = false }) : base(new PlayerConfiguration { ShowResults = false })
@ -29,7 +28,12 @@ namespace osu.Game.Screens.Edit.GameplayTest
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart)
{ {
var masterGameplayClockContainer = new MasterGameplayClockContainer(beatmap, gameplayStart); var masterGameplayClockContainer = new MasterGameplayClockContainer(beatmap, gameplayStart);
masterGameplayClockContainer.Reset(editorState.Time);
// Only reset the time to the current point if the editor is later than the normal start time (and the first object).
// This allows more sane test playing from the start of the beatmap (ie. correctly adding lead-in time).
if (editorState.Time > gameplayStart && editorState.Time > DrawableRuleset.Objects.FirstOrDefault()?.StartTime)
masterGameplayClockContainer.Reset(editorState.Time);
return masterGameplayClockContainer; return masterGameplayClockContainer;
} }
@ -70,7 +74,6 @@ namespace osu.Game.Screens.Edit.GameplayTest
{ {
musicController.Stop(); musicController.Stop();
editorState.Time = GameplayClockContainer.CurrentTime;
editor.RestoreState(editorState); editor.RestoreState(editorState);
return base.OnExiting(e); return base.OnExiting(e);
} }

View File

@ -7,7 +7,9 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Screens.Edit.Timing namespace osu.Game.Screens.Edit.Timing
{ {
@ -41,6 +43,10 @@ namespace osu.Game.Screens.Edit.Timing
omitBarLine.Current.BindValueChanged(_ => saveChanges()); omitBarLine.Current.BindValueChanged(_ => saveChanges());
scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges()); scrollSpeedSlider.Current.BindValueChanged(_ => saveChanges());
var drawableRuleset = Beatmap.BeatmapInfo.Ruleset.CreateInstance().CreateDrawableRulesetWith(Beatmap.PlayableBeatmap);
if (drawableRuleset is not IDrawableScrollingRuleset scrollingRuleset || scrollingRuleset.VisualisationMethod == ScrollVisualisationMethod.Constant)
scrollSpeedSlider.Hide();
void saveChanges() void saveChanges()
{ {
if (!isRebinding) ChangeHandler?.SaveState(); if (!isRebinding) ChangeHandler?.SaveState();

View File

@ -224,8 +224,8 @@ namespace osu.Game.Screens.Menu
{ {
rulesetsScale.ScaleTo(0.8f, 1000); rulesetsScale.ScaleTo(0.8f, 1000);
rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0)); rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0));
welcomeText.FadeOut(); welcomeText.FadeOut().Expire();
triangles.FadeOut(); triangles.FadeOut().Expire();
} }
using (BeginDelayedSequence(rulesets_2)) using (BeginDelayedSequence(rulesets_2))
@ -307,7 +307,7 @@ namespace osu.Game.Screens.Menu
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {

View File

@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{ {
await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false); await base.PrepareScoreForResultsAsync(score).ConfigureAwait(false);
Score.ScoreInfo.TotalScore = (int)Math.Round(ScoreProcessor.ComputeScore(ScoringMode.Standardised, Score.ScoreInfo)); Score.ScoreInfo.TotalScore = ScoreProcessor.ComputeScore(ScoringMode.Standardised, Score.ScoreInfo);
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)

View File

@ -171,12 +171,14 @@ namespace osu.Game.Screens.Play.HUD
for (int i = 0; i < Flow.Count; i++) for (int i = 0; i < Flow.Count; i++)
{ {
Flow.SetLayoutPosition(orderedByScore[i], i); Flow.SetLayoutPosition(orderedByScore[i], i);
orderedByScore[i].ScorePosition = i + 1; orderedByScore[i].ScorePosition = CheckValidScorePosition(i + 1) ? i + 1 : null;
} }
sorting.Validate(); sorting.Validate();
} }
protected virtual bool CheckValidScorePosition(int i) => true;
private class InputDisabledScrollContainer : OsuScrollContainer private class InputDisabledScrollContainer : OsuScrollContainer
{ {
public InputDisabledScrollContainer() public InputDisabledScrollContainer()

View File

@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD
private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText; private OsuSpriteText positionText, scoreText, accuracyText, comboText, usernameText;
public BindableDouble TotalScore { get; } = new BindableDouble(); public BindableLong TotalScore { get; } = new BindableLong();
public BindableDouble Accuracy { get; } = new BindableDouble(1); public BindableDouble Accuracy { get; } = new BindableDouble(1);
public BindableInt Combo { get; } = new BindableInt(); public BindableInt Combo { get; } = new BindableInt();
public BindableBool HasQuit { get; } = new BindableBool(); public BindableBool HasQuit { get; } = new BindableBool();
@ -62,20 +62,22 @@ namespace osu.Game.Screens.Play.HUD
private int? scorePosition; private int? scorePosition;
private bool scorePositionIsSet;
public int? ScorePosition public int? ScorePosition
{ {
get => scorePosition; get => scorePosition;
set set
{ {
if (value == scorePosition) // We always want to run once, as the incoming value may be null and require a visual update to "-".
if (value == scorePosition && scorePositionIsSet)
return; return;
scorePosition = value; scorePosition = value;
if (scorePosition.HasValue) positionText.Text = scorePosition.HasValue ? $"#{scorePosition.Value.FormatRank()}" : "-";
positionText.Text = $"#{scorePosition.Value.FormatRank()}"; scorePositionIsSet = true;
positionText.FadeTo(scorePosition.HasValue ? 1 : 0);
updateState(); updateState();
} }
} }

View File

@ -9,7 +9,7 @@ namespace osu.Game.Screens.Play.HUD
{ {
public interface ILeaderboardScore public interface ILeaderboardScore
{ {
BindableDouble TotalScore { get; } BindableLong TotalScore { get; }
BindableDouble Accuracy { get; } BindableDouble Accuracy { get; }
BindableInt Combo { get; } BindableInt Combo { get; }

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.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
@ -184,7 +183,7 @@ namespace osu.Game.Screens.Play.HUD
continue; continue;
if (TeamScores.TryGetValue(u.Team.Value, out var team)) if (TeamScores.TryGetValue(u.Team.Value, out var team))
team.Value += (int)Math.Round(u.ScoreProcessor.TotalScore.Value); team.Value += u.ScoreProcessor.TotalScore.Value;
} }
} }

View File

@ -7,8 +7,10 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring; using osu.Game.Scoring;
using osu.Game.Screens.Select;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
@ -18,6 +20,9 @@ namespace osu.Game.Screens.Play.HUD
private const int duration = 100; private const int duration = 100;
private readonly Bindable<bool> configVisibility = new Bindable<bool>(); private readonly Bindable<bool> configVisibility = new Bindable<bool>();
private readonly Bindable<PlayBeatmapDetailArea.TabType> scoreSource = new Bindable<PlayBeatmapDetailArea.TabType>();
private readonly IUser trackingUser; private readonly IUser trackingUser;
public readonly IBindableList<ScoreInfo> Scores = new BindableList<ScoreInfo>(); public readonly IBindableList<ScoreInfo> Scores = new BindableList<ScoreInfo>();
@ -46,11 +51,13 @@ namespace osu.Game.Screens.Play.HUD
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility); config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
config.BindWith(OsuSetting.BeatmapDetailTab, scoreSource);
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
Scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true); Scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true);
// Alpha will be updated via `updateVisibility` below. // Alpha will be updated via `updateVisibility` below.
@ -93,6 +100,18 @@ namespace osu.Game.Screens.Play.HUD
local.DisplayOrder.Value = long.MaxValue; local.DisplayOrder.Value = long.MaxValue;
} }
protected override bool CheckValidScorePosition(int i)
{
// change displayed position to '-' when there are 50 already submitted scores and tracked score is last
if (scoreSource.Value != PlayBeatmapDetailArea.TabType.Local)
{
if (i == Flow.Count && Flow.Count > GetScoresRequest.MAX_SCORES_PER_REQUEST)
return false;
}
return base.CheckValidScorePosition(i);
}
private void updateVisibility() => private void updateVisibility() =>
this.FadeTo(AlwaysVisible.Value || configVisibility.Value ? 1 : 0, duration); this.FadeTo(AlwaysVisible.Value || configVisibility.Value ? 1 : 0, duration);
} }

View File

@ -85,7 +85,7 @@ namespace osu.Game.Screens.Play
api.Queue(req); api.Queue(req);
// Generally a timeout would not happen here as APIAccess will timeout first. // Generally a timeout would not happen here as APIAccess will timeout first.
if (!tcs.Task.Wait(60000)) if (!tcs.Task.Wait(30000))
req.TriggerFailure(new InvalidOperationException("Token retrieval timed out (request never run)")); req.TriggerFailure(new InvalidOperationException("Token retrieval timed out (request never run)"));
return true; return true;

View File

@ -770,7 +770,7 @@ namespace osu.Game.Screens.Select
{ {
updateItem(item); updateItem(item);
if (!item.Item.Filtered.Value) if (item.Item.Visible)
{ {
bool isSelected = item.Item.State.Value == CarouselItemState.Selected; bool isSelected = item.Item.State.Value == CarouselItemState.Selected;

View File

@ -57,7 +57,18 @@ namespace osu.Game.Screens.Select
} }
} }
private void updateModeLight() => modeLight.FadeColour(buttons.FirstOrDefault(b => b.IsHovered)?.SelectedColour ?? Color4.Transparent, TRANSITION_LENGTH, Easing.OutQuint); private void updateModeLight()
{
var selectedButton = buttons.FirstOrDefault(b => b.Enabled.Value && b.IsHovered);
if (selectedButton != null)
{
modeLight.FadeIn(TRANSITION_LENGTH, Easing.OutQuint);
modeLight.FadeColour(selectedButton.SelectedColour, TRANSITION_LENGTH, Easing.OutQuint);
}
else
modeLight.FadeOut(TRANSITION_LENGTH, Easing.OutQuint);
}
public Footer() public Footer()
{ {
@ -78,6 +89,7 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = 3, Height = 3,
Position = new Vector2(0, -3), Position = new Vector2(0, -3),
Colour = Color4.Black,
}, },
new FillFlowContainer new FillFlowContainer
{ {

View File

@ -120,10 +120,18 @@ namespace osu.Game.Screens.Select
}; };
} }
protected override void LoadComplete()
{
base.LoadComplete();
Enabled.BindValueChanged(_ => updateDisplay(), true);
}
public Action Hovered; public Action Hovered;
public Action HoverLost; public Action HoverLost;
public GlobalAction? Hotkey; public GlobalAction? Hotkey;
private bool mouseDown;
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
@ -140,32 +148,38 @@ namespace osu.Game.Screens.Select
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
Hovered?.Invoke(); Hovered?.Invoke();
light.ScaleTo(new Vector2(1, 2), Footer.TRANSITION_LENGTH, Easing.OutQuint); updateDisplay();
light.FadeColour(SelectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
return true; return true;
} }
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
HoverLost?.Invoke(); HoverLost?.Invoke();
light.ScaleTo(new Vector2(1, 1), Footer.TRANSITION_LENGTH, Easing.OutQuint); updateDisplay();
light.FadeColour(DeselectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
} }
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
{ {
box.FadeTo(0.3f, Footer.TRANSITION_LENGTH * 2, Easing.OutQuint); if (!Enabled.Value)
return true;
mouseDown = true;
updateDisplay();
return base.OnMouseDown(e); return base.OnMouseDown(e);
} }
protected override void OnMouseUp(MouseUpEvent e) protected override void OnMouseUp(MouseUpEvent e)
{ {
box.FadeOut(Footer.TRANSITION_LENGTH, Easing.OutQuint); mouseDown = false;
updateDisplay();
base.OnMouseUp(e); base.OnMouseUp(e);
} }
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
if (!Enabled.Value)
return true;
box.ClearTransforms(); box.ClearTransforms();
box.Alpha = 1; box.Alpha = 1;
box.FadeOut(Footer.TRANSITION_LENGTH * 3, Easing.OutQuint); box.FadeOut(Footer.TRANSITION_LENGTH * 3, Easing.OutQuint);
@ -184,5 +198,20 @@ namespace osu.Game.Screens.Select
} }
public virtual void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) { } public virtual void OnReleased(KeyBindingReleaseEvent<GlobalAction> e) { }
private void updateDisplay()
{
this.FadeTo(Enabled.Value ? 1 : 0.25f, Footer.TRANSITION_LENGTH, Easing.OutQuint);
light.ScaleTo(Enabled.Value && IsHovered ? new Vector2(1, 2) : new Vector2(1), Footer.TRANSITION_LENGTH, Easing.OutQuint);
light.FadeColour(Enabled.Value && IsHovered ? SelectedColour : DeselectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
box.FadeTo(Enabled.Value & mouseDown ? 0.3f : 0f, Footer.TRANSITION_LENGTH * 2, Easing.OutQuint);
if (Enabled.Value && IsHovered)
Hovered?.Invoke();
else
HoverLost?.Invoke();
}
} }
} }

View File

@ -204,10 +204,11 @@ namespace osu.Game.Screens.Select.Leaderboards
} }
else if (filterMods) else if (filterMods)
{ {
// otherwise find all the scores that have *any* of the currently selected mods (similar to how web applies mod filters) // otherwise find all the scores that have all of the currently selected mods (similar to how web applies mod filters)
// we're creating and using a string list representation of selected mods so that it can be translated into the DB query itself // we're creating and using a string HashSet representation of selected mods so that it can be translated into the DB query itself
var selectedMods = mods.Value.Select(m => m.Acronym); var selectedMods = mods.Value.Select(m => m.Acronym).ToHashSet();
scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym)));
scores = scores.Where(s => selectedMods.SetEquals(s.Mods.Select(m => m.Acronym)));
} }
scores = scoreManager.OrderByTotalScore(scores.Detach()); scores = scoreManager.OrderByTotalScore(scores.Detach());

View File

@ -112,6 +112,8 @@ namespace osu.Game.Screens.Select
protected BeatmapDetailArea BeatmapDetails { get; private set; } protected BeatmapDetailArea BeatmapDetails { get; private set; }
private FooterButtonOptions beatmapOptionsButton;
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>(); private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
private double audioFeedbackLastPlaybackTime; private double audioFeedbackLastPlaybackTime;
@ -314,7 +316,7 @@ namespace osu.Game.Screens.Select
NextRandom = () => Carousel.SelectNextRandom(), NextRandom = () => Carousel.SelectNextRandom(),
PreviousRandom = Carousel.SelectPreviousRandom PreviousRandom = Carousel.SelectPreviousRandom
}, null), }, null),
(new FooterButtonOptions(), BeatmapOptions) (beatmapOptionsButton = new FooterButtonOptions(), BeatmapOptions)
}; };
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(); protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
@ -739,6 +741,16 @@ namespace osu.Game.Screens.Select
beatmapInfoWedge.Beatmap = beatmap; beatmapInfoWedge.Beatmap = beatmap;
BeatmapDetails.Beatmap = beatmap; BeatmapDetails.Beatmap = beatmap;
bool beatmapSelected = beatmap is not DummyWorkingBeatmap;
if (beatmapSelected)
beatmapOptionsButton.Enabled.Value = true;
else
{
beatmapOptionsButton.Enabled.Value = false;
BeatmapOptions.Hide();
}
} }
private readonly WeakReference<ITrack> lastTrack = new WeakReference<ITrack>(null); private readonly WeakReference<ITrack> lastTrack = new WeakReference<ITrack>(null);