1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-21 22:07:25 +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", () =>
{
// 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;
});

View File

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

View File

@ -52,11 +52,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
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>();
private float pointInterval => radius * 7f / 8;
private float pointInterval => width * 7f / 8;
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 IVertexBatch<TexturedVertex2D>? quadBatch;
private float radius;
private float width;
private float height;
private Vector2 drawSize;
private Texture? texture;
private int rotationSeed;
@ -202,7 +205,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
base.ApplyState();
radius = Source.radius;
width = Source.width;
height = Source.height;
drawSize = Source.DrawSize;
texture = Source.Texture;
@ -334,11 +338,13 @@ namespace osu.Game.Rulesets.Osu.Skinning
var dir = PointDirection(point, index);
var ortho = dir.PerpendicularLeft;
dir *= scale * width;
ortho *= scale * height;
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
var localBotRight = point.Position + (radius * scale * (ortho + dir));
var localTopLeft = point.Position - ortho - dir;
var localTopRight = point.Position - ortho + dir;
var localBotLeft = point.Position + ortho - dir;
var localBotRight = point.Position + ortho + dir;
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.TaikoExplosionOk:
return new ArgonHitExplosion(taikoComponent.Component);
case TaikoSkinComponents.Swell:
return new ArgonSwellCirclePiece();
}
break;

View File

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

View File

@ -35,7 +35,7 @@ namespace osu.Game.Tests.Gameplay
// Apply a miss judgement
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]

View File

@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.Editing
}
[Test]
public void TestSharedClockState()
public void TestClockTimeTransferIsOneDirectional()
{
AddStep("seek to 00:01:00", () => EditorClock.Seek(60_000));
AddStep("click test gameplay button", () =>
@ -195,15 +195,15 @@ namespace osu.Game.Tests.Visual.Editing
GameplayClockContainer gameplayClockContainer = null;
AddStep("fetch gameplay clock", () => gameplayClockContainer = editorPlayer.ChildrenOfType<GameplayClockContainer>().First());
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);
double timeAtPlayerExit = 0;
AddWaitStep("wait some", 5);
AddStep("store time before exit", () => timeAtPlayerExit = gameplayClockContainer.CurrentTime);
AddStep("exit player", () => editorPlayer.Exit());
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()

View File

@ -3,14 +3,15 @@
#nullable disable
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Timing;
using osu.Game.Screens.Edit.Timing.RowAttributes;
@ -21,10 +22,6 @@ namespace osu.Game.Tests.Visual.Editing
[TestFixture]
public class TestSceneTimingScreen : EditorClockTestScene
{
[Cached(typeof(EditorBeatmap))]
[Cached(typeof(IBeatSnapProvider))]
private readonly EditorBeatmap editorBeatmap;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
@ -32,21 +29,27 @@ namespace osu.Game.Tests.Visual.Editing
protected override bool ScrollUsingMouseWheel => false;
public TestSceneTimingScreen()
{
editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo));
}
protected override void LoadComplete()
{
base.LoadComplete();
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
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 readonly BindableDouble playerScore = new BindableDouble();
private readonly BindableLong playerScore = new BindableLong();
public TestSceneGameplayLeaderboard()
{
@ -76,8 +76,8 @@ namespace osu.Game.Tests.Visual.Gameplay
createLeaderboard();
addLocalPlayer();
var player2Score = new BindableDouble(1234567);
var player3Score = new BindableDouble(1111111);
var player2Score = new BindableLong(1234567);
var player3Score = new BindableLong(1111111);
AddStep("add player 2", () => createLeaderboardScore(player2Score, new APIUser { Username = "Player 2" }));
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);
leaderboardScore.TotalScore.BindTo(score);

View File

@ -15,6 +15,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select;
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 Bindable<bool> configVisibility = new Bindable<bool>();
private readonly Bindable<PlayBeatmapDetailArea.TabType> beatmapTabType = new Bindable<PlayBeatmapDetailArea.TabType>();
private SoloGameplayLeaderboard leaderboard = null!;
@ -33,6 +35,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
config.BindWith(OsuSetting.BeatmapDetailTab, beatmapTabType);
}
[SetUpSteps]
@ -70,6 +73,25 @@ namespace osu.Game.Tests.Visual.Gameplay
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]
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 = @"frenzibyte" }, 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
using System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Extensions;
@ -85,6 +86,19 @@ namespace osu.Game.Tests.Visual.Navigation
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]
public void TestOverlaysAlwaysClosed()
{
@ -270,5 +284,16 @@ namespace osu.Game.Tests.Visual.Navigation
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 NUnit.Framework;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Select;
namespace osu.Game.Tests.Visual.Navigation
{
@ -55,6 +57,7 @@ namespace osu.Game.Tests.Visual.Navigation
presentAndConfirm(firstImport);
var secondImport = importBeatmap(3);
confirmBeatmapInSongSelect(secondImport);
presentAndConfirm(secondImport);
// Test presenting same beatmap more than once
@ -74,6 +77,7 @@ namespace osu.Game.Tests.Visual.Navigation
presentAndConfirm(firstImport);
var secondImport = importBeatmap(3, new ManiaRuleset().RulesetInfo);
confirmBeatmapInSongSelect(secondImport);
presentAndConfirm(secondImport);
presentSecondDifficultyAndConfirm(firstImport, 1);
@ -134,13 +138,22 @@ namespace osu.Game.Tests.Visual.Navigation
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)
{
AddStep("present beatmap", () => Game.PresentBeatmap(getImport()));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.MatchesOnlineID(getImport()));
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.Equals(getImport().Beatmaps.First().Ruleset));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID, () => Is.EqualTo(getImport().OnlineID));
AddAssert("correct ruleset selected", () => Game.Ruleset.Value, () => Is.EqualTo(getImport().Beatmaps.First().Ruleset));
}
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;
AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID == importedID * 2048);
AddAssert("correct ruleset selected", () => Game.Ruleset.Value.Equals(getImport().Beatmaps.First().Ruleset));
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect songSelect && songSelect.IsLoaded);
AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID, () => Is.EqualTo(importedID * 2048));
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);
AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely());
TestPlaySongSelect songSelect = null;
PushAndConfirm(() => songSelect = new TestPlaySongSelect());

