1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 11:37:28 +08:00

Merge branch 'master' into editor-test-play-dont-retain-test-time

This commit is contained in:
Bartłomiej Dach 2022-11-19 12:09:04 +01:00 committed by GitHub
commit c05c1625f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 509 additions and 166 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

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

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

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

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

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

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

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

@ -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,6 +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.
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;
@ -27,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;
} }

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

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

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