diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs index cd51ccd751..fdb1cac3e5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModTouchDevice.cs @@ -51,7 +51,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestUserAlreadyHasTouchDeviceActive() { loadPlayer(); - // it is presumed that a previous screen (i.e. song select) will set this up AddStep("set up touchscreen user", () => { currentPlayer.Score.ScoreInfo.Mods = currentPlayer.Score.ScoreInfo.Mods.Append(new OsuModTouchDevice()).ToArray(); @@ -69,6 +68,14 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods AddAssert("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); } + [Test] + public void TestTouchActivePriorToPlayerLoad() + { + AddStep("set touch input active", () => statics.SetValue(Static.TouchInputActive, true)); + loadPlayer(); + AddUntilStep("touch device mod activated", () => currentPlayer.Score.ScoreInfo.Mods, () => Has.One.InstanceOf()); + } + [Test] public void TestTouchDuringBreak() { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs index 67a6d5e41a..d4a0f243e4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs @@ -135,7 +135,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void Update() { base.Update(); - spinningMiddle.Rotation = discTop.Rotation = DrawableSpinner.RotationTracker.Rotation; + + float turnRatio = spinningMiddle.Texture != null ? 0.5f : 1; + discTop.Rotation = DrawableSpinner.RotationTracker.Rotation * turnRatio; + spinningMiddle.Rotation = DrawableSpinner.RotationTracker.Rotation; + discBottom.Rotation = discTop.Rotation / 3; glow.Alpha = DrawableSpinner.Progress; diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index 567bf6968f..73465fae08 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -18,10 +18,12 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.UI; +using osu.Game.Scoring; using osu.Game.Scoring.Legacy; using osu.Game.Tests.Beatmaps; @@ -385,6 +387,42 @@ namespace osu.Game.Tests.Rulesets.Scoring Assert.That(scoreProcessor.Accuracy.Value, Is.Not.EqualTo(1)); } + [Test] + public void TestNormalGrades() + { + scoreProcessor.ApplyBeatmap(new Beatmap()); + + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.X)); + + scoreProcessor.Accuracy.Value = 0.99f; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.S)); + } + + [Test] + public void TestSilverGrades() + { + scoreProcessor.ApplyBeatmap(new Beatmap()); + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.X)); + + scoreProcessor.Mods.Value = new[] { new OsuModHidden() }; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.XH)); + + scoreProcessor.Accuracy.Value = 0.99f; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.SH)); + } + + [Test] + public void TestSilverGradesModsAppliedFirst() + { + scoreProcessor.Mods.Value = new[] { new OsuModHidden() }; + scoreProcessor.ApplyBeatmap(new Beatmap()); + + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.XH)); + + scoreProcessor.Accuracy.Value = 0.99f; + Assert.That(scoreProcessor.Rank.Value, Is.EqualTo(ScoreRank.SH)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 8cb993eff2..f59fbc75ac 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -35,6 +35,7 @@ using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; @@ -834,6 +835,24 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("exit dialog is shown", () => Game.Dependencies.Get().CurrentDialog is ConfirmExitDialog); } + [Test] + public void TestQuickSkinEditorDoesntNukeSkin() + { + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddStep("open", () => InputManager.Key(Key.Space)); + AddStep("skin", () => InputManager.Key(Key.E)); + AddStep("editor", () => InputManager.Key(Key.S)); + AddStep("and close immediately", () => InputManager.Key(Key.Escape)); + + AddStep("open again", () => InputManager.Key(Key.S)); + + Player player = null; + + AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); + AddUntilStep("wait for gameplay still has health bar", () => player.ChildrenOfType().Any()); + } + [Test] public void TestTouchScreenDetectionAtSongSelect() { diff --git a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs index 1355bfc272..669c5da01e 100644 --- a/osu.Game/Graphics/Sprites/GlowingSpriteText.cs +++ b/osu.Game/Graphics/Sprites/GlowingSpriteText.cs @@ -5,95 +5,90 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Utils; using osuTK; namespace osu.Game.Graphics.Sprites { - public partial class GlowingSpriteText : Container, IHasText + public partial class GlowingSpriteText : BufferedContainer, IHasText { - private readonly OsuSpriteText spriteText, blurredText; + private const float blur_sigma = 3f; + + // Inflate draw quad to prevent glow from trimming at the edges. + // Padding won't suffice since it will affect text position in cases when it's not centered. + protected override Quad ComputeScreenSpaceDrawQuad() => base.ComputeScreenSpaceDrawQuad().AABBFloat.Inflate(Blur.KernelSize(blur_sigma)); + + private readonly OsuSpriteText text; public LocalisableString Text { - get => spriteText.Text; - set => blurredText.Text = spriteText.Text = value; + get => text.Text; + set => text.Text = value; } public FontUsage Font { - get => spriteText.Font; - set => blurredText.Font = spriteText.Font = value.With(fixedWidth: true); + get => text.Font; + set => text.Font = value.With(fixedWidth: true); } public Vector2 TextSize { - get => spriteText.Size; - set => blurredText.Size = spriteText.Size = value; + get => text.Size; + set => text.Size = value; } public ColourInfo TextColour { - get => spriteText.Colour; - set => spriteText.Colour = value; + get => text.Colour; + set => text.Colour = value; } public ColourInfo GlowColour { - get => blurredText.Colour; - set => blurredText.Colour = value; + get => EffectColour; + set + { + EffectColour = value; + BackgroundColour = value.MultiplyAlpha(0f); + } } public Vector2 Spacing { - get => spriteText.Spacing; - set => spriteText.Spacing = blurredText.Spacing = value; + get => text.Spacing; + set => text.Spacing = value; } public bool UseFullGlyphHeight { - get => spriteText.UseFullGlyphHeight; - set => spriteText.UseFullGlyphHeight = blurredText.UseFullGlyphHeight = value; + get => text.UseFullGlyphHeight; + set => text.UseFullGlyphHeight = value; } public Bindable Current { - get => spriteText.Current; - set => spriteText.Current = value; + get => text.Current; + set => text.Current = value; } public GlowingSpriteText() + : base(cachedFrameBuffer: true) { AutoSizeAxes = Axes.Both; - - Children = new Drawable[] + BlurSigma = new Vector2(blur_sigma); + RedrawOnScale = false; + DrawOriginal = true; + EffectBlending = BlendingParameters.Additive; + EffectPlacement = EffectPlacement.InFront; + Child = text = new OsuSpriteText { - new BufferedContainer(cachedFrameBuffer: true) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - BlurSigma = new Vector2(4), - RedrawOnScale = false, - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - Size = new Vector2(3f), - Children = new[] - { - blurredText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - }, - }, - }, - spriteText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Shadow = false, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Shadow = false, }; } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index a76f4ae955..964f065813 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -164,7 +164,8 @@ namespace osu.Game.Online.Leaderboards { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.X, + Height = 28, Direction = FillDirection.Horizontal, Spacing = new Vector2(10f, 0f), Children = new Drawable[] @@ -357,14 +358,12 @@ namespace osu.Game.Online.Leaderboards }, }, }, - new GlowingSpriteText + new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - TextColour = Color4.White, - GlowColour = Color4Extensions.FromHex(@"83ccfa"), Text = statistic.Value, - Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold), + Font = OsuFont.GetFont(size: 17, weight: FontWeight.Bold, fixedWidth: true) }, }, }; diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index f972186333..d3af928907 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -47,7 +47,7 @@ namespace osu.Game.Overlays.SkinEditor protected override bool StartHidden => true; - private Drawable targetScreen = null!; + private Drawable? targetScreen; private OsuTextFlowContainer headerText = null!; @@ -541,8 +541,14 @@ namespace osu.Game.Overlays.SkinEditor if (!hasBegunMutating) return; + if (targetScreen?.IsLoaded != true) + return; + SkinComponentsContainer[] targetContainers = availableTargets.ToArray(); + if (!targetContainers.All(c => c.ComponentsLoaded)) + return; + foreach (var t in targetContainers) currentSkin.Value.UpdateDrawableTarget(t); diff --git a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs index bedaf12c9b..40cd31934f 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditorOverlay.cs @@ -136,10 +136,15 @@ namespace osu.Game.Overlays.SkinEditor globallyReenableBeatmapSkinSetting(); } - public void PresentGameplay() + public void PresentGameplay() => presentGameplay(false); + + private void presentGameplay(bool attemptedBeatmapSwitch) { performer?.PerformFromScreen(screen => { + if (State.Value != Visibility.Visible) + return; + if (beatmap.Value is DummyWorkingBeatmap) { // presume we don't have anything good to play and just bail. @@ -149,8 +154,12 @@ namespace osu.Game.Overlays.SkinEditor // If we're playing the intro, switch away to another beatmap. if (beatmap.Value.BeatmapSetInfo.Protected) { - music.NextTrack(); - Schedule(PresentGameplay); + if (!attemptedBeatmapSwitch) + { + music.NextTrack(); + Schedule(() => presentGameplay(true)); + } + return; } diff --git a/osu.Game/Rulesets/Judgements/JudgementResult.cs b/osu.Game/Rulesets/Judgements/JudgementResult.cs index 1b915d52b7..b781a13929 100644 --- a/osu.Game/Rulesets/Judgements/JudgementResult.cs +++ b/osu.Game/Rulesets/Judgements/JudgementResult.cs @@ -94,6 +94,11 @@ namespace osu.Game.Rulesets.Judgements /// public bool IsHit => Type.IsHit(); + /// + /// The increase in health resulting from this judgement result. + /// + public double HealthIncrease => Judgement.HealthIncreaseFor(this); + /// /// Creates a new . /// diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index 3e0b6433c2..b5eb755650 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Scoring /// /// The . /// The health increase. - protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.Judgement.HealthIncreaseFor(result); + protected virtual double GetHealthIncreaseFor(JudgementResult result) => result.HealthIncrease; /// /// The default conditions for failing. diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 14fa928224..80e751422e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -186,16 +186,7 @@ namespace osu.Game.Rulesets.Scoring Ruleset = ruleset; Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue); - Accuracy.ValueChanged += accuracy => - { - // Once failed, we shouldn't update the rank anymore. - if (rank.Value == ScoreRank.F) - return; - - rank.Value = RankFromAccuracy(accuracy.NewValue); - foreach (var mod in Mods.Value.OfType()) - rank.Value = mod.AdjustRank(Rank.Value, accuracy.NewValue); - }; + Accuracy.ValueChanged += _ => updateRank(); Mods.ValueChanged += mods => { @@ -205,6 +196,7 @@ namespace osu.Game.Rulesets.Scoring scoreMultiplier *= m.ScoreMultiplier; updateScore(); + updateRank(); }; } @@ -372,6 +364,17 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = (long)Math.Round(ComputeTotalScore(comboProgress, accuracyProcess, currentBonusPortion) * scoreMultiplier); } + private void updateRank() + { + // Once failed, we shouldn't update the rank anymore. + if (rank.Value == ScoreRank.F) + return; + + rank.Value = RankFromAccuracy(Accuracy.Value); + foreach (var mod in Mods.Value.OfType()) + rank.Value = mod.AdjustRank(Rank.Value, Accuracy.Value); + } + protected virtual double ComputeTotalScore(double comboProgress, double accuracyProgress, double bonusPortion) { return 500000 * Accuracy.Value * comboProgress + @@ -417,8 +420,8 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; - rank.Value = ScoreRank.X; HighestCombo.Value = 0; + updateRank(); } /// diff --git a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs index 6fc957874a..71996718d9 100644 --- a/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ArgonHealthDisplay.cs @@ -136,7 +136,12 @@ namespace osu.Game.Screens.Play.HUD BarHeight.BindValueChanged(_ => updateContentSize(), true); } - private void onNewJudgement(JudgementResult result) => pendingMissAnimation |= !result.IsHit; + private void onNewJudgement(JudgementResult result) + { + // Check the health increase because cases like osu!catch bananas fire `IgnoreMiss`, + // which counts as a miss but doesn't actually subtract any health. + pendingMissAnimation |= !result.IsHit && result.HealthIncrease < 0; + } protected override void Update() { diff --git a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs index 69c3cd0ded..12fb748e7d 100644 --- a/osu.Game/Screens/Play/PlayerTouchInputDetector.cs +++ b/osu.Game/Screens/Play/PlayerTouchInputDetector.cs @@ -20,12 +20,16 @@ namespace osu.Game.Screens.Play private GameplayState gameplayState { get; set; } = null!; private IBindable touchActive = new BindableBool(); + private IBindable isBreakTime = null!; [BackgroundDependencyLoader] private void load(SessionStatics statics) { touchActive = statics.GetBindable(Static.TouchInputActive); touchActive.BindValueChanged(_ => updateState()); + + isBreakTime = player.IsBreakTime.GetBoundCopy(); + isBreakTime.BindValueChanged(_ => updateState(), true); } private void updateState() @@ -39,7 +43,7 @@ namespace osu.Game.Screens.Play if (gameplayState.Score.ScoreInfo.Mods.OfType().Any()) return; - if (player.IsBreakTime.Value) + if (isBreakTime.Value) return; var touchDeviceMod = gameplayState.Ruleset.GetTouchDeviceMod();