View File

@ -40,6 +40,43 @@ namespace osu.Game.Tests.Visual.Settings
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]
public void TestBindingMouseWheelToNonGameplay()
{
@ -169,7 +206,8 @@ namespace osu.Game.Tests.Visual.Settings
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]
@ -198,7 +236,8 @@ namespace osu.Game.Tests.Visual.Settings
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]
@ -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 firstButton = firstRow.ChildrenOfType<KeyBindingRow.KeyButton>().First();
return firstButton.Text.Text == keyName;
});
return firstButton.Text.Text.ToString();
}, () => Is.EqualTo(keyName));
}
private void scrollToAndStartBinding(string name)

View File

@ -11,6 +11,7 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
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]
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);
}
[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()
{
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);

View File

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

View File

@ -8,7 +8,7 @@ namespace osu.Game.Configuration
{
/// <summary>
/// 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>
/// <remarks>
/// 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.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.IO;
@ -22,22 +22,42 @@ namespace osu.Game.Database
{
// make sure the directory exists
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);
if (!directoryStorage.GetFiles(string.Empty).ExcludeSystemFileNames().Any())
foreach (string directory in storage.GetDirectories(string.Empty))
{
// 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))
yield return subDirectory;
var directoryStorage = storage.GetStorageForDirectory(directory);
try
{
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)

View File

@ -4,6 +4,7 @@
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -69,6 +70,8 @@ namespace osu.Game.Graphics.UserInterface
protected Box Background;
protected SpriteText SpriteText;
private readonly Box flashLayer;
public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button)
{
Height = 40;
@ -99,6 +102,14 @@ namespace osu.Game.Graphics.UserInterface
Depth = float.MinValue
},
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)
{
if (Enabled.Value)
Background.FlashColour(Color4.White, 800, Easing.OutQuint);
flashLayer.FadeOutFromOne(800, Easing.OutQuint);
return base.OnClick(e);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
@ -727,13 +728,20 @@ namespace osu.Game.Online.Multiplayer
if (Room == null)
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));
APIRoom.Playlist.RemoveAt(existingIndex);
APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item));
int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID));
APIRoom.Playlist.RemoveAt(existingIndex);
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);
RoomUpdated?.Invoke();

View File

@ -145,7 +145,9 @@ namespace osu.Game.Online
private async Task onConnectionClosed(Exception? ex, CancellationToken cancellationToken)
{
isConnected.Value = false;
bool hasBeenCancelled = cancellationToken.IsCancellationRequested;
await disconnect(true);
if (ex != null)
await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false);
@ -153,7 +155,7 @@ namespace osu.Game.Online
Logger.Log($"{ClientName} disconnected", LoggingTarget.Network);
// 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);
}
@ -174,7 +176,9 @@ namespace osu.Game.Online
}
finally
{
isConnected.Value = false;
CurrentConnection = null;
if (takeLock)
connectionLock.Release();
}

View File

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

View File

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

View File

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

View File

@ -119,22 +119,17 @@ namespace osu.Game.Overlays.Dashboard.Home.News
[BackgroundDependencyLoader]
private void load(GameHost host)
{
NewsPostBackground bg;
Child = new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage)
Child = new DelayedLoadUnloadWrapper(() => new NewsPostBackground(post.FirstImage)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0
})
{
RelativeSizeAxes = Axes.Both
};
bg.OnLoadComplete += d => d.FadeIn(250, Easing.In);
TooltipText = "view in browser";
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.Sprites;
using osu.Game.Localisation;
using osu.Game.Overlays.Settings;
using osuTK;
namespace osu.Game.Overlays.FirstRunSetup
@ -26,7 +27,7 @@ namespace osu.Game.Overlays.FirstRunSetup
public class ScreenWelcome : FirstRunSetupScreen
{
[BackgroundDependencyLoader]
private void load()
private void load(FrameworkConfigManager frameworkConfig)
{
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
{
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);
}
NewsPostBackground bg;
AddRange(new Drawable[]
{
background = new Box
@ -71,14 +70,14 @@ namespace osu.Game.Overlays.News
CornerRadius = 6,
Children = new Drawable[]
{
new DelayedLoadWrapper(bg = new NewsPostBackground(post.FirstImage)
new DelayedLoadUnloadWrapper(() => new NewsPostBackground(post.FirstImage)
{
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Alpha = 0
})
}, timeBeforeUnload: 5000)
{
RelativeSizeAxes = Axes.Both
},
@ -116,8 +115,6 @@ namespace osu.Game.Overlays.News
IdleColour = colourProvider.Background4;
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.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font
main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12));

View File

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

View File

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

View File

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

View File

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

View File

@ -33,6 +33,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
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 IEnumerable<RealmKeyBinding> bindings;
@ -153,7 +158,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Spacing = new Vector2(5),
Children = new Drawable[]
{
new CancelButton { Action = finalise },
new CancelButton { Action = () => finalise(false) },
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;
}
@ -240,7 +245,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}
if (bindTarget.IsHovered)
finalise();
finalise(false);
// prevent updating bind target before clear button's action
else if (!cancelAndClearButtons.Any(b => b.IsHovered))
updateBindTarget();
@ -252,7 +257,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
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();
return true;
}
@ -263,10 +268,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
protected override bool OnKeyDown(KeyDownEvent e)
{
if (!HasFocus)
if (!HasFocus || e.Repeat)
return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState));
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromKey(e.Key));
if (!isModifier(e.Key)) finalise();
return true;
@ -288,7 +293,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus)
return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState));
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromJoystickButton(e.Button));
finalise();
return true;
@ -310,7 +315,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus)
return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState));
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromMidiKey(e.Key));
finalise();
return true;
@ -332,7 +337,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus)
return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState));
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromTabletAuxiliaryButton(e.Button));
finalise();
return true;
@ -354,7 +359,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!HasFocus)
return false;
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState));
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(e.CurrentState), KeyCombination.FromTabletPenButton(e.Button));
finalise();
return true;
@ -377,10 +382,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input
return;
bindTarget.UpdateKeyCombination(InputKey.None);
finalise();
finalise(false);
}
private void finalise()
private void finalise(bool hasChanged = true)
{
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.)
bindTarget = null;
if (hasChanged)
BindingUpdated?.Invoke(this);
});
}
@ -417,7 +424,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
protected override void OnFocusLost(FocusLostEvent e)
{
finalise();
finalise(false);
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)
{
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
{
/// <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;
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())
{
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())
});
}
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

View File

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

View File

@ -3,7 +3,6 @@
#nullable disable
using System;
using osu.Framework;
using osu.Framework.Allocation;
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)
{
t.NewLine();
var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(RuntimeInfo.OS == RuntimeInfo.Platform.Windows
? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value);
var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription(
RuntimeInfo.OS == RuntimeInfo.Platform.Windows
? @"https://opentabletdriver.net/Wiki/FAQ/Windows"
: @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value);
t.AddLinks(formattedSource.Text, formattedSource.Links);
}
}),
@ -274,6 +274,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
sizeY.Default = sizeY.MaxValue = tab.Size.Y;
areaSize.Default = new Vector2(sizeX.Default, sizeY.Default);
areaOffset.Default = new Vector2(offsetX.Default, offsetY.Default);
}), true);
}
@ -325,7 +326,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
{
aspectLock.Value = false;
int proposedHeight = getHeight(sizeX.Value, aspectRatio);
float proposedHeight = getHeight(sizeX.Value, aspectRatio);
if (proposedHeight < sizeY.MaxValue)
sizeY.Value = proposedHeight;
@ -342,8 +343,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
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
{
protected override bool AutoAdvanceTarget => true;
protected override LocalisableString Header { get; }
public VariantBindingsSubsection(RulesetInfo ruleset, int variant)

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Scoring
/// <summary>
/// The current total score.
/// </summary>
public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 };
public readonly BindableLong TotalScore = new BindableLong { MinValue = 0 };
/// <summary>
/// The current accuracy.
@ -267,7 +267,7 @@ namespace osu.Game.Rulesets.Scoring
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);
}
@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Scoring
/// <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>
[Pure]
public double ComputeScore(ScoringMode mode, ScoreInfo scoreInfo)
public long ComputeScore(ScoringMode mode, ScoreInfo scoreInfo)
{
if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset))
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>
/// <returns>The total score computed from the given scoring values.</returns>
[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;
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>
/// <returns>The total score computed from the given scoring component ratios.</returns>
[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)
{
default:
case ScoringMode.Standardised:
double accuracyScore = accuracyPortion * accuracyRatio;
double comboScore = comboPortion * comboRatio;
return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier;
return (long)Math.Round(rawScore);
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.
// 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;
return Math.Pow(scaledStandardised * Math.Max(1, totalBasicHitObjects), 2) * ClassicScoreMultiplier;
double scaledRawScore = rawScore / max_score;
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);
// Populate total score after everything else.
score.TotalScore = (long)Math.Round(ComputeScore(ScoringMode.Standardised, score));
score.TotalScore = ComputeScore(ScoringMode.Standardised, score);
}
/// <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"/>.
/// <see cref="HitObject"/>s inside this <see cref="DrawableRuleset{TObject}"/> will scroll within the playfield.
/// </summary>
public abstract class DrawableScrollingRuleset<TObject> : DrawableRuleset<TObject>, IKeyBindingHandler<GlobalAction>
public abstract class DrawableScrollingRuleset<TObject> : DrawableRuleset<TObject>, IDrawableScrollingRuleset, IKeyBindingHandler<GlobalAction>
where TObject : HitObject
{
/// <summary>
@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.UI.Scrolling
protected virtual ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Sequential;
ScrollVisualisationMethod IDrawableScrollingRuleset.VisualisationMethod => VisualisationMethod;
/// <summary>
/// Whether the player can change <see cref="TimeRange"/>.
/// </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();
scoreProcessor.Mods.Value = score.Mods;
return (long)Math.Round(scoreProcessor.ComputeScore(mode, score));
return scoreProcessor.ComputeScore(mode, score);
}
/// <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"/>.
/// </summary>
[Key(0)]
public double BaseScore;
public long BaseScore;
/// <summary>
/// The sum of all "bonus" <see cref="HitObject"/> scoring values. See: <see cref="HitResultExtensions.IsBonus"/> and <see cref="Judgement.ToNumericResult"/>.
/// </summary>
[Key(1)]
public double BonusScore;
public long BonusScore;
/// <summary>
/// The highest achieved combo.

View File

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

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
@ -19,7 +20,7 @@ using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Screens.Edit.Compose
{
public class ComposeScreen : EditorScreenWithTimeline
public class ComposeScreen : EditorScreenWithTimeline, IGameplaySettings
{
[Resolved]
private GameHost host { get; set; }
@ -27,6 +28,9 @@ namespace osu.Game.Screens.Edit.Compose
[Resolved]
private EditorClock clock { get; set; }
[Resolved]
private IGameplaySettings globalGameplaySettings { get; set; }
private Bindable<string> clipboard { get; set; }
private HitObjectComposer composer;
@ -157,5 +161,12 @@ namespace osu.Game.Screens.Edit.Compose
}
#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]
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider,
IGameplaySettings
public class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler<GlobalAction>, IKeyBindingHandler<PlatformAction>, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider
{
public override float BackgroundParallaxAmount => 0.1f;
@ -99,9 +98,6 @@ namespace osu.Game.Screens.Edit
[Resolved(canBeNull: true)]
private INotificationOverlay notifications { get; set; }
[Resolved]
private IGameplaySettings globalGameplaySettings { get; set; }
public readonly Bindable<EditorScreenMode> Mode = new Bindable<EditorScreenMode>();
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();
if (track.Value?.IsRunning != true)
if (!IsRunning)
{
// 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).

View File

@ -1,8 +1,7 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Beatmaps;
@ -17,7 +16,7 @@ namespace osu.Game.Screens.Edit.GameplayTest
private readonly EditorState editorState;
[Resolved]
private MusicController musicController { get; set; }
private MusicController musicController { get; set; } = null!;
public EditorPlayer(Editor editor)
: base(new PlayerConfiguration { ShowResults = false })
@ -29,7 +28,12 @@ namespace osu.Game.Screens.Edit.GameplayTest
protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double 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;
}
@ -70,7 +74,6 @@ namespace osu.Game.Screens.Edit.GameplayTest
{
musicController.Stop();
editorState.Time = GameplayClockContainer.CurrentTime;
editor.RestoreState(editorState);
return base.OnExiting(e);
}

View File

@ -7,7 +7,9 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Screens.Edit.Timing
{
@ -41,6 +43,10 @@ namespace osu.Game.Screens.Edit.Timing
omitBarLine.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()
{
if (!isRebinding) ChangeHandler?.SaveState();

View File

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

View File

@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
{
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)

View File

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

View File

@ -50,7 +50,7 @@ namespace osu.Game.Screens.Play.HUD
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 BindableInt Combo { get; } = new BindableInt();
public BindableBool HasQuit { get; } = new BindableBool();
@ -62,20 +62,22 @@ namespace osu.Game.Screens.Play.HUD
private int? scorePosition;
private bool scorePositionIsSet;
public int? ScorePosition
{
get => scorePosition;
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;
scorePosition = value;
if (scorePosition.HasValue)
positionText.Text = $"#{scorePosition.Value.FormatRank()}";
positionText.Text = scorePosition.HasValue ? $"#{scorePosition.Value.FormatRank()}" : "-";
scorePositionIsSet = true;
positionText.FadeTo(scorePosition.HasValue ? 1 : 0);
updateState();
}
}

View File

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

View File

@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
@ -184,7 +183,7 @@ namespace osu.Game.Screens.Play.HUD
continue;
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.Graphics;
using osu.Game.Configuration;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Select;
using osu.Game.Users;
namespace osu.Game.Screens.Play.HUD
@ -18,6 +20,9 @@ namespace osu.Game.Screens.Play.HUD
private const int duration = 100;
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
private readonly Bindable<PlayBeatmapDetailArea.TabType> scoreSource = new Bindable<PlayBeatmapDetailArea.TabType>();
private readonly IUser trackingUser;
public readonly IBindableList<ScoreInfo> Scores = new BindableList<ScoreInfo>();
@ -46,11 +51,13 @@ namespace osu.Game.Screens.Play.HUD
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.GameplayLeaderboard, configVisibility);
config.BindWith(OsuSetting.BeatmapDetailTab, scoreSource);
}
protected override void LoadComplete()
{
base.LoadComplete();
Scores.BindCollectionChanged((_, _) => Scheduler.AddOnce(showScores), true);
// Alpha will be updated via `updateVisibility` below.
@ -93,6 +100,18 @@ namespace osu.Game.Screens.Play.HUD
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() =>
this.FadeTo(AlwaysVisible.Value || configVisibility.Value ? 1 : 0, duration);
}

View File

@ -85,7 +85,7 @@ namespace osu.Game.Screens.Play
api.Queue(req);
// 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)"));
return true;

View File

@ -770,7 +770,7 @@ namespace osu.Game.Screens.Select
{
updateItem(item);
if (!item.Item.Filtered.Value)
if (item.Item.Visible)
{
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()
{
@ -78,6 +89,7 @@ namespace osu.Game.Screens.Select
RelativeSizeAxes = Axes.X,
Height = 3,
Position = new Vector2(0, -3),
Colour = Color4.Black,
},
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 HoverLost;
public GlobalAction? Hotkey;
private bool mouseDown;
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@ -140,32 +148,38 @@ namespace osu.Game.Screens.Select
protected override bool OnHover(HoverEvent e)
{
Hovered?.Invoke();
light.ScaleTo(new Vector2(1, 2), Footer.TRANSITION_LENGTH, Easing.OutQuint);
light.FadeColour(SelectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
updateDisplay();
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
HoverLost?.Invoke();
light.ScaleTo(new Vector2(1, 1), Footer.TRANSITION_LENGTH, Easing.OutQuint);
light.FadeColour(DeselectedColour, Footer.TRANSITION_LENGTH, Easing.OutQuint);
updateDisplay();
}
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);
}
protected override void OnMouseUp(MouseUpEvent e)
{
box.FadeOut(Footer.TRANSITION_LENGTH, Easing.OutQuint);
mouseDown = false;
updateDisplay();
base.OnMouseUp(e);
}
protected override bool OnClick(ClickEvent e)
{
if (!Enabled.Value)
return true;
box.ClearTransforms();
box.Alpha = 1;
box.FadeOut(Footer.TRANSITION_LENGTH * 3, Easing.OutQuint);
@ -184,5 +198,20 @@ namespace osu.Game.Screens.Select
}
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)
{
// otherwise find all the scores that have *any* 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
var selectedMods = mods.Value.Select(m => m.Acronym);
scores = scores.Where(s => s.Mods.Any(m => selectedMods.Contains(m.Acronym)));
// 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 HashSet representation of selected mods so that it can be translated into the DB query itself
var selectedMods = mods.Value.Select(m => m.Acronym).ToHashSet();
scores = scores.Where(s => selectedMods.SetEquals(s.Mods.Select(m => m.Acronym)));
}
scores = scoreManager.OrderByTotalScore(scores.Detach());

View File

@ -112,6 +112,8 @@ namespace osu.Game.Screens.Select
protected BeatmapDetailArea BeatmapDetails { get; private set; }
private FooterButtonOptions beatmapOptionsButton;
private readonly Bindable<RulesetInfo> decoupledRuleset = new Bindable<RulesetInfo>();
private double audioFeedbackLastPlaybackTime;
@ -314,7 +316,7 @@ namespace osu.Game.Screens.Select
NextRandom = () => Carousel.SelectNextRandom(),
PreviousRandom = Carousel.SelectPreviousRandom
}, null),
(new FooterButtonOptions(), BeatmapOptions)
(beatmapOptionsButton = new FooterButtonOptions(), BeatmapOptions)
};
protected virtual ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay();
@ -739,6 +741,16 @@ namespace osu.Game.Screens.Select
beatmapInfoWedge.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);