From dc588e6d56a8e88eb408d75f7b7d2cb1780e5ef9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 04:58:17 +0300 Subject: [PATCH 001/139] Implement OsuModDepth --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 163 ++++++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 3 +- 2 files changed, 165 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs new file mode 100644 index 0000000000..9c0e34d0c8 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModDepth : ModWithVisibilityAdjustment, IUpdatableByPlayfield, IApplicableToDrawableRuleset + { + public override string Name => "Depth"; + public override string Acronym => "DH"; + public override IconUsage? Icon => FontAwesome.Solid.Cube; + public override ModType Type => ModType.Fun; + public override LocalisableString Description => "3D. Almost."; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray(); + + private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); + + [SettingSource("Maximum Depth", "How far away object appear.", 0)] + public BindableFloat MaxDepth { get; } = new BindableFloat(100) + { + Precision = 10, + MinValue = 50, + MaxValue = 200 + }; + + [SettingSource("Show Approach Circles", "Whether approach circles should be visible.", 1)] + public BindableBool ShowApproachCircles { get; } = new BindableBool(true); + + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state); + + protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => applyTransform(hitObject, state); + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public override void ApplyToDrawableHitObject(DrawableHitObject dho) + { + base.ApplyToDrawableHitObject(dho); + + switch (dho) + { + case DrawableSliderHead: + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + return; + + case DrawableHitCircle: + case DrawableSlider: + dho.Anchor = Anchor.Centre; + break; + } + } + + private void applyTransform(DrawableHitObject drawable, ArmedState state) + { + switch (drawable) + { + case DrawableSliderHead head: + if (!ShowApproachCircles.Value) + { + var hitObject = (OsuHitObject)drawable.HitObject; + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + + using (head.BeginAbsoluteSequence(appearTime)) + head.ApproachCircle.Hide(); + } + + break; + + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + return; + + case DrawableHitCircle circle: + + if (!ShowApproachCircles.Value) + { + var hitObject = (OsuHitObject)drawable.HitObject; + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + + using (circle.BeginAbsoluteSequence(appearTime)) + circle.ApproachCircle.Hide(); + } + + setStartPosition(drawable); + break; + + case DrawableSlider: + setStartPosition(drawable); + break; + } + } + + private void setStartPosition(DrawableHitObject drawable) + { + var hitObject = (OsuHitObject)drawable.HitObject; + + float d = mappedDepth(MaxDepth.Value); + + using (drawable.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) + { + drawable.MoveTo(positionAtDepth(d, hitObject.Position)); + drawable.ScaleTo(new Vector2(d)); + } + } + + public void Update(Playfield playfield) + { + double time = playfield.Time.Current; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + switch (drawable) + { + case DrawableSliderHead: + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + continue; + + case DrawableHitCircle: + case DrawableSlider: + var hitObject = (OsuHitObject)drawable.HitObject; + + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + double moveDuration = hitObject.TimePreempt; + float z = time > appearTime + moveDuration ? 0 : (MaxDepth.Value - (float)((time - appearTime) / moveDuration * MaxDepth.Value)); + + float d = mappedDepth(z); + drawable.Position = positionAtDepth(d, hitObject.Position); + drawable.Scale = new Vector2(d); + break; + } + } + } + + private static float mappedDepth(float depth) => 100 / (depth - camera_position.Z); + + private static Vector2 positionAtDepth(float mappedDepth, Vector2 positionAtZeroDepth) + { + return (positionAtZeroDepth - camera_position.Xy) * mappedDepth; + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index aaf0ab41a0..50e6a5b572 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -211,7 +211,8 @@ namespace osu.Game.Rulesets.Osu new ModAdaptiveSpeed(), new OsuModFreezeFrame(), new OsuModBubbles(), - new OsuModSynesthesia() + new OsuModSynesthesia(), + new OsuModDepth() }; case ModType.System: From cf6e50f73c5f0fabd35141602187981c86842a44 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 05:07:40 +0300 Subject: [PATCH 002/139] Add header and fix typo --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 9c0e34d0c8..b2d52aef42 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using System; using System.Linq; using osu.Framework.Bindables; @@ -27,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); - [SettingSource("Maximum Depth", "How far away object appear.", 0)] + [SettingSource("Maximum depth", "How far away objects appear.", 0)] public BindableFloat MaxDepth { get; } = new BindableFloat(100) { Precision = 10, From 937689ee6bb4eeee18b007ea153b36e5c8e6a961 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 05:39:44 +0300 Subject: [PATCH 003/139] Add OsuModDepth as incompatable to other mods --- osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 10 files changed, 11 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs index f1197ce0cd..06cb9c3419 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Burn the notes into your memory."; //Alters the transforms of the approach circles, breaking the effects of these mods. - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth) }).ToArray(); public override ModType Type => ModType.Fun; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index dd2befef4e..6dc0d5d522 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders."; public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) }; public const double FADE_IN_DURATION_MULTIPLIER = 0.4; public const double FADE_OUT_DURATION_MULTIPLIER = 0.3; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index c8c4cd6a14..befee4af5a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "No need to chase the circles – your cursor is a magnet!"; public override double ScoreMultiplier => 0.5; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel), typeof(OsuModBubbles), typeof(OsuModDepth) }; [SettingSource("Attraction strength", "How strong the pull is.", 0)] public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 6f1206382a..1df344648a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected virtual float EndScale => 1; - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModObjectScaleTween), typeof(OsuModDepth) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 28d459cedb..91feb33931 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Hit objects run away!"; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModBubbles), typeof(OsuModDepth) }; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index b0533d0cfa..59a1342480 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods // todo: this mod needs to be incompatible with "hidden" due to forcing the circle to remain opaque, // further implementation will be required for supporting that. - public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden) }; + public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModObjectScaleTween), typeof(OsuModHidden), typeof(OsuModDepth) }; private const int rotate_offset = 360; private const float rotate_starting_width = 2; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs index 77cf340b95..a5846efdfe 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs @@ -47,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Mods typeof(OsuModRandom), typeof(OsuModSpunOut), typeof(OsuModStrictTracking), - typeof(OsuModSuddenDeath) + typeof(OsuModSuddenDeath), + typeof(OsuModDepth) }).ToArray(); [SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))] diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 25d05a88a8..9671f53bea 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override LocalisableString Description => "Put your faith in the approach circles..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) }; + public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModDepth) }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 92a499e735..b6907af119 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(OsuModDepth) }).ToArray(); private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index a45338d91f..d14a821541 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override LocalisableString Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModDepth) }; private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles From ebcde63caa4aef0696c977c04bbd5d3e2f2ed5da Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 17:13:47 +0300 Subject: [PATCH 004/139] Don't override hitobjects anchor --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index b2d52aef42..5ae9fc4fac 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -53,25 +53,6 @@ namespace osu.Game.Rulesets.Osu.Mods (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); } - public override void ApplyToDrawableHitObject(DrawableHitObject dho) - { - base.ApplyToDrawableHitObject(dho); - - switch (dho) - { - case DrawableSliderHead: - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - return; - - case DrawableHitCircle: - case DrawableSlider: - dho.Anchor = Anchor.Centre; - break; - } - } - private void applyTransform(DrawableHitObject drawable, ArmedState state) { switch (drawable) @@ -160,7 +141,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static Vector2 positionAtDepth(float mappedDepth, Vector2 positionAtZeroDepth) { - return (positionAtZeroDepth - camera_position.Xy) * mappedDepth; + return (positionAtZeroDepth - camera_position.Xy) * mappedDepth + camera_position.Xy; } } } From b90000f7b70f2840d6ef683aacb6043f78add0e3 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 3 Dec 2023 17:15:52 +0300 Subject: [PATCH 005/139] Simplify objects depth calculation --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 5ae9fc4fac..f2ea8b9698 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -126,8 +127,7 @@ namespace osu.Game.Rulesets.Osu.Mods var hitObject = (OsuHitObject)drawable.HitObject; double appearTime = hitObject.StartTime - hitObject.TimePreempt; - double moveDuration = hitObject.TimePreempt; - float z = time > appearTime + moveDuration ? 0 : (MaxDepth.Value - (float)((time - appearTime) / moveDuration * MaxDepth.Value)); + float z = Interpolation.ValueAt(Math.Clamp(time, appearTime, hitObject.StartTime), MaxDepth.Value, 0f, appearTime, hitObject.StartTime); float d = mappedDepth(z); drawable.Position = positionAtDepth(d, hitObject.Position); From ec5c7d7830dfac2f37a8e3d6cbc14967c8473f21 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 4 Dec 2023 09:43:09 +0300 Subject: [PATCH 006/139] Add deceleration and rework depth handling --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 105 ++++++++++------------ 1 file changed, 49 insertions(+), 56 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index f2ea8b9698..5054c75e97 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Utils; @@ -30,6 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray(); private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); + private readonly float minDepth = depthForScale(1.5f); [SettingSource("Maximum depth", "How far away objects appear.", 0)] public BindableFloat MaxDepth { get; } = new BindableFloat(100) @@ -58,25 +58,7 @@ namespace osu.Game.Rulesets.Osu.Mods { switch (drawable) { - case DrawableSliderHead head: - if (!ShowApproachCircles.Value) - { - var hitObject = (OsuHitObject)drawable.HitObject; - double appearTime = hitObject.StartTime - hitObject.TimePreempt; - - using (head.BeginAbsoluteSequence(appearTime)) - head.ApproachCircle.Hide(); - } - - break; - - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - return; - case DrawableHitCircle circle: - if (!ShowApproachCircles.Value) { var hitObject = (OsuHitObject)drawable.HitObject; @@ -86,25 +68,7 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - setStartPosition(drawable); break; - - case DrawableSlider: - setStartPosition(drawable); - break; - } - } - - private void setStartPosition(DrawableHitObject drawable) - { - var hitObject = (OsuHitObject)drawable.HitObject; - - float d = mappedDepth(MaxDepth.Value); - - using (drawable.BeginAbsoluteSequence(hitObject.StartTime - hitObject.TimePreempt)) - { - drawable.MoveTo(positionAtDepth(d, hitObject.Position)); - drawable.ScaleTo(new Vector2(d)); } } @@ -114,34 +78,63 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - switch (drawable) - { - case DrawableSliderHead: - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - continue; + if (drawable is not DrawableOsuHitObject d) + continue; + switch (d) + { case DrawableHitCircle: case DrawableSlider: - var hitObject = (OsuHitObject)drawable.HitObject; - - double appearTime = hitObject.StartTime - hitObject.TimePreempt; - float z = Interpolation.ValueAt(Math.Clamp(time, appearTime, hitObject.StartTime), MaxDepth.Value, 0f, appearTime, hitObject.StartTime); - - float d = mappedDepth(z); - drawable.Position = positionAtDepth(d, hitObject.Position); - drawable.Scale = new Vector2(d); + processObject(time, d); break; } } } - private static float mappedDepth(float depth) => 100 / (depth - camera_position.Z); - - private static Vector2 positionAtDepth(float mappedDepth, Vector2 positionAtZeroDepth) + private void processObject(double time, DrawableOsuHitObject drawable) { - return (positionAtZeroDepth - camera_position.Xy) * mappedDepth + camera_position.Xy; + var hitObject = drawable.HitObject; + + double baseSpeed = MaxDepth.Value / hitObject.TimePreempt; + double hitObjectDuration = hitObject is Slider s ? s.Duration : 0.0; + double offsetAfterStartTime = hitObjectDuration + hitObject.MaximumJudgementOffset + 500; + double slowSpeed = -minDepth / offsetAfterStartTime; + + float decelerationDistance = MaxDepth.Value * 0.2f; + double decelerationTime = (slowSpeed - baseSpeed) * 2 * decelerationDistance / (slowSpeed * slowSpeed - baseSpeed * baseSpeed); + + float z; + + if (time < hitObject.StartTime - decelerationTime) + { + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); + z = Interpolation.ValueAt(Math.Max(time, appearTime), fullDistance, decelerationDistance, appearTime, hitObject.StartTime - decelerationTime); + } + else if (time < hitObject.StartTime) + { + double timeOffset = time - (hitObject.StartTime - decelerationTime); + double deceleration = (slowSpeed - baseSpeed) / decelerationTime; + z = decelerationDistance - (float)(baseSpeed * timeOffset + deceleration * timeOffset * timeOffset * 0.5); + } + else + { + double endTime = hitObject.StartTime + offsetAfterStartTime; + z = Interpolation.ValueAt(Math.Min(time, endTime), 0f, minDepth, hitObject.StartTime, endTime); + } + + float scale = scaleForDepth(z); + drawable.Position = positionAtDepth(scale, hitObject.Position); + drawable.Scale = new Vector2(scale); + } + + private static float scaleForDepth(float depth) => 100 / (depth - camera_position.Z); + + private static float depthForScale(float scale) => 100 / scale + camera_position.Z; + + private static Vector2 positionAtDepth(float scale, Vector2 positionAtZeroDepth) + { + return (positionAtZeroDepth - camera_position.Xy) * scale + camera_position.Xy; } } } From 68907fe1ba93fec9b513c1c6e6072567e7913bb5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 5 Dec 2023 02:48:11 +0300 Subject: [PATCH 007/139] Cleanup pass --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 34 +++++++++++------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 5054c75e97..18d4fef5e8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; @@ -78,30 +77,29 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - if (drawable is not DrawableOsuHitObject d) - continue; - - switch (d) + switch (drawable) { - case DrawableHitCircle: - case DrawableSlider: - processObject(time, d); + case DrawableHitCircle circle: + processObject(time, circle, 0); + break; + + case DrawableSlider slider: + processObject(time, slider, slider.HitObject.Duration); break; } } } - private void processObject(double time, DrawableOsuHitObject drawable) + private void processObject(double time, DrawableOsuHitObject drawable, double duration) { var hitObject = drawable.HitObject; double baseSpeed = MaxDepth.Value / hitObject.TimePreempt; - double hitObjectDuration = hitObject is Slider s ? s.Duration : 0.0; - double offsetAfterStartTime = hitObjectDuration + hitObject.MaximumJudgementOffset + 500; - double slowSpeed = -minDepth / offsetAfterStartTime; + double offsetAfterStartTime = duration + hitObject.MaximumJudgementOffset + 500; + double slowSpeed = Math.Min(-minDepth / offsetAfterStartTime, baseSpeed); - float decelerationDistance = MaxDepth.Value * 0.2f; - double decelerationTime = (slowSpeed - baseSpeed) * 2 * decelerationDistance / (slowSpeed * slowSpeed - baseSpeed * baseSpeed); + double decelerationTime = hitObject.TimePreempt * 0.2; + float decelerationDistance = (float)(decelerationTime * (baseSpeed + slowSpeed) * 0.5); float z; @@ -109,7 +107,7 @@ namespace osu.Game.Rulesets.Osu.Mods { double appearTime = hitObject.StartTime - hitObject.TimePreempt; float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); - z = Interpolation.ValueAt(Math.Max(time, appearTime), fullDistance, decelerationDistance, appearTime, hitObject.StartTime - decelerationTime); + z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed); } else if (time < hitObject.StartTime) { @@ -120,11 +118,11 @@ namespace osu.Game.Rulesets.Osu.Mods else { double endTime = hitObject.StartTime + offsetAfterStartTime; - z = Interpolation.ValueAt(Math.Min(time, endTime), 0f, minDepth, hitObject.StartTime, endTime); + z = -(float)((Math.Min(time, endTime) - hitObject.StartTime) * slowSpeed); } float scale = scaleForDepth(z); - drawable.Position = positionAtDepth(scale, hitObject.Position); + drawable.Position = toPlayfieldPosition(scale, hitObject.Position); drawable.Scale = new Vector2(scale); } @@ -132,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Mods private static float depthForScale(float scale) => 100 / scale + camera_position.Z; - private static Vector2 positionAtDepth(float scale, Vector2 positionAtZeroDepth) + private static Vector2 toPlayfieldPosition(float scale, Vector2 positionAtZeroDepth) { return (positionAtZeroDepth - camera_position.Xy) * scale + camera_position.Xy; } From 594ea4da5f5324bb4cce86b071acff13a9a1cb1d Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 5 Dec 2023 16:00:20 +0300 Subject: [PATCH 008/139] Apply suggested behaviour --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 18d4fef5e8..16517a2f36 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -103,13 +103,7 @@ namespace osu.Game.Rulesets.Osu.Mods float z; - if (time < hitObject.StartTime - decelerationTime) - { - double appearTime = hitObject.StartTime - hitObject.TimePreempt; - float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); - z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed); - } - else if (time < hitObject.StartTime) + if (time < hitObject.StartTime) { double timeOffset = time - (hitObject.StartTime - decelerationTime); double deceleration = (slowSpeed - baseSpeed) / decelerationTime; From 160edcd2707d64aede30385eb9bf59ef4641fb7b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Dec 2023 07:09:46 +0300 Subject: [PATCH 009/139] Move objects at a constant speed whenever possible --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 54 ++++++++++++++++++----- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 16517a2f36..0defebc1c3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -27,8 +27,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModFreezeFrame), typeof(ModWithVisibilityAdjustment) }).ToArray(); - private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -100); - private readonly float minDepth = depthForScale(1.5f); + private static readonly Vector3 camera_position = new Vector3(OsuPlayfield.BASE_SIZE.X * 0.5f, OsuPlayfield.BASE_SIZE.Y * 0.5f, -200); + private readonly float sliderMinDepth = depthForScale(1.5f); // Depth at which slider's scale will be 1.5f [SettingSource("Maximum depth", "How far away objects appear.", 0)] public BindableFloat MaxDepth { get; } = new BindableFloat(100) @@ -80,30 +80,60 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - processObject(time, circle, 0); + processHitObject(time, circle); break; case DrawableSlider slider: - processObject(time, slider, slider.HitObject.Duration); + processSlider(time, slider); break; } } } - private void processObject(double time, DrawableOsuHitObject drawable, double duration) + private void processHitObject(double time, DrawableOsuHitObject drawable) { var hitObject = drawable.HitObject; + // Circles are always moving at the constant speed. They'll fade out before reaching the camera even at extreme conditions (AR 11, max depth). + double speed = MaxDepth.Value / hitObject.TimePreempt; + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + float z = MaxDepth.Value - (float)((Math.Max(time, appearTime) - appearTime) * speed); + + float scale = scaleForDepth(z); + drawable.Position = toPlayfieldPosition(scale, hitObject.Position); + drawable.Scale = new Vector2(scale); + } + + private void processSlider(double time, DrawableSlider drawableSlider) + { + var hitObject = drawableSlider.HitObject; + double baseSpeed = MaxDepth.Value / hitObject.TimePreempt; - double offsetAfterStartTime = duration + hitObject.MaximumJudgementOffset + 500; - double slowSpeed = Math.Min(-minDepth / offsetAfterStartTime, baseSpeed); + double appearTime = hitObject.StartTime - hitObject.TimePreempt; + + // Allow slider to move at a constant speed if its scale at the end time will be lower than 1.5f + float zEnd = MaxDepth.Value - (float)((Math.Max(hitObject.StartTime + hitObject.Duration, appearTime) - appearTime) * baseSpeed); + + if (zEnd > sliderMinDepth) + { + processHitObject(time, drawableSlider); + return; + } + + double offsetAfterStartTime = hitObject.Duration + 500; + double slowSpeed = Math.Min(-sliderMinDepth / offsetAfterStartTime, baseSpeed); double decelerationTime = hitObject.TimePreempt * 0.2; float decelerationDistance = (float)(decelerationTime * (baseSpeed + slowSpeed) * 0.5); float z; - if (time < hitObject.StartTime) + if (time < hitObject.StartTime - decelerationTime) + { + float fullDistance = decelerationDistance + (float)(baseSpeed * (hitObject.TimePreempt - decelerationTime)); + z = fullDistance - (float)((Math.Max(time, appearTime) - appearTime) * baseSpeed); + } + else if (time < hitObject.StartTime) { double timeOffset = time - (hitObject.StartTime - decelerationTime); double deceleration = (slowSpeed - baseSpeed) / decelerationTime; @@ -116,13 +146,13 @@ namespace osu.Game.Rulesets.Osu.Mods } float scale = scaleForDepth(z); - drawable.Position = toPlayfieldPosition(scale, hitObject.Position); - drawable.Scale = new Vector2(scale); + drawableSlider.Position = toPlayfieldPosition(scale, hitObject.Position); + drawableSlider.Scale = new Vector2(scale); } - private static float scaleForDepth(float depth) => 100 / (depth - camera_position.Z); + private static float scaleForDepth(float depth) => -camera_position.Z / Math.Max(1f, depth - camera_position.Z); - private static float depthForScale(float scale) => 100 / scale + camera_position.Z; + private static float depthForScale(float scale) => -camera_position.Z / scale + camera_position.Z; private static Vector2 toPlayfieldPosition(float scale, Vector2 positionAtZeroDepth) { From b0878e36cf39baeb92da9b646e2a47c62acf7891 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 6 Dec 2023 10:30:21 +0300 Subject: [PATCH 010/139] Fix stacks having incorrect position --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index 0defebc1c3..f71acf95b8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Mods float z = MaxDepth.Value - (float)((Math.Max(time, appearTime) - appearTime) * speed); float scale = scaleForDepth(z); - drawable.Position = toPlayfieldPosition(scale, hitObject.Position); + drawable.Position = toPlayfieldPosition(scale, hitObject.StackedPosition); drawable.Scale = new Vector2(scale); } @@ -146,7 +146,7 @@ namespace osu.Game.Rulesets.Osu.Mods } float scale = scaleForDepth(z); - drawableSlider.Position = toPlayfieldPosition(scale, hitObject.Position); + drawableSlider.Position = toPlayfieldPosition(scale, hitObject.StackedPosition); drawableSlider.Scale = new Vector2(scale); } From 3f6dad5502d6aec67d9e7522fdb69d21299d5058 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 01:33:50 +0900 Subject: [PATCH 011/139] Use classic HP values for non-classic osu! HP drain --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 + .../Scoring/OsuHealthProcessor.cs | 68 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index e53f20277b..35cbfa3790 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Osu public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor(); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new OsuHealthProcessor(drainStartTime); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new OsuBeatmapConverter(beatmap, this); public override IBeatmapProcessor CreateBeatmapProcessor(IBeatmap beatmap) => new OsuBeatmapProcessor(beatmap); diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs new file mode 100644 index 0000000000..784d3b934d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + public partial class OsuHealthProcessor : DrainingHealthProcessor + { + public OsuHealthProcessor(double drainStartTime, double drainLenience = 0) + : base(drainStartTime, drainLenience) + { + } + + protected override double GetHealthIncreaseFor(JudgementResult result) + { + switch (result.Type) + { + case HitResult.SmallTickMiss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14); + + case HitResult.LargeTickMiss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.02, -0.075, -0.14); + + case HitResult.Miss: + return IBeatmapDifficultyInfo.DifficultyRange(Beatmap.Difficulty.DrainRate, -0.03, -0.125, -0.2); + + case HitResult.SmallTickHit: + // When classic slider mechanics are enabled, this result comes from the tail. + return 0.02; + + case HitResult.LargeTickHit: + switch (result.HitObject) + { + case SliderTick: + return 0.015; + + case SliderHeadCircle: + case SliderTailCircle: + case SliderRepeat: + return 0.02; + } + + break; + + case HitResult.Meh: + return 0.002; + + case HitResult.Ok: + return 0.011; + + case HitResult.Great: + return 0.03; + + case HitResult.SmallBonus: + return 0.0085; + + case HitResult.LargeBonus: + return 0.01; + } + + return base.GetHealthIncreaseFor(result); + } + } +} From b31b9e96d0cf016e4f7b892116fd6951977a7853 Mon Sep 17 00:00:00 2001 From: Simon G <45692977+jeenyuhs@users.noreply.github.com> Date: Fri, 22 Dec 2023 03:04:48 +0100 Subject: [PATCH 012/139] adjust beatmap length and drain based on rate changing mods --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 25 ++++++++++++++------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 2613857998..c69cd6ead6 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -161,6 +161,7 @@ namespace osu.Game.Screens.Select private ILocalisedBindableString artistBinding; private FillFlowContainer infoLabelContainer; private Container bpmLabelContainer; + private Container lengthLabelContainer; private readonly WorkingBeatmap working; private readonly RulesetInfo ruleset; @@ -341,10 +342,10 @@ namespace osu.Game.Screens.Select { settingChangeTracker?.Dispose(); - refreshBPMLabel(); + refreshBPMAndLengthLabel(); settingChangeTracker = new ModSettingChangeTracker(m.NewValue); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + settingChangeTracker.SettingChanged += _ => refreshBPMAndLengthLabel(); }, true); } @@ -370,12 +371,10 @@ namespace osu.Game.Screens.Select infoLabelContainer.Children = new Drawable[] { - new InfoLabel(new BeatmapStatistic + lengthLabelContainer = new Container { - Name = BeatmapsetsStrings.ShowStatsTotalLength(playableBeatmap.CalculateDrainLength().ToFormattedDuration()), - CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(), - }), + AutoSizeAxes = Axes.Both, + }, bpmLabelContainer = new Container { AutoSizeAxes = Axes.Both, @@ -394,7 +393,7 @@ namespace osu.Game.Screens.Select } } - private void refreshBPMLabel() + private void refreshBPMAndLengthLabel() { var beatmap = working.Beatmap; @@ -420,6 +419,16 @@ namespace osu.Game.Screens.Select CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Bpm), Content = labelText }); + + double drainLength = Math.Round(beatmap.CalculateDrainLength() / rate); + double hitLength = Math.Round(beatmap.BeatmapInfo.Length / rate); + + lengthLabelContainer.Child = new InfoLabel(new BeatmapStatistic + { + Name = BeatmapsetsStrings.ShowStatsTotalLength(drainLength.ToFormattedDuration()), + CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), + Content = hitLength.ToFormattedDuration().ToString(), + }); } private Drawable getMapper(BeatmapMetadata metadata) From 9c35e250368ca3d777e1b2f4394310008839d3b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 13:38:52 +0900 Subject: [PATCH 013/139] Add failing tests --- .../Mods/TestSceneManiaModPerfect.cs | 29 +++++++++++++++++++ .../Mods/TestSceneOsuModPerfect.cs | 29 +++++++++++++++++++ osu.Game/Tests/Visual/ModPerfectTestScene.cs | 8 ++--- osu.Game/Tests/Visual/ModTestScene.cs | 18 ++++++------ 4 files changed, 71 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 97a6ee28f4..a734979bf6 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -1,9 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods @@ -24,5 +29,29 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); + + [Test] + public void TestBreakOnHoldNote() => CreateModTest(new ModTestData + { + Mod = new ManiaModPerfect(), + PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HoldNote + { + StartTime = 1000, + EndTime = 3000, + }, + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1000, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index 26c4133bc4..7030061e0e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -1,11 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using NUnit.Framework; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; using osu.Game.Tests.Visual; using osuTK; @@ -50,5 +54,30 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss); } + + [Test] + public void TestMissSliderTail() => CreateModTest(new ModTestData + { + Mod = new OsuModPerfect(), + PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + Position = new Vector2(256, 192), + StartTime = 1000, + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton), + new OsuReplayFrame(1001, new Vector2(256, 192)), + } + }); } } diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModPerfectTestScene.cs index 164faa16aa..c6f4a27ec4 100644 --- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs +++ b/osu.Game/Tests/Visual/ModPerfectTestScene.cs @@ -29,12 +29,12 @@ namespace osu.Game.Tests.Visual PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss) }); - protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new PerfectModTestPlayer(); + protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new PerfectModTestPlayer(CurrentTestData, AllowFail); - private partial class PerfectModTestPlayer : TestPlayer + protected partial class PerfectModTestPlayer : ModTestPlayer { - public PerfectModTestPlayer() - : base(showResults: false) + public PerfectModTestPlayer(ModTestData data, bool allowFail) + : base(data, allowFail) { } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index aa5b506343..c2ebcdefac 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -20,35 +20,35 @@ namespace osu.Game.Tests.Visual { protected sealed override bool HasCustomSteps => true; - private ModTestData currentTestData; + protected ModTestData CurrentTestData { get; private set; } protected void CreateModTest(ModTestData testData) => CreateTest(() => { - AddStep("set test data", () => currentTestData = testData); + AddStep("set test data", () => CurrentTestData = testData); }); public override void TearDownSteps() { AddUntilStep("test passed", () => { - if (currentTestData == null) + if (CurrentTestData == null) return true; - return currentTestData.PassCondition?.Invoke() ?? false; + return CurrentTestData.PassCondition?.Invoke() ?? false; }); base.TearDownSteps(); } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => CurrentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) { var mods = new List(SelectedMods.Value); - if (currentTestData.Mods != null) - mods.AddRange(currentTestData.Mods); - if (currentTestData.Autoplay) + if (CurrentTestData.Mods != null) + mods.AddRange(CurrentTestData.Mods); + if (CurrentTestData.Autoplay) mods.Add(ruleset.GetAutoplayMod()); SelectedMods.Value = mods; @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Visual return CreateModPlayer(ruleset); } - protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(currentTestData, AllowFail); + protected virtual TestPlayer CreateModPlayer(Ruleset ruleset) => new ModTestPlayer(CurrentTestData, AllowFail); protected partial class ModTestPlayer : TestPlayer { From ea778c6e0aa6bf52ae5e12f287b0fb7f131348ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 13:39:41 +0900 Subject: [PATCH 014/139] Fix perfect/sudden death not working on slider tails --- osu.Game/Rulesets/Mods/ModPerfect.cs | 4 +++- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index 6f0bb7ad3b..0ba40ba070 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -28,7 +28,9 @@ namespace osu.Game.Rulesets.Mods } protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => result.Type.AffectsAccuracy() + => (isRelevantResult(result.Judgement.MinResult) || isRelevantResult(result.Judgement.MaxResult) || isRelevantResult(result.Type)) && result.Type != result.Judgement.MaxResult; + + private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo(); } } diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 4e4e8662e8..56420e5ff4 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => result.Type.AffectsCombo() + => (result.Judgement.MinResult.AffectsCombo() || result.Judgement.MaxResult.AffectsCombo() || result.Type.AffectsCombo()) && !result.IsHit; } } From 93efa98d9b8c1ce647a5baca6770f3c0c3e39755 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 16:19:36 +0900 Subject: [PATCH 015/139] Fix mania "Great" hits failing with perfect mod --- .../Mods/TestSceneManiaModPerfect.cs | 23 +++++++++++++++++++ .../Mods/ManiaModPerfect.cs | 15 ++++++++++++ 2 files changed, 38 insertions(+) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index a734979bf6..73d6fe357d 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -30,6 +30,29 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(true)] public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); + [Test] + public void TestGreatHit() => CreateModTest(new ModTestData + { + Mod = new ManiaModPerfect(), + PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(false), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Note + { + StartTime = 1000, + } + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1020, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); + [Test] public void TestBreakOnHoldNote() => CreateModTest(new ModTestData { diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs index 2e22e23dbd..b02a18c9f4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs @@ -1,11 +1,26 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModPerfect : ModPerfect { + protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) + { + if (!isRelevantResult(result.Judgement.MinResult) && !isRelevantResult(result.Judgement.MaxResult) && !isRelevantResult(result.Type)) + return false; + + // Mania allows imperfect "Great" hits without failing. + if (result.Judgement.MaxResult == HitResult.Perfect) + return result.Type < HitResult.Great; + + return result.Type != result.Judgement.MaxResult; + } + + private bool isRelevantResult(HitResult result) => result.AffectsAccuracy() || result.AffectsCombo(); } } From 88a5ba8167facffe4c7c401ca32ddf9f003bf4df Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 16:43:09 +0900 Subject: [PATCH 016/139] Add mania/osu sudden death mod tests --- .../Mods/TestSceneCatchModPerfect.cs | 2 +- .../Mods/TestSceneManiaModPerfect.cs | 6 +- .../Mods/TestSceneManiaModSuddenDeath.cs | 72 +++++++++++++++++ .../Mods/TestSceneOsuModPerfect.cs | 4 +- .../Mods/TestSceneOsuModSuddenDeath.cs | 77 +++++++++++++++++++ .../Mods/TestSceneTaikoModPerfect.cs | 2 +- ...tScene.cs => ModFailConditionTestScene.cs} | 14 ++-- 7 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs rename osu.Game/Tests/Visual/{ModPerfectTestScene.cs => ModFailConditionTestScene.cs} (74%) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 45e7d7aa28..7d539f91e4 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests.Mods { - public partial class TestSceneCatchModPerfect : ModPerfectTestScene + public partial class TestSceneCatchModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 73d6fe357d..51730e2b43 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -13,7 +13,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests.Mods { - public partial class TestSceneManiaModPerfect : ModPerfectTestScene + public partial class TestSceneManiaModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods public void TestGreatHit() => CreateModTest(new ModTestData { Mod = new ManiaModPerfect(), - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(false), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), Autoplay = false, Beatmap = new Beatmap { @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods public void TestBreakOnHoldNote() => CreateModTest(new ModTestData { Mod = new ManiaModPerfect(), - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, Autoplay = false, Beatmap = new Beatmap { diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs new file mode 100644 index 0000000000..619816a815 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModSuddenDeath.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests.Mods +{ + public partial class TestSceneManiaModSuddenDeath : ModFailConditionTestScene + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + + public TestSceneManiaModSuddenDeath() + : base(new ManiaModSuddenDeath()) + { + } + + [Test] + public void TestGreatHit() => CreateModTest(new ModTestData + { + Mod = new ManiaModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Note + { + StartTime = 1000, + } + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1020, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); + + [Test] + public void TestBreakOnHoldNote() => CreateModTest(new ModTestData + { + Mod = new ManiaModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true) && Player.Results.Count == 2, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HoldNote + { + StartTime = 1000, + EndTime = 3000, + }, + }, + }, + ReplayFrames = new List + { + new ManiaReplayFrame(1000, ManiaAction.Key1), + new ManiaReplayFrame(2000) + } + }); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index 7030061e0e..b01bbbfca1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -15,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public partial class TestSceneOsuModPerfect : ModPerfectTestScene + public partial class TestSceneOsuModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public void TestMissSliderTail() => CreateModTest(new ModTestData { Mod = new OsuModPerfect(), - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(true), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), Autoplay = false, Beatmap = new Beatmap { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs new file mode 100644 index 0000000000..ea048aaa6e --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSuddenDeath.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModSuddenDeath : ModFailConditionTestScene + { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + + public TestSceneOsuModSuddenDeath() + : base(new OsuModSuddenDeath()) + { + } + + [Test] + public void TestMissTail() => CreateModTest(new ModTestData + { + Mod = new OsuModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(false), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + Position = new Vector2(256, 192), + StartTime = 1000, + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(100, 0), }) + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton), + new OsuReplayFrame(1001, new Vector2(256, 192)), + } + }); + + [Test] + public void TestMissTick() => CreateModTest(new ModTestData + { + Mod = new OsuModSuddenDeath(), + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(true), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + Position = new Vector2(256, 192), + StartTime = 1000, + Path = new SliderPath(PathType.LINEAR, new[] { Vector2.Zero, new Vector2(200, 0), }) + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(1000, new Vector2(256, 192), OsuAction.LeftButton), + new OsuReplayFrame(1001, new Vector2(256, 192)), + } + }); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs index aed08f33e0..8a1157a7f8 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs @@ -10,7 +10,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests.Mods { - public partial class TestSceneTaikoModPerfect : ModPerfectTestScene + public partial class TestSceneTaikoModPerfect : ModFailConditionTestScene { protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset(); diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs similarity index 74% rename from osu.Game/Tests/Visual/ModPerfectTestScene.cs rename to osu.Game/Tests/Visual/ModFailConditionTestScene.cs index c6f4a27ec4..8f0dff055d 100644 --- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs +++ b/osu.Game/Tests/Visual/ModFailConditionTestScene.cs @@ -8,11 +8,11 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Tests.Visual { - public abstract partial class ModPerfectTestScene : ModTestScene + public abstract partial class ModFailConditionTestScene : ModTestScene { - private readonly ModPerfect mod; + private readonly ModFailCondition mod; - protected ModPerfectTestScene(ModPerfect mod) + protected ModFailConditionTestScene(ModFailCondition mod) { this.mod = mod; } @@ -26,14 +26,14 @@ namespace osu.Game.Tests.Visual HitObjects = { testData.HitObject } }, Autoplay = !shouldMiss, - PassCondition = () => ((PerfectModTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss) + PassCondition = () => ((ModFailConditionTestPlayer)Player).CheckFailed(shouldMiss && testData.FailOnMiss) }); - protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new PerfectModTestPlayer(CurrentTestData, AllowFail); + protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModFailConditionTestPlayer(CurrentTestData, AllowFail); - protected partial class PerfectModTestPlayer : ModTestPlayer + protected partial class ModFailConditionTestPlayer : ModTestPlayer { - public PerfectModTestPlayer(ModTestData data, bool allowFail) + public ModFailConditionTestPlayer(ModTestData data, bool allowFail) : base(data, allowFail) { } From 5703546d715687d58b832c25082386d35abadb09 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 16:43:17 +0900 Subject: [PATCH 017/139] Revert change to ModSuddenDeath --- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 56420e5ff4..4e4e8662e8 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModPerfect)).ToArray(); protected override bool FailCondition(HealthProcessor healthProcessor, JudgementResult result) - => (result.Judgement.MinResult.AffectsCombo() || result.Judgement.MaxResult.AffectsCombo() || result.Type.AffectsCombo()) + => result.Type.AffectsCombo() && !result.IsHit; } } From a0185508b76af5f650534d575226d27fe3fc1f21 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Dec 2023 17:55:06 +0900 Subject: [PATCH 018/139] Add basic consideration of density for HP drain --- .../Scoring/OsuHealthProcessor.cs | 4 ++ .../Scoring/DrainingHealthProcessor.cs | 55 +++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 784d3b934d..5ae5766cda 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -3,6 +3,8 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -15,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { } + protected override int? GetDensityGroup(HitObject hitObject) => (hitObject as IHasComboInformation)?.ComboIndex; + protected override double GetHealthIncreaseFor(JudgementResult result) { switch (result.Type) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index a2fc23ac2e..4f6f8598fb 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -61,7 +61,9 @@ namespace osu.Game.Rulesets.Scoring /// protected readonly double DrainLenience; - private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>(); + private readonly List healthIncreases = new List(); + private readonly Dictionary densityMultiplierByGroup = new Dictionary(); + private double gameplayEndTime; private double targetMinimumHealth; @@ -133,14 +135,33 @@ namespace osu.Game.Rulesets.Scoring { base.ApplyResultInternal(result); - if (!result.Type.IsBonus()) - healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result))); + if (IsSimulating && !result.Type.IsBonus()) + { + healthIncreases.Add(new HealthIncrease( + result.HitObject.GetEndTime() + result.TimeOffset, + GetHealthIncreaseFor(result), + GetDensityGroup(result.HitObject))); + } } + protected override double GetHealthIncreaseFor(JudgementResult result) => base.GetHealthIncreaseFor(result) * getDensityMultiplier(GetDensityGroup(result.HitObject)); + + private double getDensityMultiplier(int? group) + { + if (group == null) + return 1; + + return densityMultiplierByGroup.TryGetValue(group.Value, out double multiplier) ? multiplier : 1; + } + + protected virtual int? GetDensityGroup(HitObject hitObject) => null; + protected override void Reset(bool storeResults) { base.Reset(storeResults); + densityMultiplierByGroup.Clear(); + if (storeResults) DrainRate = ComputeDrainRate(); @@ -152,6 +173,24 @@ namespace osu.Game.Rulesets.Scoring if (healthIncreases.Count <= 1) return 0; + // Normalise the health gain during sections with higher densities. + (int group, double avgIncrease)[] avgIncreasesByGroup = healthIncreases + .Where(i => i.Group != null) + .GroupBy(i => i.Group) + .Select(g => ((int)g.Key!, g.Sum(i => i.Amount) / (g.Max(i => i.Time) - g.Min(i => i.Time) + 1))) + .ToArray(); + + if (avgIncreasesByGroup.Length > 1) + { + double overallAverageIncrease = avgIncreasesByGroup.Average(g => g.avgIncrease); + + foreach ((int group, double avgIncrease) in avgIncreasesByGroup) + { + // Reduce the health increase for groups that return more health than average. + densityMultiplierByGroup[group] = Math.Min(1, overallAverageIncrease / avgIncrease); + } + } + int adjustment = 1; double result = 1; @@ -165,8 +204,8 @@ namespace osu.Game.Rulesets.Scoring for (int i = 0; i < healthIncreases.Count; i++) { - double currentTime = healthIncreases[i].time; - double lastTime = i > 0 ? healthIncreases[i - 1].time : DrainStartTime; + double currentTime = healthIncreases[i].Time; + double lastTime = i > 0 ? healthIncreases[i - 1].Time : DrainStartTime; while (currentBreak < Beatmap.Breaks.Count && Beatmap.Breaks[currentBreak].EndTime <= currentTime) { @@ -177,10 +216,12 @@ namespace osu.Game.Rulesets.Scoring currentBreak++; } + double multiplier = getDensityMultiplier(healthIncreases[i].Group); + // Apply health adjustments currentHealth -= (currentTime - lastTime) * result; lowestHealth = Math.Min(lowestHealth, currentHealth); - currentHealth = Math.Min(1, currentHealth + healthIncreases[i].health); + currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount * multiplier); // Common scenario for when the drain rate is definitely too harsh if (lowestHealth < 0) @@ -198,5 +239,7 @@ namespace osu.Game.Rulesets.Scoring return result; } + + private record struct HealthIncrease(double Time, double Amount, int? Group); } } From 7e557152fb430219ab484107d592311e983094f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Dec 2023 12:40:24 +0100 Subject: [PATCH 019/139] Fix relax mod not considering full follow area radius when automatically holding sliders Closes https://github.com/ppy/osu/issues/25947. Regressed in https://github.com/ppy/osu/pull/25776 with the changes to `DrawableSliderBall`. I would have liked to include tests, but relax mod is a bit untestable, because it disengages completely in the presence of a replay: https://github.com/ppy/osu/blob/7e09164d7084265536570ac7036d7244934b651c/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs#L49-L58 Additionally, `RulesetInputManager` disengages completely from parent inputs when there is a replay active: https://github.com/ppy/osu/blob/7e09164d7084265536570ac7036d7244934b651c/osu.Game/Rulesets/UI/RulesetInputManager.cs#L116 which means there is really no easy way to control positional input while still having relax logic work. So I'm hoping the fix could be considered obvious enough to not require test coverage. --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- .../Objects/Drawables/SliderInputManager.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index aaa7c70a8d..40fadfb77e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (!slider.HeadCircle.IsHit) handleHitCircle(slider.HeadCircle); - requiresHold |= slider.Ball.IsHovered || h.IsHovered; + requiresHold |= slider.SliderInputManager.IsMouseInFollowArea(true); break; case DrawableSpinner spinner: diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs index 8aa982783e..95896c7c91 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/SliderInputManager.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected override void Update() { base.Update(); - updateTracking(isMouseInFollowArea(Tracking)); + updateTracking(IsMouseInFollowArea(Tracking)); } public void PostProcessHeadJudgement(DrawableSliderHead head) @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (!head.Judged || !head.Result.IsHit) return; - if (!isMouseInFollowArea(true)) + if (!IsMouseInFollowArea(true)) return; Debug.Assert(screenSpaceMousePosition != null); @@ -129,7 +129,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // If all ticks were hit so far, enable tracking the full extent. // If any ticks were missed, assume tracking would've broken at some point, and should only activate if the cursor is within the slider ball. // For the second case, this may be the last chance we have to enable tracking before other objects get judged, otherwise the same would normally happen via Update(). - updateTracking(allTicksInRange || isMouseInFollowArea(false)); + updateTracking(allTicksInRange || IsMouseInFollowArea(false)); } public void TryJudgeNestedObject(DrawableOsuHitObject nestedObject, double timeOffset) @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables /// Whether the mouse is currently in the follow area. /// /// Whether to test against the maximum area of the follow circle. - private bool isMouseInFollowArea(bool expanded) + public bool IsMouseInFollowArea(bool expanded) { if (screenSpaceMousePosition is not Vector2 pos) return false; From 6cb82310549a73ca2657efe65c9528da8367f1ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Dec 2023 13:22:26 +0100 Subject: [PATCH 020/139] Add test coverage for failure case --- .../Mods/TestSceneOsuModStrictTracking.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs new file mode 100644 index 0000000000..726b415977 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModStrictTracking.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModStrictTracking : OsuModTestScene + { + [Test] + public void TestSliderInput() => CreateModTest(new ModTestData + { + Mod = new OsuModStrictTracking(), + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Slider + { + StartTime = 1000, + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(0, 100)) + } + } + } + } + }, + ReplayFrames = new List + { + new OsuReplayFrame(0, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(500, new Vector2(200, 0), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200, 0)), + new OsuReplayFrame(1000, new Vector2(), OsuAction.LeftButton), + new OsuReplayFrame(1750, new Vector2(0, 100), OsuAction.LeftButton), + new OsuReplayFrame(1751, new Vector2(0, 100)), + }, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2 + }); + } +} From 30553dc7b839600145ac57bd789b7e9992c34dfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Dec 2023 13:38:07 +0100 Subject: [PATCH 021/139] Fix strict tracking mod forcefully missing tail before slider start time Closes https://github.com/ppy/osu/issues/25816. Regressed in https://github.com/ppy/osu/pull/25748. The reason this broke is that allowing the state of `Tracking` to change before the slider's start time to support the early hit scenario causes strict tracking to consider loss of tracking before the slider's start time as an actual miss, and thus forcefully miss the tail (see test case in 6cb82310549a73ca2657efe65c9528da8367f1ab). --- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index c465ab8732..2c9292c58b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Mods { if (e.NewValue || slider.Judged) return; + if (slider.Time.Current < slider.HitObject.StartTime) + return; + var tail = slider.NestedHitObjects.OfType().First(); if (!tail.Judged) From 01cf4ee15a8456ccc56381f138dfdf7f210de121 Mon Sep 17 00:00:00 2001 From: Simon G <45692977+jeenyuhs@users.noreply.github.com> Date: Fri, 22 Dec 2023 18:11:37 +0100 Subject: [PATCH 022/139] add test for length updates --- .../SongSelect/TestSceneBeatmapInfoWedge.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 7cd4f06bce..4e100a37dc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; @@ -194,6 +195,36 @@ namespace osu.Game.Tests.Visual.SongSelect }); } + [TestCase] + public void TestLengthUpdates() + { + IBeatmap beatmap = createTestBeatmap(new OsuRuleset().RulesetInfo); + double drain = beatmap.CalculateDrainLength(); + beatmap.BeatmapInfo.Length = drain; + + OsuModDoubleTime doubleTime = null; + + selectBeatmap(beatmap); + checkDisplayedLength(drain); + + AddStep("select DT", () => SelectedMods.Value = new[] { doubleTime = new OsuModDoubleTime() }); + checkDisplayedLength(Math.Round(drain / 1.5f)); + + AddStep("change DT rate", () => doubleTime.SpeedChange.Value = 2); + checkDisplayedLength(Math.Round(drain / 2)); + } + + private void checkDisplayedLength(double drain) + { + var displayedLength = drain.ToFormattedDuration(); + + AddUntilStep($"check map drain ({displayedLength})", () => + { + var label = infoWedge.DisplayedContent.ChildrenOfType().Single(l => l.Statistic.Name == BeatmapsetsStrings.ShowStatsTotalLength(displayedLength)); + return label.Statistic.Content == displayedLength.ToString(); + }); + } + private void setRuleset(RulesetInfo rulesetInfo) { Container containerBefore = null; From c5893f245ce7a89d1900dbb620390823702481fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 14:03:45 +0900 Subject: [PATCH 023/139] Change legacy version checks to account for users specifying incorrect versions --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs | 2 +- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index d8d86d1802..ef616ae964 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -160,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; - if (legacyVersion >= 2.0m) + if (legacyVersion > 1.0m) // legacy skins of version 2.0 and newer only apply very short fade out to the number piece. hitCircleText.FadeOut(legacy_fade_duration / 4); else diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index 68274ffa2d..b8fe2f8d06 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -67,7 +67,7 @@ namespace osu.Game.Skinning decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; - if (legacyVersion >= 2.0m) + if (legacyVersion > 1.0m) { this.MoveTo(new Vector2(0, -5)); this.MoveToOffset(new Vector2(0, 80), fade_out_delay + fade_out_length, Easing.In); From 9f34dfa2baa7e649d43ad74a444fa32df7b172e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 16:25:52 +0900 Subject: [PATCH 024/139] Add missing using statement --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs index 4e100a37dc..fd102da026 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapInfoWedge.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Extensions; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; From 8349cb7bbe4f30777f73cbf7c18f071997bd185c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 17:03:57 +0900 Subject: [PATCH 025/139] Fix hard crash when attempting to change folder location during a large import Closes https://github.com/ppy/osu/issues/26067. --- osu.Game/OsuGameBase.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 2d8024a45a..48548dc1ef 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -527,14 +527,21 @@ namespace osu.Game { ManualResetEventSlim readyToRun = new ManualResetEventSlim(); + bool success = false; + Scheduler.Add(() => { - realmBlocker = realm.BlockAllOperations("migration"); + try + { + realmBlocker = realm.BlockAllOperations("migration"); + success = true; + } + catch { } readyToRun.Set(); }, false); - if (!readyToRun.Wait(30000)) + if (!readyToRun.Wait(30000) || !success) throw new TimeoutException("Attempting to block for migration took too long."); bool? cleanupSucceded = (Storage as OsuStorage)?.Migrate(Host.GetStorage(path)); From 27a9dcc5a1a375e7d0591c90880ccb09fd8c0042 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 19:55:05 +0900 Subject: [PATCH 026/139] Add basic hotkey offset adjust support (via existing offset control) --- .../Input/Bindings/GlobalActionContainer.cs | 8 ++ .../GlobalActionKeyBindingStrings.cs | 10 +++ .../PlayerSettings/BeatmapOffsetControl.cs | 73 +++++++++++++------ 3 files changed, 68 insertions(+), 23 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 947cd5f54f..5a39c02185 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -160,6 +160,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Enter, GlobalAction.ToggleChatFocus), new KeyBinding(InputKey.F1, GlobalAction.SaveReplay), new KeyBinding(InputKey.F2, GlobalAction.ExportReplay), + new KeyBinding(InputKey.Plus, GlobalAction.IncreaseOffset), + new KeyBinding(InputKey.Minus, GlobalAction.DecreaseOffset), }; private static IEnumerable replayKeyBindings => new[] @@ -404,6 +406,12 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorToggleRotateControl))] EditorToggleRotateControl, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.IncreaseOffset))] + IncreaseOffset, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DecreaseOffset))] + DecreaseOffset } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 8356c480dd..ca27d0ff95 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -344,6 +344,16 @@ namespace osu.Game.Localisation /// public static LocalisableString ExportReplay => new TranslatableString(getKey(@"export_replay"), @"Export replay"); + /// + /// "Increase offset" + /// + public static LocalisableString IncreaseOffset => new TranslatableString(getKey(@"increase_offset"), @"Increase offset"); + + /// + /// "Decrease offset" + /// + public static LocalisableString DecreaseOffset => new TranslatableString(getKey(@"decrease_offset"), @"Decrease offset"); + /// /// "Toggle rotate control" /// diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 840077eb7f..b4bb35377d 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -9,6 +9,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -16,6 +18,7 @@ using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Input.Bindings; using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; @@ -26,7 +29,7 @@ using osuTK; namespace osu.Game.Screens.Play.PlayerSettings { - public partial class BeatmapOffsetControl : CompositeDrawable + public partial class BeatmapOffsetControl : CompositeDrawable, IKeyBindingHandler { public Bindable ReferenceScore { get; } = new Bindable(); @@ -88,28 +91,6 @@ namespace osu.Game.Screens.Play.PlayerSettings }; } - public partial class OffsetSliderBar : PlayerSliderBar - { - protected override Drawable CreateControl() => new CustomSliderBar(); - - protected partial class CustomSliderBar : SliderBar - { - public override LocalisableString TooltipText => - Current.Value == 0 - ? LocalisableString.Interpolate($@"{base.TooltipText} ms") - : LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}"); - - private LocalisableString getEarlyLateText(double value) - { - Debug.Assert(value != 0); - - return value > 0 - ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier - : BeatmapOffsetControlStrings.HitObjectsAppearLater; - } - } - } - protected override void LoadComplete() { base.LoadComplete(); @@ -243,5 +224,51 @@ namespace osu.Game.Screens.Play.PlayerSettings base.Dispose(isDisposing); beatmapOffsetSubscription?.Dispose(); } + + public bool OnPressed(KeyBindingPressEvent e) + { + double amount = e.AltPressed ? 1 : 5; + + switch (e.Action) + { + case GlobalAction.IncreaseOffset: + Current.Value += amount; + + return true; + + case GlobalAction.DecreaseOffset: + Current.Value -= amount; + + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + + public partial class OffsetSliderBar : PlayerSliderBar + { + protected override Drawable CreateControl() => new CustomSliderBar(); + + protected partial class CustomSliderBar : SliderBar + { + public override LocalisableString TooltipText => + Current.Value == 0 + ? LocalisableString.Interpolate($@"{base.TooltipText} ms") + : LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}"); + + private LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier + : BeatmapOffsetControlStrings.HitObjectsAppearLater; + } + } + } } } From 7e9522a722fd01ca872877e1e3b4bcfeb61c8a2b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 20:46:12 +0900 Subject: [PATCH 027/139] Allow external use of offset text explanation --- .../PlayerSettings/BeatmapOffsetControl.cs | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index b4bb35377d..80549343a5 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -233,12 +233,10 @@ namespace osu.Game.Screens.Play.PlayerSettings { case GlobalAction.IncreaseOffset: Current.Value += amount; - return true; case GlobalAction.DecreaseOffset: Current.Value -= amount; - return true; } @@ -249,25 +247,29 @@ namespace osu.Game.Screens.Play.PlayerSettings { } + public static LocalisableString GetOffsetExplanatoryText(double offset) + { + return offset == 0 + ? LocalisableString.Interpolate($@"{offset:0.0} ms") + : LocalisableString.Interpolate($@"{offset:0.0} ms {getEarlyLateText(offset)}"); + + LocalisableString getEarlyLateText(double value) + { + Debug.Assert(value != 0); + + return value > 0 + ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier + : BeatmapOffsetControlStrings.HitObjectsAppearLater; + } + } + public partial class OffsetSliderBar : PlayerSliderBar { protected override Drawable CreateControl() => new CustomSliderBar(); protected partial class CustomSliderBar : SliderBar { - public override LocalisableString TooltipText => - Current.Value == 0 - ? LocalisableString.Interpolate($@"{base.TooltipText} ms") - : LocalisableString.Interpolate($@"{base.TooltipText} ms {getEarlyLateText(Current.Value)}"); - - private LocalisableString getEarlyLateText(double value) - { - Debug.Assert(value != 0); - - return value > 0 - ? BeatmapOffsetControlStrings.HitObjectsAppearEarlier - : BeatmapOffsetControlStrings.HitObjectsAppearLater; - } + public override LocalisableString TooltipText => GetOffsetExplanatoryText(Current.Value); } } } From 6f11885d4bedc303859964ab1415a2f1959e9bda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 20:46:22 +0900 Subject: [PATCH 028/139] Add control to allow changing offset from gameplay --- .../Screens/Play/GameplayOffsetControl.cs | 104 ++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 6 + 2 files changed, 110 insertions(+) create mode 100644 osu.Game/Screens/Play/GameplayOffsetControl.cs diff --git a/osu.Game/Screens/Play/GameplayOffsetControl.cs b/osu.Game/Screens/Play/GameplayOffsetControl.cs new file mode 100644 index 0000000000..3f5a5bef2a --- /dev/null +++ b/osu.Game/Screens/Play/GameplayOffsetControl.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . 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.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Screens.Play.PlayerSettings; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Play +{ + /// + /// This provides the ability to change the offset while in gameplay. + /// Eventually this should be replaced with all settings from PlayerLoader being accessible from the game. + /// + internal partial class GameplayOffsetControl : VisibilityContainer + { + protected override bool StartHidden => true; + + public override bool PropagateNonPositionalInputSubTree => true; + + private BeatmapOffsetControl offsetControl = null!; + + private OsuTextFlowContainer text = null!; + + private ScheduledDelegate? hideOp; + + public GameplayOffsetControl() + { + AutoSizeAxes = Axes.Y; + Width = SettingsToolboxGroup.CONTAINER_WIDTH; + + Masking = true; + CornerRadius = 5; + + // Allow BeatmapOffsetControl to handle keyboard input. + AlwaysPresent = true; + + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; + + X = 100; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider? colourProvider) + { + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.8f, + Colour = colourProvider?.Background4 ?? Color4.Black, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(10), + Spacing = new Vector2(5), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + offsetControl = new BeatmapOffsetControl(), + text = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + } + } + }, + }; + + offsetControl.Current.BindValueChanged(val => + { + text.Text = BeatmapOffsetControl.GetOffsetExplanatoryText(val.NewValue); + Show(); + + hideOp?.Cancel(); + hideOp = Scheduler.AddDelayed(Hide, 500); + }); + } + + protected override void PopIn() + { + this.FadeIn(500, Easing.OutQuint) + .MoveToX(0, 500, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(500, Easing.InQuint) + .MoveToX(100, 500, Easing.InQuint); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c9251b0a78..c960ac357f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -461,6 +461,12 @@ namespace osu.Game.Screens.Play OnRetry = () => Restart(), OnQuit = () => PerformExit(true), }, + new GameplayOffsetControl + { + Margin = new MarginPadding(20), + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + } }, }; From a2e5f624789d852a23d555e12b5dd962ad6f7712 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 21:07:17 +0900 Subject: [PATCH 029/139] Fix user profile cover showing 1px line when contracted Addresses https://github.com/ppy/osu/discussions/26068. --- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 36bd8a5af5..c9e5068b2a 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -232,6 +232,14 @@ namespace osu.Game.Overlays.Profile.Header bool expanded = coverToggle.CoverExpanded.Value; cover.ResizeHeightTo(expanded ? 250 : 0, transition_duration, Easing.OutQuint); + + // Without this a very tiny slither of the cover will be visible even with a size of zero. + // Integer masking woes, no doubt. + if (expanded) + cover.FadeIn(transition_duration, Easing.OutQuint); + else + cover.FadeOut(transition_duration, Easing.InQuint); + avatar.ResizeTo(new Vector2(expanded ? 120 : content_height), transition_duration, Easing.OutQuint); avatar.TransformTo(nameof(avatar.CornerRadius), expanded ? 40f : 20f, transition_duration, Easing.OutQuint); flow.TransformTo(nameof(flow.Spacing), new Vector2(expanded ? 20f : 10f), transition_duration, Easing.OutQuint); From 007ea51e202d546e813725991221436066c3db74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 13:07:29 +0100 Subject: [PATCH 030/139] Add extra safety against returning negative total score in conversion operation --- .../StandardisedScoreMigrationTools.cs | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 380bf63e63..5cd2a2f29c 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -321,6 +321,8 @@ namespace osu.Game.Database double modMultiplier = score.Mods.Select(m => m.ScoreMultiplier).Aggregate(1.0, (c, n) => c * n); + long convertedTotalScore; + switch (score.Ruleset.OnlineID) { case 0: @@ -417,32 +419,42 @@ namespace osu.Game.Database double newComboScoreProportion = estimatedComboPortionInStandardisedScore / maximumAchievableComboPortionInStandardisedScore; - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 500000 * newComboScoreProportion * score.Accuracy + 500000 * Math.Pow(score.Accuracy, 5) + bonusProportion) * modMultiplier); + break; case 1: - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 250000 * comboProportion + 750000 * Math.Pow(score.Accuracy, 3.6) + bonusProportion) * modMultiplier); + break; case 2: - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 600000 * comboProportion + 400000 * score.Accuracy + bonusProportion) * modMultiplier); + break; case 3: - return (long)Math.Round(( + convertedTotalScore = (long)Math.Round(( 850000 * comboProportion + 150000 * Math.Pow(score.Accuracy, 2 + 2 * score.Accuracy) + bonusProportion) * modMultiplier); + break; default: - return score.TotalScore; + convertedTotalScore = score.TotalScore; + break; } + + if (convertedTotalScore < 0) + throw new InvalidOperationException($"Total score conversion operation returned invalid total of {convertedTotalScore}"); + + return convertedTotalScore; } public static double ComputeAccuracy(ScoreInfo scoreInfo) From 15a9740eb65f39dbc3e58c55ad146e3103d483a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 21:12:47 +0900 Subject: [PATCH 031/139] Change "cinema" mod to never fail Addresses https://github.com/ppy/osu/discussions/26032. --- osu.Game/Rulesets/Mods/ModCinema.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index ae661c5f25..a942bcc678 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mods } } - public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer + public class ModCinema : ModAutoplay, IApplicableToHUD, IApplicableToPlayer, IApplicableFailOverride { public override string Name => "Cinema"; public override string Acronym => "CN"; @@ -45,5 +45,9 @@ namespace osu.Game.Rulesets.Mods player.BreakOverlay.Hide(); } + + public bool PerformFail() => false; + + public bool RestartOnFail => false; } } From 644c9816739b2aadfcdb9b5e14d438a0cae81313 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Dec 2023 21:21:34 +0900 Subject: [PATCH 032/139] Fix "spectate" button not always being clickable in online users list --- osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index fe3151398f..37ea3f9c38 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -218,6 +219,7 @@ namespace osu.Game.Overlays.Dashboard { panel.Anchor = Anchor.TopCentre; panel.Origin = Anchor.TopCentre; + panel.CanSpectate.Value = playingUsers.Contains(user.Id); }); public partial class OnlineUserPanel : CompositeDrawable, IFilterable From 3a2ed3677b75bbbf8ec1683be7ecd8eb6e0a3e5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 13:25:20 +0100 Subject: [PATCH 033/139] Fix standardised score conversion failing for scores set with 0.0x mod mutliplier Closes https://github.com/ppy/osu/issues/26073. --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 8 ++++++-- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 5cd2a2f29c..66c816e796 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -312,8 +312,12 @@ namespace osu.Game.Database double legacyAccScore = maximumLegacyAccuracyScore * score.Accuracy; // We can not separate the ComboScore from the BonusScore, so we keep the bonus in the ratio. - double comboProportion = - ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore); + // Note that `maximumLegacyComboScore + maximumLegacyBonusScore` can actually be 0 + // when playing a beatmap with no bonus objects, with mods that have a 0.0x multiplier on stable (relax/autopilot). + // In such cases, just assume 0. + double comboProportion = maximumLegacyComboScore + maximumLegacyBonusScore > 0 + ? ((double)score.LegacyTotalScore - legacyAccScore) / (maximumLegacyComboScore + maximumLegacyBonusScore) + : 0; // We assume the bonus proportion only makes up the rest of the score that exceeds maximumLegacyBaseScore. long maximumLegacyBaseScore = maximumLegacyAccuracyScore + maximumLegacyComboScore; diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index dbd9a106df..cf0a7bd54f 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -35,9 +35,10 @@ namespace osu.Game.Scoring.Legacy /// 30000006: Fix edge cases in conversion after combo exponent introduction that lead to NaNs. Reconvert all scores. /// 30000007: Adjust osu!mania combo and accuracy portions and judgement scoring values. Reconvert all scores. /// 30000008: Add accuracy conversion. Reconvert all scores. + /// 30000009: Fix edge cases in conversion for scores which have 0.0x mod multiplier on stable. Reconvert all scores. /// /// - public const int LATEST_VERSION = 30000008; + public const int LATEST_VERSION = 30000009; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. From 32d8ee2d0c30b4922ac96bb172959ea375cd2e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 13:42:19 +0100 Subject: [PATCH 034/139] Add test coverage --- .../Online/TestSceneCurrentlyOnlineDisplay.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs index 7687cd195d..b696c5d8ca 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyOnlineDisplay.cs @@ -81,6 +81,21 @@ namespace osu.Game.Tests.Visual.Online AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence()); } + [Test] + public void TestUserWasPlayingBeforeWatchingUserPresence() + { + AddStep("User began playing", () => spectatorClient.SendStartPlay(streamingUser.Id, 0)); + AddStep("Begin watching user presence", () => metadataClient.BeginWatchingUserPresence()); + AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() })); + AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType().FirstOrDefault()?.User.Id == 2); + AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.True); + + AddStep("User finished playing", () => spectatorClient.SendEndPlay(streamingUser.Id)); + AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType().First().Enabled.Value, () => Is.False); + AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null)); + AddStep("End watching user presence", () => metadataClient.EndWatchingUserPresence()); + } + internal partial class TestUserLookupCache : UserLookupCache { private static readonly string[] usernames = From 0cbf594a8c151e3699ca15431e29d3dac96f2016 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 15:10:31 +0100 Subject: [PATCH 035/139] Make cinema mod incompatible with no fail --- osu.Game/Rulesets/Mods/ModCinema.cs | 2 +- osu.Game/Rulesets/Mods/ModNoFail.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index a942bcc678..dbb37e0af6 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods public override IconUsage? Icon => OsuIcon.ModCinema; public override LocalisableString Description => "Watch the video without visual distractions."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModAutoplay)).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(ModAutoplay), typeof(ModNoFail) }).ToArray(); public void ApplyToHUD(HUDOverlay overlay) { diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 0cf81bf4c9..cc451772b2 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mods public override ModType Type => ModType.DifficultyReduction; public override LocalisableString Description => "You can't fail, no matter what."; public override double ScoreMultiplier => 0.5; - public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition) }; + public override Type[] IncompatibleMods => new[] { typeof(ModFailCondition), typeof(ModCinema) }; private readonly Bindable showHealthBar = new Bindable(); From d1000b2e6c527f170e9fd1dd525d2ba296c31f17 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 23 Dec 2023 23:36:15 +0900 Subject: [PATCH 036/139] remove HP density --- .../Scoring/OsuHealthProcessor.cs | 2 - .../Scoring/DrainingHealthProcessor.cs | 42 ++----------------- 2 files changed, 3 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 5ae5766cda..7463bb565c 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -17,8 +17,6 @@ namespace osu.Game.Rulesets.Osu.Scoring { } - protected override int? GetDensityGroup(HitObject hitObject) => (hitObject as IHasComboInformation)?.ComboIndex; - protected override double GetHealthIncreaseFor(JudgementResult result) { switch (result.Type) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 4f6f8598fb..629a84ea62 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Scoring protected readonly double DrainLenience; private readonly List healthIncreases = new List(); - private readonly Dictionary densityMultiplierByGroup = new Dictionary(); private double gameplayEndTime; private double targetMinimumHealth; @@ -139,29 +138,14 @@ namespace osu.Game.Rulesets.Scoring { healthIncreases.Add(new HealthIncrease( result.HitObject.GetEndTime() + result.TimeOffset, - GetHealthIncreaseFor(result), - GetDensityGroup(result.HitObject))); + GetHealthIncreaseFor(result))); } } - protected override double GetHealthIncreaseFor(JudgementResult result) => base.GetHealthIncreaseFor(result) * getDensityMultiplier(GetDensityGroup(result.HitObject)); - - private double getDensityMultiplier(int? group) - { - if (group == null) - return 1; - - return densityMultiplierByGroup.TryGetValue(group.Value, out double multiplier) ? multiplier : 1; - } - - protected virtual int? GetDensityGroup(HitObject hitObject) => null; - protected override void Reset(bool storeResults) { base.Reset(storeResults); - densityMultiplierByGroup.Clear(); - if (storeResults) DrainRate = ComputeDrainRate(); @@ -173,24 +157,6 @@ namespace osu.Game.Rulesets.Scoring if (healthIncreases.Count <= 1) return 0; - // Normalise the health gain during sections with higher densities. - (int group, double avgIncrease)[] avgIncreasesByGroup = healthIncreases - .Where(i => i.Group != null) - .GroupBy(i => i.Group) - .Select(g => ((int)g.Key!, g.Sum(i => i.Amount) / (g.Max(i => i.Time) - g.Min(i => i.Time) + 1))) - .ToArray(); - - if (avgIncreasesByGroup.Length > 1) - { - double overallAverageIncrease = avgIncreasesByGroup.Average(g => g.avgIncrease); - - foreach ((int group, double avgIncrease) in avgIncreasesByGroup) - { - // Reduce the health increase for groups that return more health than average. - densityMultiplierByGroup[group] = Math.Min(1, overallAverageIncrease / avgIncrease); - } - } - int adjustment = 1; double result = 1; @@ -216,12 +182,10 @@ namespace osu.Game.Rulesets.Scoring currentBreak++; } - double multiplier = getDensityMultiplier(healthIncreases[i].Group); - // Apply health adjustments currentHealth -= (currentTime - lastTime) * result; lowestHealth = Math.Min(lowestHealth, currentHealth); - currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount * multiplier); + currentHealth = Math.Min(1, currentHealth + healthIncreases[i].Amount); // Common scenario for when the drain rate is definitely too harsh if (lowestHealth < 0) @@ -240,6 +204,6 @@ namespace osu.Game.Rulesets.Scoring return result; } - private record struct HealthIncrease(double Time, double Amount, int? Group); + private record struct HealthIncrease(double Time, double Amount); } } From 00090bc527989b6caa094a40caf67023aeb352b3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 23 Dec 2023 23:36:24 +0900 Subject: [PATCH 037/139] Add combo end bonus to HP --- .../Scoring/OsuHealthProcessor.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 7463bb565c..672a8c91ba 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; @@ -12,12 +14,68 @@ namespace osu.Game.Rulesets.Osu.Scoring { public partial class OsuHealthProcessor : DrainingHealthProcessor { + private ComboResult currentComboResult = ComboResult.Perfect; + public OsuHealthProcessor(double drainStartTime, double drainLenience = 0) : base(drainStartTime, drainLenience) { } protected override double GetHealthIncreaseFor(JudgementResult result) + { + if (IsSimulating) + return getHealthIncreaseFor(result); + + if (result.HitObject is not IHasComboInformation combo) + return getHealthIncreaseFor(result); + + if (combo.NewCombo) + currentComboResult = ComboResult.Perfect; + + switch (result.Type) + { + case HitResult.LargeTickMiss: + case HitResult.Ok: + setComboResult(ComboResult.Good); + break; + + case HitResult.Meh: + case HitResult.Miss: + setComboResult(ComboResult.None); + break; + } + + // The tail has a special IgnoreMiss judgement + if (result.HitObject is SliderTailCircle && !result.IsHit) + setComboResult(ComboResult.Good); + + if (combo.LastInCombo && result.Type.IsHit()) + { + switch (currentComboResult) + { + case ComboResult.Perfect: + return getHealthIncreaseFor(result) + 0.07; + + case ComboResult.Good: + return getHealthIncreaseFor(result) + 0.05; + + default: + return getHealthIncreaseFor(result) + 0.03; + } + } + + return getHealthIncreaseFor(result); + + void setComboResult(ComboResult comboResult) => currentComboResult = (ComboResult)Math.Min((int)currentComboResult, (int)comboResult); + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + currentComboResult = ComboResult.Perfect; + } + + private double getHealthIncreaseFor(JudgementResult result) { switch (result.Type) { From 8b11bcc6ea617bbb689b793443a90b977ae2d5e9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 24 Dec 2023 00:08:15 +0900 Subject: [PATCH 038/139] Remove unused using --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index 672a8c91ba..bd6281a7d3 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -4,7 +4,6 @@ using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; From 7437d21f498cca28084c7ed70341e714392e0690 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 24 Dec 2023 00:45:22 +0900 Subject: [PATCH 039/139] Adjust comment regarding slider tail --- osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs index bd6281a7d3..2eb257b3e6 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuHealthProcessor.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Osu.Scoring break; } - // The tail has a special IgnoreMiss judgement + // The slider tail has a special judgement that can't accurately be described above. if (result.HitObject is SliderTailCircle && !result.IsHit) setComboResult(ComboResult.Good); From 92b490f2e79d850980fa9e681f7c62c1ba1d5692 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 01:59:48 +0900 Subject: [PATCH 040/139] Don't bother with alt support for now --- osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 80549343a5..80003aeaba 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -227,7 +227,10 @@ namespace osu.Game.Screens.Play.PlayerSettings public bool OnPressed(KeyBindingPressEvent e) { - double amount = e.AltPressed ? 1 : 5; + // To match stable, this should adjust by 5 ms, or 1 ms when holding alt. + // But that is hard to make work with global actions due to the operating mode. + // Let's use the more precise as a default for now. + const double amount = 1; switch (e.Action) { From 72bec527fd06714bd0b88f56ff1d7538bbf37710 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 02:36:27 +0900 Subject: [PATCH 041/139] Add conditions to match stable offset adjust limitations --- .../PlayerSettings/BeatmapOffsetControl.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 80003aeaba..b0e7d08699 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -51,6 +51,12 @@ namespace osu.Game.Screens.Play.PlayerSettings [Resolved] private OsuColour colours { get; set; } = null!; + [Resolved] + private Player? player { get; set; } + + [Resolved] + private IGameplayClock? gameplayClock { get; set; } + private double lastPlayAverage; private double lastPlayBeatmapOffset; private HitEventTimingDistributionGraph? lastPlayGraph; @@ -227,6 +233,18 @@ namespace osu.Game.Screens.Play.PlayerSettings public bool OnPressed(KeyBindingPressEvent e) { + // General limitations to ensure players don't do anything too weird. + // These match stable for now. + if (player is SubmittingPlayer) + { + // TODO: the blocking conditions should probably display a message. + if (player?.IsBreakTime.Value == false && gameplayClock?.CurrentTime - gameplayClock?.StartTime > 10000) + return false; + + if (gameplayClock?.IsPaused.Value == true) + return false; + } + // To match stable, this should adjust by 5 ms, or 1 ms when holding alt. // But that is hard to make work with global actions due to the operating mode. // Let's use the more precise as a default for now. From 686b2a4394ac735ef309976910fed859f3f3b779 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 03:00:51 +0900 Subject: [PATCH 042/139] Disable positional interaction for now --- osu.Game/Screens/Play/GameplayOffsetControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/GameplayOffsetControl.cs b/osu.Game/Screens/Play/GameplayOffsetControl.cs index 3f5a5bef2a..2f0cb821ec 100644 --- a/osu.Game/Screens/Play/GameplayOffsetControl.cs +++ b/osu.Game/Screens/Play/GameplayOffsetControl.cs @@ -25,6 +25,9 @@ namespace osu.Game.Screens.Play public override bool PropagateNonPositionalInputSubTree => true; + // Disable interaction for now to avoid any funny business with slider bar dragging. + public override bool PropagatePositionalInputSubTree => false; + private BeatmapOffsetControl offsetControl = null!; private OsuTextFlowContainer text = null!; From 5b03dc8d0bb1d189dc68103971fccf41bfc2a008 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Dec 2023 03:20:42 +0900 Subject: [PATCH 043/139] Use a realm subscription to avoid overhead when hovering a toolbar button Addresses https://github.com/ppy/osu/discussions/26018. --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 26 +++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 08bcb6bd8a..344f7b2018 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -157,6 +157,15 @@ namespace osu.Game.Overlays.Toolbar }; } + [BackgroundDependencyLoader] + private void load() + { + if (Hotkey != null) + { + realm.SubscribeToPropertyChanged(r => r.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value), kb => kb.KeyCombinationString, updateKeyBindingTooltip); + } + } + protected override bool OnMouseDown(MouseDownEvent e) => false; protected override bool OnClick(ClickEvent e) @@ -168,8 +177,6 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { - updateKeyBindingTooltip(); - HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); @@ -197,19 +204,12 @@ namespace osu.Game.Overlays.Toolbar { } - private void updateKeyBindingTooltip() + private void updateKeyBindingTooltip(string keyCombination) { - if (Hotkey == null) return; + string keyBindingString = keyCombinationProvider.GetReadableString(keyCombination); - var realmKeyBinding = realm.Realm.All().FirstOrDefault(rkb => rkb.RulesetName == null && rkb.ActionInt == (int)Hotkey.Value); - - if (realmKeyBinding != null) - { - string keyBindingString = keyCombinationProvider.GetReadableString(realmKeyBinding.KeyCombination); - - if (!string.IsNullOrEmpty(keyBindingString)) - keyBindingTooltip.Text = $" ({keyBindingString})"; - } + if (!string.IsNullOrEmpty(keyBindingString)) + keyBindingTooltip.Text = $" ({keyBindingString})"; } } From 68430d6ecdbf7810285a818e4f762c07477f6de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Dec 2023 19:39:17 +0100 Subject: [PATCH 044/139] Fix toolbar keybinding hint not clearing after unbinding the keybinding --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 344f7b2018..81d2de5acb 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -208,8 +208,9 @@ namespace osu.Game.Overlays.Toolbar { string keyBindingString = keyCombinationProvider.GetReadableString(keyCombination); - if (!string.IsNullOrEmpty(keyBindingString)) - keyBindingTooltip.Text = $" ({keyBindingString})"; + keyBindingTooltip.Text = !string.IsNullOrEmpty(keyBindingString) + ? $" ({keyBindingString})" + : string.Empty; } } From 19d02364185b72e97c157b42f1d668c2fa794dd8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 23 Dec 2023 22:11:00 +0300 Subject: [PATCH 045/139] Change mod acronym --- osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs index f71acf95b8..b70d607ca1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDepth.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModDepth : ModWithVisibilityAdjustment, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Depth"; - public override string Acronym => "DH"; + public override string Acronym => "DP"; public override IconUsage? Icon => FontAwesome.Solid.Cube; public override ModType Type => ModType.Fun; public override LocalisableString Description => "3D. Almost."; From b1d994b6ffb617f12a09aca20cf0482a3b5db2b9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Dec 2023 17:17:23 +0900 Subject: [PATCH 046/139] Add classic skin sprites for slider tick and slider end misses --- osu.Game/Skinning/LegacyJudgementPieceOld.cs | 16 +++++++++++++--- osu.Game/Skinning/LegacySkin.cs | 13 ++++++++++--- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/osu.Game/Skinning/LegacyJudgementPieceOld.cs b/osu.Game/Skinning/LegacyJudgementPieceOld.cs index b8fe2f8d06..1834a17279 100644 --- a/osu.Game/Skinning/LegacyJudgementPieceOld.cs +++ b/osu.Game/Skinning/LegacyJudgementPieceOld.cs @@ -58,14 +58,24 @@ namespace osu.Game.Skinning if (result.IsMiss()) { + decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; + + // missed ticks / slider end don't get the normal animation. if (isMissedTick()) - applyMissedTickScaling(); - else { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); - decimal? legacyVersion = skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value; + if (legacyVersion > 1.0m) + { + this.MoveTo(new Vector2(0, -2f)); + this.MoveToOffset(new Vector2(0, 10), fade_out_delay + fade_out_length, Easing.In); + } + } + else + { + this.ScaleTo(1.6f); + this.ScaleTo(1, 100, Easing.In); if (legacyVersion > 1.0m) { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index a37a386889..270abe2849 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -453,11 +453,18 @@ namespace osu.Game.Skinning private Drawable? getJudgementAnimation(HitResult result) { - if (result.IsMiss()) - return this.GetAnimation("hit0", true, false); - switch (result) { + case HitResult.Miss: + return this.GetAnimation("hit0", true, false); + + case HitResult.LargeTickMiss: + return this.GetAnimation("slidertickmiss", true, false); + + case HitResult.ComboBreak: + case HitResult.IgnoreMiss: + return this.GetAnimation("sliderendmiss", true, false); + case HitResult.Meh: return this.GetAnimation("hit50", true, false); From 02c771f540489eedaf56719632f500981b20da6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Dec 2023 17:27:15 +0900 Subject: [PATCH 047/139] Add warning for linux users about to report a non-bug as a bug --- .github/ISSUE_TEMPLATE/bug-issue.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug-issue.yml b/.github/ISSUE_TEMPLATE/bug-issue.yml index 00a873f9c8..dfdcf8d320 100644 --- a/.github/ISSUE_TEMPLATE/bug-issue.yml +++ b/.github/ISSUE_TEMPLATE/bug-issue.yml @@ -11,6 +11,10 @@ body: - Current open `priority:0` issues, filterable [here](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3Apriority%3A0). - And most importantly, search for your issue both in the [issue listing](https://github.com/ppy/osu/issues) and the [Q&A discussion listing](https://github.com/ppy/osu/discussions/categories/q-a). If you find that it already exists, respond with a reaction or add any further information that may be helpful. + # ATTENTION LINUX USERS + + If you are having an issue and it is hardware related, **please open a [q&a discussion](https://github.com/ppy/osu/discussions/categories/q-a)** instead of an issue. There's a high chance your issue is due to your system configuration, and not our software. + - type: dropdown attributes: label: Type From 8e6ea2dd9b098aa41f32d54a3c953218aee459fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Dec 2023 17:32:30 +0900 Subject: [PATCH 048/139] Update argon and triangles to match display style --- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++++++++++- .../Rulesets/Judgements/DefaultJudgementPiece.cs | 15 ++++++++++++++- osu.Game/Rulesets/Scoring/HitResult.cs | 4 ++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index edeece0293..bb61bd37c1 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -62,7 +62,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon /// public virtual void PlayAnimation() { - if (Result.IsMiss()) + if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss) + { + this.RotateTo(-45); + this.ScaleTo(1.8f); + this.ScaleTo(1.2f, 100, Easing.In); + + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint); + } + else if (Result.IsMiss()) { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 3c5e37f91c..ada651b60e 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -38,7 +38,20 @@ namespace osu.Game.Rulesets.Judgements /// public virtual void PlayAnimation() { - if (Result != HitResult.None && !Result.IsHit()) + // TODO: make these better. currently they are using a text `-` and it's not centered properly. + // Should be an explicit drawable. + // + // When this is done, remove the [Description] attributes from HitResults which were added for this purpose. + if (Result == HitResult.IgnoreMiss || Result == HitResult.LargeTickMiss) + { + this.RotateTo(-45); + this.ScaleTo(1.8f); + this.ScaleTo(1.2f, 100, Easing.In); + + this.MoveTo(Vector2.Zero); + this.MoveToOffset(new Vector2(0, 10), 800, Easing.InQuint); + } + else if (Result.IsMiss()) { this.ScaleTo(1.6f); this.ScaleTo(1, 100, Easing.In); diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index e174ebd00f..2d55f1a649 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a large tick miss. /// [EnumMember(Value = "large_tick_miss")] - [Description(@"x")] + [Description("-")] [Order(10)] LargeTickMiss, @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Scoring /// Indicates a miss that should be ignored for scoring purposes. /// [EnumMember(Value = "ignore_miss")] - [Description("x")] + [Description("-")] [Order(13)] IgnoreMiss, From 8142a7cb7e82d7053004cd6e4b2f62e74254ed57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Dec 2023 14:00:32 +0100 Subject: [PATCH 049/139] Remove incorrect spec --- osu.Game/Skinning/LegacySkin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 270abe2849..8f0cd59b68 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -461,7 +461,6 @@ namespace osu.Game.Skinning case HitResult.LargeTickMiss: return this.GetAnimation("slidertickmiss", true, false); - case HitResult.ComboBreak: case HitResult.IgnoreMiss: return this.GetAnimation("sliderendmiss", true, false); From 4fa35d709cb163a1a93bc17c6772f2b034614e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Dec 2023 14:56:40 +0100 Subject: [PATCH 050/139] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2ec954b3a5..fbcdb00cdb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From f84b07e71a3298a5093eab2806a74c0ef2066679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 Dec 2023 19:01:06 +0100 Subject: [PATCH 051/139] Do not attempt to stop preview tracks when arriving from a "track completed" sync This fixes an issue identified with the WASAPI implementation in https://github.com/ppy/osu-framework/pull/6088. It has no real effect on current `master`, but fixes a deadlock that occurs with the aforementioned framework branch when one lets a preview track play out to the end - at this point all audio will stop and an attempt to perform any synchronous BASS operation (playing another track, seeking) will result in a deadlock. It isn't terribly clear as to why this is happening precisely, but there does not appear to be any need to stop and seek at that point, so this feels like a decent workaround even if the actual issue is upstream (and will unblock pushing out WASAPI support to users). --- osu.Game/Audio/PreviewTrack.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index d625566ee7..6184ff85dd 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -96,10 +96,13 @@ namespace osu.Game.Audio hasStarted = false; - Track.Stop(); + if (!Track.HasCompleted) + { + Track.Stop(); - // Ensure the track is reset immediately on stopping, so the next time it is started it has a correct time value. - Track.Seek(0); + // Ensure the track is reset immediately on stopping, so the next time it is started it has a correct time value. + Track.Seek(0); + } Stopped?.Invoke(); } From 060bf8beff4f58060e9d60f41dce5850a20748ba Mon Sep 17 00:00:00 2001 From: Nathan Tran Date: Mon, 25 Dec 2023 15:09:39 -0800 Subject: [PATCH 052/139] Fix rewind backtracking --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 370b559897..89911c9a69 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -643,7 +643,7 @@ namespace osu.Game.Screens.Select while (randomSelectedBeatmaps.Any()) { var beatmap = randomSelectedBeatmaps[^1]; - randomSelectedBeatmaps.Remove(beatmap); + randomSelectedBeatmaps.RemoveAt(randomSelectedBeatmaps.Count - 1); if (!beatmap.Filtered.Value && beatmap.BeatmapInfo.BeatmapSet?.DeletePending != true) { From b18b5b99773bffbd5cea8f2b536db763ae983f80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 12:06:56 +0900 Subject: [PATCH 053/139] Add inline note about deadlock --- osu.Game/Audio/PreviewTrack.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 6184ff85dd..961990a1bd 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -96,6 +96,7 @@ namespace osu.Game.Audio hasStarted = false; + // This pre-check is important, fixes a BASS deadlock in some scenarios. if (!Track.HasCompleted) { Track.Stop(); From 2ec6aa7fbb92aa286f1281c4043274f6d007e98b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 26 Dec 2023 12:46:21 +0900 Subject: [PATCH 054/139] Make mania scroll speed independent of hit position --- .../UI/DrawableManiaRuleset.cs | 20 ++++++++++++++++++- .../Skinning/LegacyManiaSkinConfiguration.cs | 4 +++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 9169599798..bea536e4af 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -19,12 +19,14 @@ using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Configuration; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Replays; +using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Mania.UI { @@ -57,6 +59,9 @@ namespace osu.Game.Rulesets.Mania.UI // Stores the current speed adjustment active in gameplay. private readonly Track speedAdjustmentTrack = new TrackVirtual(0); + [Resolved] + private ISkinSource skin { get; set; } + public DrawableManiaRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -104,7 +109,20 @@ namespace osu.Game.Rulesets.Mania.UI updateTimeRange(); } - private void updateTimeRange() => TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value; + private void updateTimeRange() + { + float hitPosition = skin.GetConfig( + new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.HitPosition))?.Value + ?? Stage.HIT_TARGET_POSITION; + + const float length_to_default_hit_position = 768 - LegacyManiaSkinConfiguration.DEFAULT_HIT_POSITION; + float lengthToHitPosition = 768 - hitPosition; + + // This scaling factor preserves the scroll speed as the scroll length varies from changes to the hit position. + float scale = lengthToHitPosition / length_to_default_hit_position; + + TimeRange.Value = smoothTimeRange * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value * scale; + } /// /// Computes a scroll time (in milliseconds) from a scroll speed in the range of 1-40. diff --git a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs index 9acb29a793..042836984a 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfiguration.cs @@ -21,6 +21,8 @@ namespace osu.Game.Skinning /// public const float DEFAULT_COLUMN_SIZE = 30 * POSITION_SCALE_FACTOR; + public const float DEFAULT_HIT_POSITION = (480 - 402) * POSITION_SCALE_FACTOR; + public readonly int Keys; public Dictionary CustomColours { get; } = new Dictionary(); @@ -35,7 +37,7 @@ namespace osu.Game.Skinning public readonly float[] ExplosionWidth; public readonly float[] HoldNoteLightWidth; - public float HitPosition = (480 - 402) * POSITION_SCALE_FACTOR; + public float HitPosition = DEFAULT_HIT_POSITION; public float LightPosition = (480 - 413) * POSITION_SCALE_FACTOR; public float ScorePosition = 300 * POSITION_SCALE_FACTOR; public bool ShowJudgementLine = true; From f9e47242db508d596fe92afef5e15ab5f6583c1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 17:44:49 +0900 Subject: [PATCH 055/139] Add visual offset to better align editor waveforms with expectations --- .../Edit/Compose/Components/Timeline/Timeline.cs | 7 ++++++- osu.Game/Screens/Edit/Editor.cs | 13 +++++++++++++ .../Edit/Timing/WaveformComparisonDisplay.cs | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 75de15fe56..83d34ab61a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -141,7 +141,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); track.BindTo(editorClock.Track); - track.BindValueChanged(_ => waveform.Waveform = beatmap.Value.Waveform, true); + track.BindValueChanged(_ => + { + waveform.Waveform = beatmap.Value.Waveform; + waveform.RelativePositionAxes = Axes.X; + waveform.X = -(float)(Editor.WAVEFORM_VISUAL_OFFSET / beatmap.Value.Track.Length); + }, true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index a6c18bdf0e..c1f6c02301 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -60,6 +60,19 @@ namespace osu.Game.Screens.Edit [Cached] public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler, IKeyBindingHandler, IBeatSnapProvider, ISamplePlaybackDisabler, IBeatSyncProvider { + /// + /// An offset applied to waveform visuals to align them with expectations. + /// + /// + /// Historically, osu! beatmaps have an assumption of full system latency baked in. + /// This comes from a culmination of stable's platform offset, average hardware playback + /// latency, and users having their universal offsets tweaked to previous beatmaps. + /// + /// Coming to this value involved running various tests with existing users / beatmaps. + /// This included both visual and audible comparisons. Ballpark confidence is ≈2 ms. + /// + public const float WAVEFORM_VISUAL_OFFSET = 20; + public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 856bc7c303..b5315feccb 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -219,7 +219,7 @@ namespace osu.Game.Screens.Edit.Timing // offset to the required beat index. double time = selectedGroupStartTime + index * timingPoint.BeatLength; - float offset = (float)(time - visible_width / 2) / trackLength * scale; + float offset = (float)(time - visible_width / 2 + Editor.WAVEFORM_VISUAL_OFFSET) / trackLength * scale; row.Alpha = time < selectedGroupStartTime || time > selectedGroupEndTime ? 0.2f : 1; row.WaveformOffsetTo(-offset, animated); From 4e3bdb2b56bd0d8430ffeeb988498d42412d72ee Mon Sep 17 00:00:00 2001 From: Nathan Tran Date: Tue, 26 Dec 2023 00:57:06 -0800 Subject: [PATCH 056/139] Add test coverage --- .../SongSelect/TestSceneBeatmapCarousel.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index c509d40e07..41ea347ef3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -454,6 +454,23 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Un-filter", () => carousel.Filter(new FilterCriteria(), false)); } + [Test] + public void TestRewind() + { + const int local_set_count = 3; + const int random_select_count = local_set_count * 3; + loadBeatmaps(setCount: local_set_count); + + for (int i = 0; i < random_select_count; i++) + nextRandom(); + + for (int i = 0; i < random_select_count; i++) + { + prevRandom(); + AddAssert("correct random last selected", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + } + } + [Test] public void TestRewindToDeletedBeatmap() { From 225528d519cf65420cf54055ff0602edc3c211d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 19:20:41 +0900 Subject: [PATCH 057/139] Bail from score submission if audio playback rate is too far from reality Closes https://github.com/ppy/osu/issues/23149. --- .../Play/MasterGameplayClockContainer.cs | 55 +++++++++++++++++++ osu.Game/Screens/Play/SubmittingPlayer.cs | 8 +++ 2 files changed, 63 insertions(+) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 54ed7ba626..2844d84f31 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -8,6 +8,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -39,6 +40,14 @@ namespace osu.Game.Screens.Play Precision = 0.1, }; + /// + /// Whether the audio playback is within acceptable ranges. + /// Will become false if audio playback is not going as expected. + /// + public IBindable PlaybackRateValid => playbackRateValid; + + private readonly Bindable playbackRateValid = new Bindable(true); + private readonly WorkingBeatmap beatmap; private Track track; @@ -128,6 +137,7 @@ namespace osu.Game.Screens.Play { // Safety in case the clock is seeked while stopped. LastStopTime = null; + elapsedValidationTime = null; base.Seek(time); } @@ -197,6 +207,51 @@ namespace osu.Game.Screens.Play addAdjustmentsToTrack(); } + protected override void Update() + { + base.Update(); + checkPlaybackValidity(); + } + + #region Clock validation (ensure things are running correctly for local gameplay) + + private double elapsedGameplayClockTime; + private double? elapsedValidationTime; + private int playbackDiscrepancyCount; + + private const int allowed_playback_discrepancies = 5; + + private void checkPlaybackValidity() + { + if (GameplayClock.IsRunning) + { + elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime; + + elapsedValidationTime ??= elapsedGameplayClockTime; + elapsedValidationTime += GameplayClock.Rate * Time.Elapsed; + + if (Math.Abs(elapsedGameplayClockTime - elapsedValidationTime!.Value) > 300) + { + if (playbackDiscrepancyCount++ > allowed_playback_discrepancies) + { + if (playbackRateValid.Value) + { + playbackRateValid.Value = false; + Logger.Log("System audio playback is not working as expected. Some online functionality will not work.\n\nPlease check your audio drivers.", level: LogLevel.Important); + } + } + else + { + Logger.Log($"Playback discrepancy detected ({playbackDiscrepancyCount} of allowed {allowed_playback_discrepancies}): {elapsedGameplayClockTime:N1} vs {elapsedValidationTime:N1}"); + } + + elapsedValidationTime = null; + } + } + } + + #endregion + private bool speedAdjustmentsApplied; private void addAdjustmentsToTrack() diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 785164178a..f88526b8f9 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -208,6 +208,14 @@ namespace osu.Game.Screens.Play private Task submitScore(Score score) { + var masterClock = GameplayClockContainer as MasterGameplayClockContainer; + + if (masterClock?.PlaybackRateValid.Value != true) + { + Logger.Log("Score submission cancelled due to audio playback rate discrepancy."); + return Task.CompletedTask; + } + // token may be null if the request failed but gameplay was still allowed (see HandleTokenRetrievalFailure). if (token == null) { From 30b5b36f1d72f9285971287d6d78c4628c3dde94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 12:19:04 +0100 Subject: [PATCH 058/139] Fix code quality inspection --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 41ea347ef3..aa4c879468 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -467,7 +467,7 @@ namespace osu.Game.Tests.Visual.SongSelect for (int i = 0; i < random_select_count; i++) { prevRandom(); - AddAssert("correct random last selected", () => selectedSets.Peek() == carousel.SelectedBeatmapSet); + AddAssert("correct random last selected", () => selectedSets.Peek(), () => Is.EqualTo(carousel.SelectedBeatmapSet)); } } From f2c0e7cf2ecc04d5f3f01bd945623a61a8ab7f04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 20:31:34 +0900 Subject: [PATCH 059/139] Fix editor's control point list refreshing multiple times for a single change --- osu.Game/Screens/Edit/Timing/ControlPointList.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 22e37b9efb..7cd1dbc630 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -109,8 +109,13 @@ namespace osu.Game.Screens.Edit.Timing controlPointGroups.BindTo(Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((_, _) => { - table.ControlGroups = controlPointGroups; - changeHandler?.SaveState(); + // This callback can happen many times in a change operation. It gets expensive. + // We really should be handling the `CollectionChanged` event properly. + Scheduler.AddOnce(() => + { + table.ControlGroups = controlPointGroups; + changeHandler?.SaveState(); + }); }, true); table.OnRowSelected += drawable => scroll.ScrollIntoView(drawable); From 1f2f749db68fe3a541150abb7acdae7e0aabf79e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 20:42:04 +0900 Subject: [PATCH 060/139] Fix selection not being retained in control point list when undoing / redoing --- osu.Game/Screens/Edit/EditorTable.cs | 30 ++++++++++++++++++- .../Screens/Edit/Timing/ControlPointTable.cs | 14 +++++---- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index b79d71b42b..5ccb21cf59 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -46,15 +47,42 @@ namespace osu.Game.Screens.Edit }); } - protected void SetSelectedRow(object? item) + protected int GetIndexForObject(object? item) { + for (int i = 0; i < BackgroundFlow.Count; i++) + { + if (BackgroundFlow[i].Item == item) + return i; + } + + return -1; + } + + protected bool SetSelectedRow(object? item) + { + bool foundSelection = false; + foreach (var b in BackgroundFlow) { b.Selected = ReferenceEquals(b.Item, item); if (b.Selected) + { + Debug.Assert(!foundSelection); OnRowSelected?.Invoke(b); + foundSelection = true; + } } + + return foundSelection; + } + + protected object? GetObjectAtIndex(int index) + { + if (index < 0 || index > BackgroundFlow.Count - 1) + return null; + + return BackgroundFlow[index].Item; } protected override Drawable CreateHeader(int index, TableColumn? column) => new HeaderText(column?.Header ?? default); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index b078e3fa44..335077c6f0 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Timing public partial class ControlPointTable : EditorTable { [Resolved] - private Bindable selectedGroup { get; set; } = null!; + private Bindable selectedGroup { get; set; } = null!; [Resolved] private EditorClock clock { get; set; } = null!; @@ -32,6 +32,8 @@ namespace osu.Game.Screens.Edit.Timing { set { + int selectedIndex = GetIndexForObject(selectedGroup.Value); + Content = null; BackgroundFlow.Clear(); @@ -53,7 +55,11 @@ namespace osu.Game.Screens.Edit.Timing Columns = createHeaders(); Content = value.Select(createContent).ToArray().ToRectangular(); - updateSelectedGroup(); + if (!SetSelectedRow(selectedGroup.Value)) + { + // Some operations completely obliterate references, so best-effort reselect based on index. + selectedGroup.Value = GetObjectAtIndex(selectedIndex) as ControlPointGroup; + } } } @@ -61,11 +67,9 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(_ => updateSelectedGroup(), true); + selectedGroup.BindValueChanged(_ => SetSelectedRow(selectedGroup.Value), true); } - private void updateSelectedGroup() => SetSelectedRow(selectedGroup.Value); - private TableColumn[] createHeaders() { var columns = new List From 03e2463b06b14b66529627fc76941d901231001c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Dec 2023 21:20:18 +0900 Subject: [PATCH 061/139] Add test coverage and refactor to better handle equality edge case --- .../Visual/Editing/TestSceneTimingScreen.cs | 43 +++++++++++++++++++ osu.Game/Screens/Edit/EditorTable.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 29 ++++++++++--- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index 216c35de65..40aadc8164 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Timing.RowAttributes; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Editing @@ -69,6 +70,48 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("Wait for rows to load", () => Child.ChildrenOfType().Any()); } + [Test] + public void TestSelectedRetainedOverUndo() + { + AddStep("Select first timing point", () => + { + InputManager.MoveMouseTo(Child.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 2170); + AddUntilStep("Ensure seeked to correct time", () => EditorClock.CurrentTimeAccurate == 2170); + + AddStep("Adjust offset", () => + { + InputManager.MoveMouseTo(timingScreen.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre + new Vector2(20, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for offset changed", () => + { + return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170; + }); + + AddStep("simulate undo", () => + { + var clone = editorBeatmap.ControlPointInfo.DeepClone(); + + editorBeatmap.ControlPointInfo.Clear(); + + foreach (var group in clone.Groups) + { + foreach (var cp in group.ControlPoints) + editorBeatmap.ControlPointInfo.Add(group.Time, cp); + } + }); + + AddUntilStep("selection retained", () => + { + return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170; + }); + } + [Test] public void TestTrackingCurrentTimeWhileRunning() { diff --git a/osu.Game/Screens/Edit/EditorTable.cs b/osu.Game/Screens/Edit/EditorTable.cs index 5ccb21cf59..e5dc540b06 100644 --- a/osu.Game/Screens/Edit/EditorTable.cs +++ b/osu.Game/Screens/Edit/EditorTable.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit return -1; } - protected bool SetSelectedRow(object? item) + protected virtual bool SetSelectedRow(object? item) { bool foundSelection = false; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 335077c6f0..7a27056da3 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing { Action = () => { - selectedGroup.Value = group; + SetSelectedRow(group); clock.SeekSmoothlyTo(group.Time); } }); @@ -55,11 +55,16 @@ namespace osu.Game.Screens.Edit.Timing Columns = createHeaders(); Content = value.Select(createContent).ToArray().ToRectangular(); - if (!SetSelectedRow(selectedGroup.Value)) - { - // Some operations completely obliterate references, so best-effort reselect based on index. - selectedGroup.Value = GetObjectAtIndex(selectedIndex) as ControlPointGroup; - } + // Attempt to retain selection. + if (SetSelectedRow(selectedGroup.Value)) + return; + + // Some operations completely obliterate references, so best-effort reselect based on index. + if (SetSelectedRow(GetObjectAtIndex(selectedIndex))) + return; + + // Selection could not be retained. + selectedGroup.Value = null; } } @@ -67,7 +72,17 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(_ => SetSelectedRow(selectedGroup.Value), true); + // Handle external selections. + selectedGroup.BindValueChanged(g => SetSelectedRow(g.NewValue), true); + } + + protected override bool SetSelectedRow(object? item) + { + if (!base.SetSelectedRow(item)) + return false; + + selectedGroup.Value = item as ControlPointGroup; + return true; } private TableColumn[] createHeaders() From d70fddb6fde18de0ef092c047509ee0a2efd2d16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 00:11:22 +0900 Subject: [PATCH 062/139] Fix elapsed time being counted twice on first frame --- osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index 2844d84f31..a475f4823f 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -227,8 +227,10 @@ namespace osu.Game.Screens.Play { elapsedGameplayClockTime += GameplayClock.ElapsedFrameTime; - elapsedValidationTime ??= elapsedGameplayClockTime; - elapsedValidationTime += GameplayClock.Rate * Time.Elapsed; + if (elapsedValidationTime == null) + elapsedValidationTime = elapsedGameplayClockTime; + else + elapsedValidationTime += GameplayClock.Rate * Time.Elapsed; if (Math.Abs(elapsedGameplayClockTime - elapsedValidationTime!.Value) > 300) { From c55458e49c6b5747a1a84e00ac05787829c81a2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:32:47 +0900 Subject: [PATCH 063/139] Remove pointless intermediary class --- .../Containers/ExpandingButtonContainer.cs | 21 ------------------- osu.Game/Overlays/Settings/SettingsSidebar.cs | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 osu.Game/Graphics/Containers/ExpandingButtonContainer.cs diff --git a/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs b/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs deleted file mode 100644 index 5abb4096ac..0000000000 --- a/osu.Game/Graphics/Containers/ExpandingButtonContainer.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -namespace osu.Game.Graphics.Containers -{ - /// - /// An with a long hover expansion delay. - /// - /// - /// Mostly used for buttons with explanatory labels, in which the label would display after a "long hover". - /// - public partial class ExpandingButtonContainer : ExpandingContainer - { - protected ExpandingButtonContainer(float contractedWidth, float expandedWidth) - : base(contractedWidth, expandedWidth) - { - } - - protected override double HoverExpansionDelay => 400; - } -} diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index 06bc2fd788..7baf9a58ff 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.Settings { - public partial class SettingsSidebar : ExpandingButtonContainer + public partial class SettingsSidebar : ExpandingContainer { public const float DEFAULT_WIDTH = 70; public const int EXPANDED_WIDTH = 200; From 58476d5429985109a13240c481da362449489167 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:33:02 +0900 Subject: [PATCH 064/139] Allow `ExpandingContainer` to not auto expand on hover --- osu.Game/Graphics/Containers/ExpandingContainer.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Containers/ExpandingContainer.cs b/osu.Game/Graphics/Containers/ExpandingContainer.cs index 60b9e6a167..2abdb508ae 100644 --- a/osu.Game/Graphics/Containers/ExpandingContainer.cs +++ b/osu.Game/Graphics/Containers/ExpandingContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -26,6 +24,8 @@ namespace osu.Game.Graphics.Containers /// protected virtual double HoverExpansionDelay => 0; + protected virtual bool ExpandOnHover => true; + protected override Container Content => FillFlow; protected FillFlowContainer FillFlow { get; } @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.Containers }; } - private ScheduledDelegate hoverExpandEvent; + private ScheduledDelegate? hoverExpandEvent; protected override void LoadComplete() { @@ -93,6 +93,9 @@ namespace osu.Game.Graphics.Containers private void updateHoverExpansion() { + if (!ExpandOnHover) + return; + hoverExpandEvent?.Cancel(); if (IsHovered && !Expanded.Value) From 9a1a97180d7938bbc1dced8d887cc226f28deb46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:33:17 +0900 Subject: [PATCH 065/139] Change settings overlay to always show expanded buttons --- osu.Game/Overlays/Settings/SettingsSidebar.cs | 3 +++ osu.Game/Overlays/SettingsOverlay.cs | 7 +++++-- osu.Game/Overlays/SettingsPanel.cs | 1 - 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index 7baf9a58ff..fc5c6b07bb 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -13,9 +13,12 @@ namespace osu.Game.Overlays.Settings public const float DEFAULT_WIDTH = 70; public const int EXPANDED_WIDTH = 200; + protected override bool ExpandOnHover => false; + public SettingsSidebar() : base(DEFAULT_WIDTH, EXPANDED_WIDTH) { + Expanded.Value = true; } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 746d451343..a779c3c263 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -72,16 +72,19 @@ namespace osu.Game.Overlays switch (state.NewValue) { case Visibility.Visible: - Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); + Sidebar.Expanded.Value = false; + Sidebar.FadeColour(Color4.DarkGray, 300, Easing.OutQuint); SectionsContainer.FadeOut(300, Easing.OutQuint); ContentContainer.MoveToX(-PANEL_WIDTH, 500, Easing.OutQuint); lastOpenedSubPanel = panel; + break; case Visibility.Hidden: - Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint); + Sidebar.Expanded.Value = true; + Sidebar.FadeColour(Color4.White, 300, Easing.OutQuint); SectionsContainer.FadeIn(500, Easing.OutQuint); ContentContainer.MoveToX(0, 500, Easing.OutQuint); diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 3bac6c400f..339120fd84 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -285,7 +285,6 @@ namespace osu.Game.Overlays return; SectionsContainer.ScrollTo(section); - Sidebar.Expanded.Value = false; }, }; } From 5de8307918216ed5f18edb42bfc8eb28c6f310b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:38:23 +0900 Subject: [PATCH 066/139] Reduce width of sidebar buttons --- osu.Game/Overlays/Settings/SettingsSidebar.cs | 2 +- osu.Game/Overlays/Settings/SidebarIconButton.cs | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index fc5c6b07bb..c751f12003 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.Settings public partial class SettingsSidebar : ExpandingContainer { public const float DEFAULT_WIDTH = 70; - public const int EXPANDED_WIDTH = 200; + public const int EXPANDED_WIDTH = 170; protected override bool ExpandOnHover => false; diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index 4e5b361460..bd9ac3cf97 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -69,18 +69,18 @@ namespace osu.Game.Overlays.Settings Colour = OsuColour.Gray(0.6f), Children = new Drawable[] { - headerText = new OsuSpriteText - { - Position = new Vector2(SettingsSidebar.DEFAULT_WIDTH + 10, 0), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, iconContainer = new ConstrainedIconContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(20), }, + headerText = new OsuSpriteText + { + Position = new Vector2(60, 0), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, } }, selectionIndicator = new CircularContainer From 5b1deb7c4b1139aa55543094dc652b6763ddfcd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:40:50 +0900 Subject: [PATCH 067/139] Give buttons a touch of padding to make click effect feel better --- osu.Game/Overlays/Settings/SidebarIconButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index bd9ac3cf97..a8d27eb792 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -60,6 +60,8 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.X; Height = 46; + Padding = new MarginPadding(5); + AddRange(new Drawable[] { textIconContent = new Container From 8e13f65c5d388ba0494367dfedcb2cccdc066b84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:46:21 +0900 Subject: [PATCH 068/139] Adjust hover effect slightly --- osu.Game/Overlays/Settings/SidebarIconButton.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index a8d27eb792..041d19e8bf 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -111,10 +111,13 @@ namespace osu.Game.Overlays.Settings private void load() { selectionIndicator.Colour = ColourProvider.Highlight1; + Hover.Colour = ColourProvider.Light4; } protected override void UpdateState() { + Hover.FadeTo(IsHovered ? 0.1f : 0, FADE_DURATION, Easing.OutQuint); + if (Selected) { textIconContent.FadeColour(ColourProvider.Content1, FADE_DURATION, Easing.OutQuint); From 5d0b5247946e758fd28f8bad58ec16cf7c15ee18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 02:54:24 +0900 Subject: [PATCH 069/139] Adjust back button to match style better --- osu.Game/Overlays/Settings/SidebarButton.cs | 7 ++++++- osu.Game/Overlays/Settings/SidebarIconButton.cs | 3 +-- osu.Game/Overlays/SettingsSubPanel.cs | 13 +++++++++---- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index a63688762d..f58c2f41ef 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Graphics.UserInterface; @@ -23,6 +24,7 @@ namespace osu.Game.Overlays.Settings private void load() { BackgroundColour = ColourProvider.Background5; + Hover.Colour = ColourProvider.Light4; } protected override void LoadComplete() @@ -40,6 +42,9 @@ namespace osu.Game.Overlays.Settings protected override void OnHoverLost(HoverLostEvent e) => UpdateState(); - protected abstract void UpdateState(); + protected virtual void UpdateState() + { + Hover.FadeTo(IsHovered ? 0.1f : 0, FADE_DURATION, Easing.OutQuint); + } } } diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index 041d19e8bf..1bb3aa2921 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -111,12 +111,11 @@ namespace osu.Game.Overlays.Settings private void load() { selectionIndicator.Colour = ColourProvider.Highlight1; - Hover.Colour = ColourProvider.Light4; } protected override void UpdateState() { - Hover.FadeTo(IsHovered ? 0.1f : 0, FADE_DURATION, Easing.OutQuint); + base.UpdateState(); if (Selected) { diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 1651975a74..1b9edeebc2 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -47,7 +47,9 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - Size = new Vector2(SettingsSidebar.DEFAULT_WIDTH); + Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); + + Padding = new MarginPadding(5); AddRange(new Drawable[] { @@ -61,7 +63,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(15), + Y = -5, + Size = new Vector2(30), Shadow = true, Icon = FontAwesome.Solid.ChevronLeft }, @@ -69,8 +72,8 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Y = 15, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Y = 30, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), Text = @"back", }, } @@ -80,6 +83,8 @@ namespace osu.Game.Overlays protected override void UpdateState() { + base.UpdateState(); + content.FadeColour(IsHovered ? ColourProvider.Light1 : ColourProvider.Light3, FADE_DURATION, Easing.OutQuint); } } From c087578e011c6ade4411dbccfcfce80da407dba3 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Tue, 26 Dec 2023 10:07:21 -0800 Subject: [PATCH 070/139] Force minimum cursor size for `OsuResumeOverlay` On cursor sizes below 0.3x it becomes exceedingly difficult to quickly locate and then accurately click the resume cursor on the pause overlay as it could as big as a handful of pixels. This clamps the minimum cursor size to 1x for the resume overlay, which is way more comfortable and more closely resembles stable. --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 10 +++++----- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index ba9fda25e4..18351c20ce 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -70,10 +70,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor }; userCursorScale = config.GetBindable(OsuSetting.GameplayCursorSize); - userCursorScale.ValueChanged += _ => calculateCursorScale(); + userCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); - autoCursorScale.ValueChanged += _ => calculateCursorScale(); + autoCursorScale.ValueChanged += _ => cursorScale.Value = CalculateCursorScale(); cursorScale.BindValueChanged(e => cursorScaleContainer.Scale = new Vector2(e.NewValue), true); } @@ -81,10 +81,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override void LoadComplete() { base.LoadComplete(); - calculateCursorScale(); + cursorScale.Value = CalculateCursorScale(); } - private void calculateCursorScale() + protected virtual float CalculateCursorScale() { float scale = userCursorScale.Value; @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize); } - cursorScale.Value = scale; + return scale; } protected override void SkinChanged(ISkinSource skin) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index ea49836772..f5e83f46f2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -71,6 +71,12 @@ namespace osu.Game.Rulesets.Osu.UI RelativePositionAxes = Axes.Both; } + protected override float CalculateCursorScale() + { + // Force minimum cursor size so it's easily clickable + return Math.Max(1f, base.CalculateCursorScale()); + } + protected override bool OnHover(HoverEvent e) { updateColour(); From c107bfcd3112cc8b1589d84f4634be1688cc684d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:09:34 +0100 Subject: [PATCH 071/139] Fix `TestSceneSideOverlays` test failure --- osu.Game/Overlays/Settings/SettingsSidebar.cs | 4 ++-- osu.Game/Overlays/Settings/SidebarIconButton.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index c751f12003..302c52fbd2 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -10,13 +10,13 @@ namespace osu.Game.Overlays.Settings { public partial class SettingsSidebar : ExpandingContainer { - public const float DEFAULT_WIDTH = 70; + public const float CONTRACTED_WIDTH = 70; public const int EXPANDED_WIDTH = 170; protected override bool ExpandOnHover => false; public SettingsSidebar() - : base(DEFAULT_WIDTH, EXPANDED_WIDTH) + : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { Expanded.Value = true; } diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index 1bb3aa2921..e7ae4cc81d 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Settings { textIconContent = new Container { - Width = SettingsSidebar.DEFAULT_WIDTH, + Width = SettingsSidebar.CONTRACTED_WIDTH, RelativeSizeAxes = Axes.Y, Colour = OsuColour.Gray(0.6f), Children = new Drawable[] diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 339120fd84..f4dfc7fa27 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays public const float TRANSITION_LENGTH = 600; - private const float sidebar_width = SettingsSidebar.DEFAULT_WIDTH; + private const float sidebar_width = SettingsSidebar.EXPANDED_WIDTH; /// /// The width of the settings panel content, excluding the sidebar. From 9ac79782d25f3625d79d1d9096cb3eecacbdad21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:21:15 +0100 Subject: [PATCH 072/139] Add visibility toggle step to settings panel test scene --- osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 5d9c2f890c..8c4ca47fc3 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -41,6 +41,7 @@ namespace osu.Game.Tests.Visual.Settings public void TestBasic() { AddStep("do nothing", () => { }); + AddToggleStep("toggle visibility", visible => settings.State.Value = visible ? Visibility.Visible : Visibility.Hidden); } [Test] From af47f9fd701897a3096c785a5c2e31c2b87ee6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:24:43 +0100 Subject: [PATCH 073/139] Fix sidebar button text becoming masked away during fadeout --- osu.Game/Overlays/Settings/SidebarIconButton.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/SidebarIconButton.cs b/osu.Game/Overlays/Settings/SidebarIconButton.cs index e7ae4cc81d..f4b71207e3 100644 --- a/osu.Game/Overlays/Settings/SidebarIconButton.cs +++ b/osu.Game/Overlays/Settings/SidebarIconButton.cs @@ -66,16 +66,16 @@ namespace osu.Game.Overlays.Settings { textIconContent = new Container { - Width = SettingsSidebar.CONTRACTED_WIDTH, - RelativeSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.6f), Children = new Drawable[] { iconContainer = new ConstrainedIconContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Size = new Vector2(20), + Margin = new MarginPadding { Left = 25 } }, headerText = new OsuSpriteText { From 6f672b8cb302fb7aa5034b07a2b6f0f351df54aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Dec 2023 20:36:12 +0100 Subject: [PATCH 074/139] Fix `TestSceneKeyBindingPanel` failures --- .../Visual/Settings/TestSceneKeyBindingPanel.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 1c4e89e1a2..57c9770c9a 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Overlays; -using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Rulesets.Taiko; using osuTK.Input; @@ -152,7 +151,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click first row with two bindings", () => { multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); - InputManager.MoveMouseTo(multiBindingRow); + InputManager.MoveMouseTo(multiBindingRow.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); @@ -256,7 +255,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("click first row with two bindings", () => { multiBindingRow = panel.ChildrenOfType().First(row => row.Defaults.Count() > 1); - InputManager.MoveMouseTo(multiBindingRow); + InputManager.MoveMouseTo(multiBindingRow.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); @@ -305,7 +304,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (rim)"); AddStep("attempt to bind M1 to two keys", () => InputManager.Click(MouseButton.Left)); @@ -325,7 +323,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (rim)"); AddStep("attempt to bind M1 to two keys", () => InputManager.Click(MouseButton.Left)); @@ -345,7 +342,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (centre)"); AddStep("clear binding", () => { @@ -377,7 +373,6 @@ namespace osu.Game.Tests.Visual.Settings section.ChildrenOfType().Single().TriggerClick(); }); AddStep("move mouse to centre", () => InputManager.MoveMouseTo(panel.ScreenSpaceDrawQuad.Centre)); - AddUntilStep("wait for collapsed", () => panel.ChildrenOfType().Single().Expanded.Value, () => Is.False); scrollToAndStartBinding("Left (centre)"); AddStep("clear binding", () => { From 8cd240fbecebf7bd37ba2cc504fe751c5afe727a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:20:19 +0900 Subject: [PATCH 075/139] Reduce size and fix alignment of back button --- osu.Game/Overlays/SettingsSubPanel.cs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 1b9edeebc2..4d1f8f45cc 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays public partial class BackButton : SidebarButton { - private Container content; + private Drawable content; public BackButton() : base(HoverSampleSet.Default) @@ -49,30 +49,31 @@ namespace osu.Game.Overlays { Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); - Padding = new MarginPadding(5); + Padding = new MarginPadding(40); AddRange(new Drawable[] { - content = new Container + content = new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), Children = new Drawable[] { new SpriteIcon { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = -5, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Size = new Vector2(30), Shadow = true, Icon = FontAwesome.Solid.ChevronLeft }, new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Y = 30, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), Text = @"back", }, From 901674a1303a349185d5123f013a3d2e3d2d4fad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:39:13 +0900 Subject: [PATCH 076/139] Move back button inside sidebar to fix weird animation --- .../Visual/Settings/TestSceneSettingsPanel.cs | 2 +- osu.Game/Overlays/Settings/SettingsSidebar.cs | 79 ++++++++++++++++++- osu.Game/Overlays/SettingsOverlay.cs | 2 +- osu.Game/Overlays/SettingsPanel.cs | 13 +-- osu.Game/Overlays/SettingsSubPanel.cs | 71 ----------------- 5 files changed, 87 insertions(+), 80 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs index 8c4ca47fc3..df0fc8de57 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsPanel.cs @@ -111,7 +111,7 @@ namespace osu.Game.Tests.Visual.Settings AddStep("Press back", () => settings .ChildrenOfType().FirstOrDefault()? - .ChildrenOfType().FirstOrDefault()?.TriggerClick()); + .ChildrenOfType().FirstOrDefault()?.TriggerClick()); AddUntilStep("top-level textbox focused", () => settings.SectionsContainer.ChildrenOfType().FirstOrDefault()?.HasFocus == true); } diff --git a/osu.Game/Overlays/Settings/SettingsSidebar.cs b/osu.Game/Overlays/Settings/SettingsSidebar.cs index 302c52fbd2..ddbcd60ef6 100644 --- a/osu.Game/Overlays/Settings/SettingsSidebar.cs +++ b/osu.Game/Overlays/Settings/SettingsSidebar.cs @@ -1,10 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Overlays.Settings { @@ -13,11 +20,16 @@ namespace osu.Game.Overlays.Settings public const float CONTRACTED_WIDTH = 70; public const int EXPANDED_WIDTH = 170; + public Action? BackButtonAction; + protected override bool ExpandOnHover => false; - public SettingsSidebar() + private readonly bool showBackButton; + + public SettingsSidebar(bool showBackButton) : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { + this.showBackButton = showBackButton; Expanded.Value = true; } @@ -30,6 +42,71 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.Both, Depth = float.MaxValue }); + + if (showBackButton) + { + AddInternal(new BackButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Action = () => BackButtonAction?.Invoke(), + }); + } + } + + public partial class BackButton : SidebarButton + { + private Drawable content = null!; + + public BackButton() + : base(HoverSampleSet.Default) + { + } + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); + + Padding = new MarginPadding(40); + + AddRange(new[] + { + content = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new SpriteIcon + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(30), + Shadow = true, + Icon = FontAwesome.Solid.ChevronLeft + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), + Text = @"back", + }, + } + } + }); + } + + protected override void UpdateState() + { + base.UpdateState(); + + content.FadeColour(IsHovered ? ColourProvider.Light1 : ColourProvider.Light3, FADE_DURATION, Easing.OutQuint); + } } } } diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index a779c3c263..5735b1515a 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -49,7 +49,7 @@ namespace osu.Game.Overlays protected override Drawable CreateFooter() => new SettingsFooter(); public SettingsOverlay() - : base(true) + : base(false) { } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index f4dfc7fa27..3861c5abc7 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/settings-pop-in"; protected override double PopInOutSampleBalance => -OsuGameBase.SFX_STEREO_STRENGTH; - private readonly bool showSidebar; + private readonly bool showBackButton; private LoadingLayer loading; @@ -72,9 +72,9 @@ namespace osu.Game.Overlays [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - protected SettingsPanel(bool showSidebar) + protected SettingsPanel(bool showBackButton) { - this.showSidebar = showSidebar; + this.showBackButton = showBackButton; RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; } @@ -146,10 +146,11 @@ namespace osu.Game.Overlays } }); - if (showSidebar) + AddInternal(Sidebar = new SettingsSidebar(showBackButton) { - AddInternal(Sidebar = new SettingsSidebar { Width = sidebar_width }); - } + BackButtonAction = Hide, + Width = sidebar_width + }); CreateSections()?.ForEach(AddSection); } diff --git a/osu.Game/Overlays/SettingsSubPanel.cs b/osu.Game/Overlays/SettingsSubPanel.cs index 4d1f8f45cc..440639f06b 100644 --- a/osu.Game/Overlays/SettingsSubPanel.cs +++ b/osu.Game/Overlays/SettingsSubPanel.cs @@ -1,17 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Overlays.Settings; -using osuTK; namespace osu.Game.Overlays { @@ -25,69 +15,8 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load() { - AddInternal(new BackButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Action = Hide - }); } protected override bool DimMainContent => false; // dimming is handled by main overlay - - public partial class BackButton : SidebarButton - { - private Drawable content; - - public BackButton() - : base(HoverSampleSet.Default) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Size = new Vector2(SettingsSidebar.EXPANDED_WIDTH); - - Padding = new MarginPadding(40); - - AddRange(new Drawable[] - { - content = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - Children = new Drawable[] - { - new SpriteIcon - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(30), - Shadow = true, - Icon = FontAwesome.Solid.ChevronLeft - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular), - Text = @"back", - }, - } - } - }); - } - - protected override void UpdateState() - { - base.UpdateState(); - - content.FadeColour(IsHovered ? ColourProvider.Light1 : ColourProvider.Light3, FADE_DURATION, Easing.OutQuint); - } - } } } From 81ba46216f0951e30329c3e2779a5380ee25e743 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:49:00 +0900 Subject: [PATCH 077/139] Speed up fades in transition to avoid ugliness --- osu.Game/Overlays/SettingsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index 3861c5abc7..748673035b 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -181,7 +181,7 @@ namespace osu.Game.Overlays Scheduler.AddDelayed(loadSections, TRANSITION_LENGTH / 3); Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(1, TRANSITION_LENGTH / 2, Easing.OutQuint); searchTextBox.TakeFocus(); searchTextBox.HoldFocus = true; @@ -197,7 +197,7 @@ namespace osu.Game.Overlays ContentContainer.MoveToX(-WIDTH + ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint); Sidebar?.MoveToX(-sidebar_width, TRANSITION_LENGTH, Easing.OutQuint); - this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + this.FadeTo(0, TRANSITION_LENGTH / 2, Easing.OutQuint); searchTextBox.HoldFocus = false; if (searchTextBox.HasFocus) From 768e10d55f5e3cc144856b47906b03187f162e13 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 13:50:47 +0900 Subject: [PATCH 078/139] Adjust `NotificationOverlay` fades to match --- osu.Game/Overlays/NotificationOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index c3ddb228ea..f56d09f2b2 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -227,7 +227,7 @@ namespace osu.Game.Overlays protected override void PopIn() { this.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint); - mainContent.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeTo(1, TRANSITION_LENGTH / 2, Easing.OutQuint); mainContent.FadeEdgeEffectTo(WaveContainer.SHADOW_OPACITY, WaveContainer.APPEAR_DURATION, Easing.Out); toastTray.FlushAllToasts(); @@ -240,7 +240,7 @@ namespace osu.Game.Overlays markAllRead(); this.MoveToX(WIDTH, TRANSITION_LENGTH, Easing.OutQuint); - mainContent.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint); + mainContent.FadeTo(0, TRANSITION_LENGTH / 2, Easing.OutQuint); mainContent.FadeEdgeEffectTo(0, WaveContainer.DISAPPEAR_DURATION, Easing.In); } From 8f7e0571f0693cfd8fefcc41f41a70239db1f21e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 10:51:05 +0100 Subject: [PATCH 079/139] Add failing test coverage of handling out-of-bounds catch objects --- .../TestSceneOutOfBoundsObjects.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs new file mode 100644 index 0000000000..951f5d1ca1 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneOutOfBoundsObjects.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public partial class TestSceneOutOfBoundsObjects : TestSceneCatchPlayer + { + protected override bool Autoplay => true; + + [Test] + public void TestNoOutOfBoundsObjects() + { + bool anyObjectOutOfBounds = false; + + AddStep("reset flag", () => anyObjectOutOfBounds = false); + + AddUntilStep("check for out-of-bounds objects", + () => + { + anyObjectOutOfBounds |= Player.ChildrenOfType().Any(dho => dho.X < 0 || dho.X > CatchPlayfield.WIDTH); + return Player.ScoreProcessor.HasCompleted.Value; + }); + + AddAssert("no out of bound objects found", () => !anyObjectOutOfBounds); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + Ruleset = ruleset, + }, + HitObjects = new List + { + new Fruit { StartTime = 1000, X = -50 }, + new Fruit { StartTime = 1200, X = CatchPlayfield.WIDTH + 50 }, + new JuiceStream + { + StartTime = 1500, + X = 10, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(-200, 0) + }) + }, + new JuiceStream + { + StartTime = 3000, + X = CatchPlayfield.WIDTH - 10, + Path = new SliderPath(PathType.LINEAR, new[] + { + Vector2.Zero, + new Vector2(200, 0) + }) + }, + } + }; + } +} From 2e8b49b93a7ec12ace3ea9e3f0713f8ba20545e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 10:55:29 +0100 Subject: [PATCH 080/139] Fix catch drawable objects not being clamped to playfield bounds --- .../Objects/Drawables/DrawablePalpableCatchHitObject.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs index 4a9661f108..ade00918ab 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawablePalpableCatchHitObject.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Catch.UI; using osuTK; using osuTK.Graphics; @@ -70,7 +72,10 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables private void updateXPosition(ValueChangedEvent _) { - X = OriginalXBindable.Value + XOffsetBindable.Value; + // same as `CatchHitObject.EffectiveX`. + // not using that property directly to support scenarios where `HitObject` may not necessarily be present + // for this pooled drawable. + X = Math.Clamp(OriginalXBindable.Value + XOffsetBindable.Value, 0, CatchPlayfield.WIDTH); } protected override void OnApply() From 1233533fb967390c2c433ea0f7feb0dadd682635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Dec 2023 22:14:15 +0900 Subject: [PATCH 081/139] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index cf01f2f99b..b179b8b837 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.iOS.props b/osu.iOS.props index c7b9d02b26..7e03ab50e2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -23,6 +23,6 @@ iossimulator-x64 - + From 14b37db3dd17d3be07a9dce5b088b59205d33815 Mon Sep 17 00:00:00 2001 From: Gabriel Del Nero <43073074+Gabixel@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:35:19 +0100 Subject: [PATCH 082/139] Make `ModDaycore` sequential for `ModHalfTime` --- osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs index 59a631a7b5..bf58efc339 100644 --- a/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs +++ b/osu.Game/Overlays/Mods/Input/ClassicModHotkeyHandler.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Mods.Input { [Key.Q] = new[] { typeof(ModEasy) }, [Key.W] = new[] { typeof(ModNoFail) }, - [Key.E] = new[] { typeof(ModHalfTime) }, + [Key.E] = new[] { typeof(ModHalfTime), typeof(ModDaycore) }, [Key.A] = new[] { typeof(ModHardRock) }, [Key.S] = new[] { typeof(ModSuddenDeath), typeof(ModPerfect) }, [Key.D] = new[] { typeof(ModDoubleTime), typeof(ModNightcore) }, From 13333f75756e587572411971524a65cda09eedab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:02:19 +0100 Subject: [PATCH 083/139] Make room for `OsuIcon` to accept new icons --- osu.Game/Graphics/OsuIcon.cs | 132 ++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 64 deletions(-) diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 15af8f000b..d1ad818300 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -7,90 +7,94 @@ namespace osu.Game.Graphics { public static class OsuIcon { - public static IconUsage Get(int icon) => new IconUsage((char)icon, "osuFont"); + #region Legacy spritesheet-based icons + + private static IconUsage get(int icon) => new IconUsage((char)icon, @"osuFont"); // ruleset icons in circles - public static IconUsage RulesetOsu => Get(0xe000); - public static IconUsage RulesetMania => Get(0xe001); - public static IconUsage RulesetCatch => Get(0xe002); - public static IconUsage RulesetTaiko => Get(0xe003); + public static IconUsage RulesetOsu => get(0xe000); + public static IconUsage RulesetMania => get(0xe001); + public static IconUsage RulesetCatch => get(0xe002); + public static IconUsage RulesetTaiko => get(0xe003); // ruleset icons without circles - public static IconUsage FilledCircle => Get(0xe004); - public static IconUsage CrossCircle => Get(0xe005); - public static IconUsage Logo => Get(0xe006); - public static IconUsage ChevronDownCircle => Get(0xe007); - public static IconUsage EditCircle => Get(0xe033); - public static IconUsage LeftCircle => Get(0xe034); - public static IconUsage RightCircle => Get(0xe035); - public static IconUsage Charts => Get(0xe036); - public static IconUsage Solo => Get(0xe037); - public static IconUsage Multi => Get(0xe038); - public static IconUsage Gear => Get(0xe039); + public static IconUsage FilledCircle => get(0xe004); + public static IconUsage CrossCircle => get(0xe005); + public static IconUsage Logo => get(0xe006); + public static IconUsage ChevronDownCircle => get(0xe007); + public static IconUsage EditCircle => get(0xe033); + public static IconUsage LeftCircle => get(0xe034); + public static IconUsage RightCircle => get(0xe035); + public static IconUsage Charts => get(0xe036); + public static IconUsage Solo => get(0xe037); + public static IconUsage Multi => get(0xe038); + public static IconUsage Gear => get(0xe039); // misc icons - public static IconUsage Bat => Get(0xe008); - public static IconUsage Bubble => Get(0xe009); - public static IconUsage BubblePop => Get(0xe02e); - public static IconUsage Dice => Get(0xe011); - public static IconUsage Heart => Get(0xe02f); - public static IconUsage HeartBreak => Get(0xe030); - public static IconUsage Hot => Get(0xe031); - public static IconUsage ListSearch => Get(0xe032); + public static IconUsage Bat => get(0xe008); + public static IconUsage Bubble => get(0xe009); + public static IconUsage BubblePop => get(0xe02e); + public static IconUsage Dice => get(0xe011); + public static IconUsage Heart => get(0xe02f); + public static IconUsage HeartBreak => get(0xe030); + public static IconUsage Hot => get(0xe031); + public static IconUsage ListSearch => get(0xe032); //osu! playstyles - public static IconUsage PlayStyleTablet => Get(0xe02a); - public static IconUsage PlayStyleMouse => Get(0xe029); - public static IconUsage PlayStyleKeyboard => Get(0xe02b); - public static IconUsage PlayStyleTouch => Get(0xe02c); + public static IconUsage PlayStyleTablet => get(0xe02a); + public static IconUsage PlayStyleMouse => get(0xe029); + public static IconUsage PlayStyleKeyboard => get(0xe02b); + public static IconUsage PlayStyleTouch => get(0xe02c); // osu! difficulties - public static IconUsage EasyOsu => Get(0xe015); - public static IconUsage NormalOsu => Get(0xe016); - public static IconUsage HardOsu => Get(0xe017); - public static IconUsage InsaneOsu => Get(0xe018); - public static IconUsage ExpertOsu => Get(0xe019); + public static IconUsage EasyOsu => get(0xe015); + public static IconUsage NormalOsu => get(0xe016); + public static IconUsage HardOsu => get(0xe017); + public static IconUsage InsaneOsu => get(0xe018); + public static IconUsage ExpertOsu => get(0xe019); // taiko difficulties - public static IconUsage EasyTaiko => Get(0xe01a); - public static IconUsage NormalTaiko => Get(0xe01b); - public static IconUsage HardTaiko => Get(0xe01c); - public static IconUsage InsaneTaiko => Get(0xe01d); - public static IconUsage ExpertTaiko => Get(0xe01e); + public static IconUsage EasyTaiko => get(0xe01a); + public static IconUsage NormalTaiko => get(0xe01b); + public static IconUsage HardTaiko => get(0xe01c); + public static IconUsage InsaneTaiko => get(0xe01d); + public static IconUsage ExpertTaiko => get(0xe01e); // fruits difficulties - public static IconUsage EasyFruits => Get(0xe01f); - public static IconUsage NormalFruits => Get(0xe020); - public static IconUsage HardFruits => Get(0xe021); - public static IconUsage InsaneFruits => Get(0xe022); - public static IconUsage ExpertFruits => Get(0xe023); + public static IconUsage EasyFruits => get(0xe01f); + public static IconUsage NormalFruits => get(0xe020); + public static IconUsage HardFruits => get(0xe021); + public static IconUsage InsaneFruits => get(0xe022); + public static IconUsage ExpertFruits => get(0xe023); // mania difficulties - public static IconUsage EasyMania => Get(0xe024); - public static IconUsage NormalMania => Get(0xe025); - public static IconUsage HardMania => Get(0xe026); - public static IconUsage InsaneMania => Get(0xe027); - public static IconUsage ExpertMania => Get(0xe028); + public static IconUsage EasyMania => get(0xe024); + public static IconUsage NormalMania => get(0xe025); + public static IconUsage HardMania => get(0xe026); + public static IconUsage InsaneMania => get(0xe027); + public static IconUsage ExpertMania => get(0xe028); // mod icons - public static IconUsage ModPerfect => Get(0xe049); - public static IconUsage ModAutopilot => Get(0xe03a); - public static IconUsage ModAuto => Get(0xe03b); - public static IconUsage ModCinema => Get(0xe03c); - public static IconUsage ModDoubleTime => Get(0xe03d); - public static IconUsage ModEasy => Get(0xe03e); - public static IconUsage ModFlashlight => Get(0xe03f); - public static IconUsage ModHalftime => Get(0xe040); - public static IconUsage ModHardRock => Get(0xe041); - public static IconUsage ModHidden => Get(0xe042); - public static IconUsage ModNightcore => Get(0xe043); - public static IconUsage ModNoFail => Get(0xe044); - public static IconUsage ModRelax => Get(0xe045); - public static IconUsage ModSpunOut => Get(0xe046); - public static IconUsage ModSuddenDeath => Get(0xe047); - public static IconUsage ModTarget => Get(0xe048); + public static IconUsage ModPerfect => get(0xe049); + public static IconUsage ModAutopilot => get(0xe03a); + public static IconUsage ModAuto => get(0xe03b); + public static IconUsage ModCinema => get(0xe03c); + public static IconUsage ModDoubleTime => get(0xe03d); + public static IconUsage ModEasy => get(0xe03e); + public static IconUsage ModFlashlight => get(0xe03f); + public static IconUsage ModHalftime => get(0xe040); + public static IconUsage ModHardRock => get(0xe041); + public static IconUsage ModHidden => get(0xe042); + public static IconUsage ModNightcore => get(0xe043); + public static IconUsage ModNoFail => get(0xe044); + public static IconUsage ModRelax => get(0xe045); + public static IconUsage ModSpunOut => get(0xe046); + public static IconUsage ModSuddenDeath => get(0xe047); + public static IconUsage ModTarget => get(0xe048); // Use "Icons/BeatmapDetails/mod-icon" instead // public static IconUsage ModBg => Get(0xe04a); + + #endregion } } From 45143a6c17e4574aab8a910a7be27e568f91c34a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:17:39 +0100 Subject: [PATCH 084/139] Implement new icon store --- osu.Game/Graphics/OsuIcon.cs | 348 ++++++++++++++++++++++++++++++++++- osu.Game/OsuGameBase.cs | 1 + 2 files changed, 347 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index d1ad818300..3cd10b1315 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -1,7 +1,16 @@ // Copyright (c) ppy Pty Ltd . 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.ComponentModel; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Text; namespace osu.Game.Graphics { @@ -19,7 +28,6 @@ namespace osu.Game.Graphics // ruleset icons without circles public static IconUsage FilledCircle => get(0xe004); - public static IconUsage CrossCircle => get(0xe005); public static IconUsage Logo => get(0xe006); public static IconUsage ChevronDownCircle => get(0xe007); public static IconUsage EditCircle => get(0xe033); @@ -35,7 +43,6 @@ namespace osu.Game.Graphics public static IconUsage Bubble => get(0xe009); public static IconUsage BubblePop => get(0xe02e); public static IconUsage Dice => get(0xe011); - public static IconUsage Heart => get(0xe02f); public static IconUsage HeartBreak => get(0xe030); public static IconUsage Hot => get(0xe031); public static IconUsage ListSearch => get(0xe032); @@ -96,5 +103,342 @@ namespace osu.Game.Graphics // public static IconUsage ModBg => Get(0xe04a); #endregion + + #region New single-file-based icons + + public const string FONT_NAME = @"Icons"; + + public static IconUsage Audio => get(OsuIconMapping.Audio); + public static IconUsage Beatmap => get(OsuIconMapping.Beatmap); + public static IconUsage Calendar => get(OsuIconMapping.Calendar); + public static IconUsage ChangelogA => get(OsuIconMapping.ChangelogA); + public static IconUsage ChangelogB => get(OsuIconMapping.ChangelogB); + public static IconUsage Chat => get(OsuIconMapping.Chat); + public static IconUsage CheckCircle => get(OsuIconMapping.CheckCircle); + public static IconUsage CollapseA => get(OsuIconMapping.CollapseA); + public static IconUsage Collections => get(OsuIconMapping.Collections); + public static IconUsage Cross => get(OsuIconMapping.Cross); + public static IconUsage CrossCircle => get(OsuIconMapping.CrossCircle); + public static IconUsage Crown => get(OsuIconMapping.Crown); + public static IconUsage Debug => get(OsuIconMapping.Debug); + public static IconUsage Delete => get(OsuIconMapping.Delete); + public static IconUsage Details => get(OsuIconMapping.Details); + public static IconUsage Discord => get(OsuIconMapping.Discord); + public static IconUsage EllipsisHorizontal => get(OsuIconMapping.EllipsisHorizontal); + public static IconUsage EllipsisVertical => get(OsuIconMapping.EllipsisVertical); + public static IconUsage ExpandA => get(OsuIconMapping.ExpandA); + public static IconUsage ExpandB => get(OsuIconMapping.ExpandB); + public static IconUsage FeaturedArtist => get(OsuIconMapping.FeaturedArtist); + public static IconUsage FeaturedArtistCircle => get(OsuIconMapping.FeaturedArtistCircle); + public static IconUsage GameplayA => get(OsuIconMapping.GameplayA); + public static IconUsage GameplayB => get(OsuIconMapping.GameplayB); + public static IconUsage GameplayC => get(OsuIconMapping.GameplayC); + public static IconUsage Global => get(OsuIconMapping.Global); + public static IconUsage Graphics => get(OsuIconMapping.Graphics); + public static IconUsage Heart => get(OsuIconMapping.Heart); + public static IconUsage Home => get(OsuIconMapping.Home); + public static IconUsage Input => get(OsuIconMapping.Input); + public static IconUsage Maintenance => get(OsuIconMapping.Maintenance); + public static IconUsage Megaphone => get(OsuIconMapping.Megaphone); + public static IconUsage Music => get(OsuIconMapping.Music); + public static IconUsage News => get(OsuIconMapping.News); + public static IconUsage Next => get(OsuIconMapping.Next); + public static IconUsage NextCircle => get(OsuIconMapping.NextCircle); + public static IconUsage Notification => get(OsuIconMapping.Notification); + public static IconUsage Online => get(OsuIconMapping.Online); + public static IconUsage Play => get(OsuIconMapping.Play); + public static IconUsage Player => get(OsuIconMapping.Player); + public static IconUsage PlayerFollow => get(OsuIconMapping.PlayerFollow); + public static IconUsage Prev => get(OsuIconMapping.Prev); + public static IconUsage PrevCircle => get(OsuIconMapping.PrevCircle); + public static IconUsage Ranking => get(OsuIconMapping.Ranking); + public static IconUsage Rulesets => get(OsuIconMapping.Rulesets); + public static IconUsage Search => get(OsuIconMapping.Search); + public static IconUsage Settings => get(OsuIconMapping.Settings); + public static IconUsage SkinA => get(OsuIconMapping.SkinA); + public static IconUsage SkinB => get(OsuIconMapping.SkinB); + public static IconUsage Star => get(OsuIconMapping.Star); + public static IconUsage Storyboard => get(OsuIconMapping.Storyboard); + public static IconUsage Team => get(OsuIconMapping.Team); + public static IconUsage ThumbsUp => get(OsuIconMapping.ThumbsUp); + public static IconUsage Tournament => get(OsuIconMapping.Tournament); + public static IconUsage Twitter => get(OsuIconMapping.Twitter); + public static IconUsage UserInterface => get(OsuIconMapping.UserInterface); + public static IconUsage Wiki => get(OsuIconMapping.Wiki); + public static IconUsage EditorAddControlPoint => get(OsuIconMapping.EditorAddControlPoint); + public static IconUsage EditorConvertToStream => get(OsuIconMapping.EditorConvertToStream); + public static IconUsage EditorDistanceSnap => get(OsuIconMapping.EditorDistanceSnap); + public static IconUsage EditorFinish => get(OsuIconMapping.EditorFinish); + public static IconUsage EditorGridSnap => get(OsuIconMapping.EditorGridSnap); + public static IconUsage EditorNewComboA => get(OsuIconMapping.EditorNewComboA); + public static IconUsage EditorNewComboB => get(OsuIconMapping.EditorNewComboB); + public static IconUsage EditorSelect => get(OsuIconMapping.EditorSelect); + public static IconUsage EditorSound => get(OsuIconMapping.EditorSound); + public static IconUsage EditorWhistle => get(OsuIconMapping.EditorWhistle); + + private static IconUsage get(OsuIconMapping glyph) => new IconUsage((char)glyph, FONT_NAME); + + private enum OsuIconMapping + { + [Description(@"audio")] + Audio, + + [Description(@"beatmap")] + Beatmap, + + [Description(@"calendar")] + Calendar, + + [Description(@"changelog-a")] + ChangelogA, + + [Description(@"changelog-b")] + ChangelogB, + + [Description(@"chat")] + Chat, + + [Description(@"check-circle")] + CheckCircle, + + [Description(@"collapse-a")] + CollapseA, + + [Description(@"collections")] + Collections, + + [Description(@"cross")] + Cross, + + [Description(@"cross-circle")] + CrossCircle, + + [Description(@"crown")] + Crown, + + [Description(@"debug")] + Debug, + + [Description(@"delete")] + Delete, + + [Description(@"details")] + Details, + + [Description(@"discord")] + Discord, + + [Description(@"ellipsis-horizontal")] + EllipsisHorizontal, + + [Description(@"ellipsis-vertical")] + EllipsisVertical, + + [Description(@"expand-a")] + ExpandA, + + [Description(@"expand-b")] + ExpandB, + + [Description(@"featured-artist")] + FeaturedArtist, + + [Description(@"featured-artist-circle")] + FeaturedArtistCircle, + + [Description(@"gameplay-a")] + GameplayA, + + [Description(@"gameplay-b")] + GameplayB, + + [Description(@"gameplay-c")] + GameplayC, + + [Description(@"global")] + Global, + + [Description(@"graphics")] + Graphics, + + [Description(@"heart")] + Heart, + + [Description(@"home")] + Home, + + [Description(@"input")] + Input, + + [Description(@"maintenance")] + Maintenance, + + [Description(@"megaphone")] + Megaphone, + + [Description(@"music")] + Music, + + [Description(@"news")] + News, + + [Description(@"next")] + Next, + + [Description(@"next-circle")] + NextCircle, + + [Description(@"notification")] + Notification, + + [Description(@"online")] + Online, + + [Description(@"play")] + Play, + + [Description(@"player")] + Player, + + [Description(@"player-follow")] + PlayerFollow, + + [Description(@"prev")] + Prev, + + [Description(@"prev-circle")] + PrevCircle, + + [Description(@"ranking")] + Ranking, + + [Description(@"rulesets")] + Rulesets, + + [Description(@"search")] + Search, + + [Description(@"settings")] + Settings, + + [Description(@"skin-a")] + SkinA, + + [Description(@"skin-b")] + SkinB, + + [Description(@"star")] + Star, + + [Description(@"storyboard")] + Storyboard, + + [Description(@"team")] + Team, + + [Description(@"thumbs-up")] + ThumbsUp, + + [Description(@"tournament")] + Tournament, + + [Description(@"twitter")] + Twitter, + + [Description(@"user-interface")] + UserInterface, + + [Description(@"wiki")] + Wiki, + + [Description(@"Editor/add-control-point")] + EditorAddControlPoint = 1000, + + [Description(@"Editor/convert-to-stream")] + EditorConvertToStream, + + [Description(@"Editor/distance-snap")] + EditorDistanceSnap, + + [Description(@"Editor/finish")] + EditorFinish, + + [Description(@"Editor/grid-snap")] + EditorGridSnap, + + [Description(@"Editor/new-combo-a")] + EditorNewComboA, + + [Description(@"Editor/new-combo-b")] + EditorNewComboB, + + [Description(@"Editor/select")] + EditorSelect, + + [Description(@"Editor/sound")] + EditorSound, + + [Description(@"Editor/whistle")] + EditorWhistle, + } + + public class OsuIconStore : ITextureStore, ITexturedGlyphLookupStore + { + private readonly TextureStore textures; + + public OsuIconStore(TextureStore textures) + { + this.textures = textures; + } + + public ITexturedCharacterGlyph? Get(string? fontName, char character) + { + if (fontName == FONT_NAME) + return new Glyph(textures.Get($@"{fontName}/{((OsuIconMapping)character).GetDescription()}")); + + return null; + } + + public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); + + public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null; + + public Texture Get(string name) => throw new NotImplementedException(); + + public Task GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public Stream GetStream(string name) => throw new NotImplementedException(); + + public IEnumerable GetAvailableResources() => throw new NotImplementedException(); + + public Task GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException(); + + public class Glyph : ITexturedCharacterGlyph + { + public float XOffset => default; + public float YOffset => default; + public float XAdvance => default; + public float Baseline => default; + public char Character => default; + + public float GetKerning(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException(); + + public Texture Texture { get; } + public float Width => Texture.Width; + public float Height => Texture.Height; + + public Glyph(Texture texture) + { + Texture = texture; + } + } + + public void Dispose() + { + textures.Dispose(); + } + } + + #endregion } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 48548dc1ef..b4ad21f045 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -479,6 +479,7 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Black"); Fonts.AddStore(new HexaconsIcons.HexaconsStore(Textures)); + Fonts.AddStore(new OsuIcon.OsuIconStore(Textures)); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => From 69baabee627f55d87f6ba61f361e1b370bbfdd2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:35:03 +0100 Subject: [PATCH 085/139] Replace hexacons in toolbar with new icons --- osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs | 2 +- osu.Game/Overlays/Changelog/ChangelogHeader.cs | 2 +- osu.Game/Overlays/ChatOverlay.cs | 2 +- osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs | 2 +- osu.Game/Overlays/News/NewsHeader.cs | 2 +- osu.Game/Overlays/NotificationOverlay.cs | 2 +- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs | 2 +- osu.Game/Overlays/SettingsOverlay.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs | 2 +- osu.Game/Overlays/Wiki/WikiHeader.cs | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs index 27fab82bf3..075dfd02b0 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingHeader.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapListing { Title = PageTitleStrings.MainBeatmapsetsControllerIndex; Description = NamedOverlayComponentStrings.BeatmapListingDescription; - Icon = HexaconsIcons.Beatmap; + Icon = OsuIcon.Beatmap; } } } diff --git a/osu.Game/Overlays/Changelog/ChangelogHeader.cs b/osu.Game/Overlays/Changelog/ChangelogHeader.cs index 61ea9dc4db..f738d70370 100644 --- a/osu.Game/Overlays/Changelog/ChangelogHeader.cs +++ b/osu.Game/Overlays/Changelog/ChangelogHeader.cs @@ -124,7 +124,7 @@ namespace osu.Game.Overlays.Changelog { Title = PageTitleStrings.MainChangelogControllerDefault; Description = NamedOverlayComponentStrings.ChangelogDescription; - Icon = HexaconsIcons.Devtools; + Icon = OsuIcon.ChangelogB; } } } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 4aa4c59471..8f3b7031c2 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -31,7 +31,7 @@ namespace osu.Game.Overlays { public partial class ChatOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, IKeyBindingHandler { - public IconUsage Icon => HexaconsIcons.Messaging; + public IconUsage Icon => OsuIcon.Chat; public LocalisableString Title => ChatStrings.HeaderTitle; public LocalisableString Description => ChatStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 104f0943dc..8fd8f6b332 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Dashboard { Title = PageTitleStrings.MainHomeControllerIndex; Description = NamedOverlayComponentStrings.DashboardDescription; - Icon = HexaconsIcons.Social; + Icon = OsuIcon.Global; } } } diff --git a/osu.Game/Overlays/News/NewsHeader.cs b/osu.Game/Overlays/News/NewsHeader.cs index f237ed66f2..92d71a21ef 100644 --- a/osu.Game/Overlays/News/NewsHeader.cs +++ b/osu.Game/Overlays/News/NewsHeader.cs @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.News { Title = PageTitleStrings.MainNewsControllerDefault; Description = NamedOverlayComponentStrings.NewsDescription; - Icon = HexaconsIcons.News; + Icon = OsuIcon.News; } } } diff --git a/osu.Game/Overlays/NotificationOverlay.cs b/osu.Game/Overlays/NotificationOverlay.cs index f56d09f2b2..18a487a312 100644 --- a/osu.Game/Overlays/NotificationOverlay.cs +++ b/osu.Game/Overlays/NotificationOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays { public partial class NotificationOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent, INotificationOverlay { - public IconUsage Icon => HexaconsIcons.Notification; + public IconUsage Icon => OsuIcon.Notification; public LocalisableString Title => NotificationsStrings.HeaderTitle; public LocalisableString Description => NotificationsStrings.HeaderDescription; diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 425ff0935d..7ec52364d4 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays { public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverlayComponent { - public IconUsage Icon => HexaconsIcons.Music; + public IconUsage Icon => OsuIcon.Music; public LocalisableString Title => NowPlayingStrings.HeaderTitle; public LocalisableString Description => NowPlayingStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 63128fb73d..a23ec18afe 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Rankings { Title = PageTitleStrings.MainRankingControllerDefault; Description = NamedOverlayComponentStrings.RankingsDescription; - Icon = HexaconsIcons.Rankings; + Icon = OsuIcon.Ranking; } } } diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 5735b1515a..9efd848035 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays { public partial class SettingsOverlay : SettingsPanel, INamedOverlayComponent { - public IconUsage Icon => HexaconsIcons.Settings; + public IconUsage Icon => OsuIcon.Settings; public LocalisableString Title => SettingsStrings.HeaderTitle; public LocalisableString Description => SettingsStrings.HeaderDescription; diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index ded0229d67..70675c1b92 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Toolbar { TooltipMain = ToolbarStrings.HomeHeaderTitle; TooltipSub = ToolbarStrings.HomeHeaderDescription; - SetIcon(HexaconsIcons.Home); + SetIcon(OsuIcon.Home); } } } diff --git a/osu.Game/Overlays/Wiki/WikiHeader.cs b/osu.Game/Overlays/Wiki/WikiHeader.cs index 9e9e565684..24eddeb0c2 100644 --- a/osu.Game/Overlays/Wiki/WikiHeader.cs +++ b/osu.Game/Overlays/Wiki/WikiHeader.cs @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Wiki { Title = PageTitleStrings.MainWikiControllerDefault; Description = NamedOverlayComponentStrings.WikiDescription; - Icon = HexaconsIcons.Wiki; + Icon = OsuIcon.Wiki; } } } From 28d9145a4c182ecb66029e0b1e52f24f7cc16acf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:47:29 +0100 Subject: [PATCH 086/139] Add more spacing to toolbar icons --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 81d2de5acb..a547fda814 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -113,7 +113,7 @@ namespace osu.Game.Overlays.Toolbar { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Size = new Vector2(26), + Size = new Vector2(20), Alpha = 0, }, DrawableText = new OsuSpriteText From c45477bd1fff8447755f7afd54bf5156e1352833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:40:48 +0100 Subject: [PATCH 087/139] Use new icons in main menu wherever feasible --- osu.Game/Screens/Menu/ButtonSystem.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index ca5bef985e..b2b3fbd626 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Logging; @@ -103,8 +102,8 @@ namespace osu.Game.Screens.Menu buttonArea.AddRange(new Drawable[] { - new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, FontAwesome.Solid.Cog, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), - backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.LeftCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, + new MainMenuButton(ButtonSystemStrings.Settings, string.Empty, OsuIcon.Settings, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), + backButton = new MainMenuButton(ButtonSystemStrings.Back, @"back-to-top", OsuIcon.PrevCircle, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) { VisibleStateMin = ButtonSystemState.Play, @@ -128,18 +127,18 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio, IdleTracker? idleTracker, GameHost host) { - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", FontAwesome.Solid.User, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", FontAwesome.Solid.Users, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); - buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Charts, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Solo, @"button-default-select", OsuIcon.Player, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, 0, Key.M)); + buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, 0, Key.L)); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); - buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", HexaconsIcons.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); - buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", HexaconsIcons.Editor, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); + buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), () => OnEditBeatmap?.Invoke(), WEDGE_WIDTH, Key.B)); + buttonsEdit.Add(new MainMenuButton(SkinEditorStrings.SkinEditor.ToLower(), @"button-default-select", OsuIcon.SkinB, new Color4(220, 160, 0, 255), () => OnEditSkin?.Invoke(), 0, Key.S)); buttonsEdit.ForEach(b => b.VisibleState = ButtonSystemState.Edit); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Play, @"button-play-select", OsuIcon.Logo, new Color4(102, 68, 204, 255), () => State = ButtonSystemState.Play, WEDGE_WIDTH, Key.P)); buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Edit, @"button-play-select", OsuIcon.EditCircle, new Color4(238, 170, 0, 255), () => State = ButtonSystemState.Edit, 0, Key.E)); - buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.ChevronDownCircle, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); + buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Browse, @"button-default-select", OsuIcon.Beatmap, new Color4(165, 204, 0, 255), () => OnBeatmapListing?.Invoke(), 0, Key.B, Key.D)); if (host.CanExit) buttonsTopLevel.Add(new MainMenuButton(ButtonSystemStrings.Exit, string.Empty, OsuIcon.CrossCircle, new Color4(238, 51, 153, 255), () => OnExit?.Invoke(), 0, Key.Q)); From 2857322a8bc3257ae26ea35567b8f1741b7a45ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:53:01 +0100 Subject: [PATCH 088/139] Use new icons in settings --- osu.Game/Overlays/Settings/Sections/AudioSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/DebugSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/GameplaySection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 2 +- osu.Game/Overlays/Settings/Sections/GraphicsSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/InputSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/OnlineSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/RulesetSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 3 ++- osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs | 3 ++- 11 files changed, 21 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/AudioSection.cs b/osu.Game/Overlays/Settings/Sections/AudioSection.cs index fb3d486776..1ab0d6c886 100644 --- a/osu.Game/Overlays/Settings/Sections/AudioSection.cs +++ b/osu.Game/Overlays/Settings/Sections/AudioSection.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Localisation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Audio; @@ -17,7 +18,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.VolumeUp + Icon = OsuIcon.Audio }; public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "sound" }); diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 33a6f4c673..b84c441057 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -5,6 +5,7 @@ using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.DebugSettings; @@ -16,7 +17,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Bug + Icon = OsuIcon.Debug }; public DebugSection() diff --git a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs index b60689b611..463b3d1d09 100644 --- a/osu.Game/Overlays/Settings/Sections/GameplaySection.cs +++ b/osu.Game/Overlays/Settings/Sections/GameplaySection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Gameplay; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Regular.DotCircle + Icon = OsuIcon.GameplayC }; public GameplaySection() diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index 2b043d40bc..2aa1008b1d 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Cog + Icon = OsuIcon.Settings }; [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs index 98f6908512..e1fa1eef9c 100644 --- a/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GraphicsSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Graphics; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Laptop + Icon = OsuIcon.Graphics }; public GraphicsSection() diff --git a/osu.Game/Overlays/Settings/Sections/InputSection.cs b/osu.Game/Overlays/Settings/Sections/InputSection.cs index a8f19cc91d..0204aa5e64 100644 --- a/osu.Game/Overlays/Settings/Sections/InputSection.cs +++ b/osu.Game/Overlays/Settings/Sections/InputSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Handlers; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Input; @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Keyboard + Icon = OsuIcon.Input }; public InputSection(KeyBindingPanel keyConfig) diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index bb0a952164..bd90e4c35d 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Maintenance; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Wrench + Icon = OsuIcon.Maintenance }; public MaintenanceSection() diff --git a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs index c8faa3b697..1484f2c756 100644 --- a/osu.Game/Overlays/Settings/Sections/OnlineSection.cs +++ b/osu.Game/Overlays/Settings/Sections/OnlineSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.Online; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.GlobeAsia + Icon = OsuIcon.Online }; public OnlineSection() diff --git a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs index aaad1ec4e2..626264151f 100644 --- a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs +++ b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Rulesets; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.Chess + Icon = OsuIcon.Rulesets }; [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 1d057f42c0..9b04f208a7 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Database; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.SkinEditor; @@ -31,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.PaintBrush + Icon = OsuIcon.SkinB }; private static readonly Live random_skin_info = new SkinInfo diff --git a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs index 2ec9e32ea9..953ede25e1 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterfaceSection.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.UserInterface; @@ -15,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections public override Drawable CreateIcon() => new SpriteIcon { - Icon = FontAwesome.Solid.LayerGroup + Icon = OsuIcon.UserInterface }; public UserInterfaceSection() From 288ac930e44bd8dc5b5ab8d5f5f342228625fc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 16:03:57 +0100 Subject: [PATCH 089/139] Use new icons in editor Some that exist on figma are purposefully not used due to an editorial request from @peppy. --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 3 ++- osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs | 3 ++- osu.Game/Rulesets/Edit/Tools/SelectTool.cs | 3 ++- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 6 +++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 8382317d70..448cfaf84c 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -54,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Edit .Concat(DistanceSnapProvider.CreateTernaryButtons()) .Concat(new[] { - new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Th }) + new TernaryButton(rectangularGridSnapToggle, "Grid Snap", () => new SpriteIcon { Icon = OsuIcon.EditorGridSnap }) }); private BindableList selectedHitObjects; diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index ddf539771d..b3ca59a5b0 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; @@ -169,7 +170,7 @@ namespace osu.Game.Rulesets.Edit public IEnumerable CreateTernaryButtons() => new[] { - new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = FontAwesome.Solid.Ruler }) + new TernaryButton(DistanceSnapToggle, "Distance Snap", () => new SpriteIcon { Icon = OsuIcon.EditorDistanceSnap }) }; protected override bool OnKeyDown(KeyDownEvent e) diff --git a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs index 9640830a09..a272e9f480 100644 --- a/osu.Game/Rulesets/Edit/Tools/SelectTool.cs +++ b/osu.Game/Rulesets/Edit/Tools/SelectTool.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; namespace osu.Game.Rulesets.Edit.Tools { @@ -15,7 +16,7 @@ namespace osu.Game.Rulesets.Edit.Tools { } - public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.Solid.MousePointer }; + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSelect }; public override PlacementBlueprint CreatePlacementBlueprint() => null; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index c7c7c4aa83..4fba798a26 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -225,7 +225,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected virtual IEnumerable CreateTernaryButtons() { //TODO: this should only be enabled (visible?) for rulesets that provide combo-supporting HitObjects. - yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = FontAwesome.Regular.DotCircle }); + yield return new TernaryButton(NewCombo, "New combo", () => new SpriteIcon { Icon = OsuIcon.EditorNewComboA }); foreach (var kvp in SelectionHandler.SelectionSampleStates) yield return new TernaryButton(kvp.Value, kvp.Key.Replace("hit", string.Empty).Titleize(), () => getIconForSample(kvp.Key)); @@ -272,10 +272,10 @@ namespace osu.Game.Screens.Edit.Compose.Components return new SpriteIcon { Icon = FontAwesome.Solid.Hands }; case HitSampleInfo.HIT_WHISTLE: - return new SpriteIcon { Icon = FontAwesome.Solid.Bullhorn }; + return new SpriteIcon { Icon = OsuIcon.EditorWhistle }; case HitSampleInfo.HIT_FINISH: - return new SpriteIcon { Icon = FontAwesome.Solid.DrumSteelpan }; + return new SpriteIcon { Icon = OsuIcon.EditorFinish }; } return null; From 53766285ce6cd5b227e933fe45da8f32bc2f6cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:55:19 +0100 Subject: [PATCH 090/139] Remove remaining hexacons usages --- osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs | 2 +- osu.Game/Overlays/Chat/ChatOverlayTopBar.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs | 2 +- osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs index 55a04b129c..5db7223bdf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayHeader.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.UserInterface public TestTitle() { Title = "title"; - Icon = HexaconsIcons.Devtools; + Icon = OsuIcon.ChangelogB; } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index eced27f35e..1df246ae77 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapHeaderTitle() { Title = PageTitleStrings.MainBeatmapsetsControllerShow; - Icon = HexaconsIcons.Beatmap; + Icon = OsuIcon.Beatmap; } } } diff --git a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs index 4fc9fbb6d5..3ecdb09976 100644 --- a/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs +++ b/osu.Game/Overlays/Chat/ChatOverlayTopBar.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = HexaconsIcons.Messaging, + Icon = OsuIcon.Chat, Size = new Vector2(24), }, // Placeholder text diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 78343d08f1..42bec50022 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.Profile public ProfileHeaderTitle() { Title = PageTitleStrings.MainUsersControllerDefault; - Icon = HexaconsIcons.Profile; + Icon = OsuIcon.Player; } } } diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 5c77672d90..0e125d0ec0 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Edit.Components.Menus Size = new Vector2(26), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Icon = HexaconsIcons.Editor, + Icon = OsuIcon.EditCircle, }, text = new TextFlowContainer { diff --git a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs index 93448c4394..022da36abc 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreenHeader.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Setup { Title = EditorSetupStrings.BeatmapSetup.ToLower(); Description = EditorSetupStrings.BeatmapSetupDescription; - Icon = HexaconsIcons.Social; + Icon = OsuIcon.Beatmap; } } From 89e2b6358a2937ee46246f3b05b4122c7528ae95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 15:55:37 +0100 Subject: [PATCH 091/139] Remove hexacons --- osu.Game/Graphics/HexaconsIcons.cs | 131 ----------------------------- osu.Game/OsuGameBase.cs | 1 - 2 files changed, 132 deletions(-) delete mode 100644 osu.Game/Graphics/HexaconsIcons.cs diff --git a/osu.Game/Graphics/HexaconsIcons.cs b/osu.Game/Graphics/HexaconsIcons.cs deleted file mode 100644 index 3eee5d7197..0000000000 --- a/osu.Game/Graphics/HexaconsIcons.cs +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) ppy Pty Ltd . 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.IO; -using System.Threading; -using System.Threading.Tasks; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Text; - -namespace osu.Game.Graphics -{ - public static class HexaconsIcons - { - public const string FONT_NAME = "Icons/Hexacons"; - - public static IconUsage BeatmapPacks => get(HexaconsMapping.beatmap_packs); - public static IconUsage Beatmap => get(HexaconsMapping.beatmap); - public static IconUsage Calendar => get(HexaconsMapping.calendar); - public static IconUsage Chart => get(HexaconsMapping.chart); - public static IconUsage Community => get(HexaconsMapping.community); - public static IconUsage Contests => get(HexaconsMapping.contests); - public static IconUsage Devtools => get(HexaconsMapping.devtools); - public static IconUsage Download => get(HexaconsMapping.download); - public static IconUsage Editor => get(HexaconsMapping.editor); - public static IconUsage FeaturedArtist => get(HexaconsMapping.featured_artist); - public static IconUsage Home => get(HexaconsMapping.home); - public static IconUsage Messaging => get(HexaconsMapping.messaging); - public static IconUsage Music => get(HexaconsMapping.music); - public static IconUsage News => get(HexaconsMapping.news); - public static IconUsage Notification => get(HexaconsMapping.notification); - public static IconUsage Profile => get(HexaconsMapping.profile); - public static IconUsage Rankings => get(HexaconsMapping.rankings); - public static IconUsage Search => get(HexaconsMapping.search); - public static IconUsage Settings => get(HexaconsMapping.settings); - public static IconUsage Social => get(HexaconsMapping.social); - public static IconUsage Store => get(HexaconsMapping.store); - public static IconUsage Tournament => get(HexaconsMapping.tournament); - public static IconUsage Wiki => get(HexaconsMapping.wiki); - - private static IconUsage get(HexaconsMapping icon) => new IconUsage((char)icon, FONT_NAME); - - // Basically just converting to something we can use in a `char` lookup for FontStore/GlyphStore compatibility. - // Names should match filenames in resources. - private enum HexaconsMapping - { - beatmap_packs, - beatmap, - calendar, - chart, - community, - contests, - devtools, - download, - editor, - featured_artist, - home, - messaging, - music, - news, - notification, - profile, - rankings, - search, - settings, - social, - store, - tournament, - wiki, - } - - public class HexaconsStore : ITextureStore, ITexturedGlyphLookupStore - { - private readonly TextureStore textures; - - public HexaconsStore(TextureStore textures) - { - this.textures = textures; - } - - public void Dispose() - { - textures.Dispose(); - } - - public ITexturedCharacterGlyph? Get(string? fontName, char character) - { - if (fontName == FONT_NAME) - return new Glyph(textures.Get($"{fontName}/{((HexaconsMapping)character).ToString().Replace("_", "-")}")); - - return null; - } - - public Task GetAsync(string fontName, char character) => Task.Run(() => Get(fontName, character)); - - public Texture? Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => null; - - public Texture Get(string name) => throw new NotImplementedException(); - - public Task GetAsync(string name, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public Stream GetStream(string name) => throw new NotImplementedException(); - - public IEnumerable GetAvailableResources() => throw new NotImplementedException(); - - public Task GetAsync(string name, WrapMode wrapModeS, WrapMode wrapModeT, CancellationToken cancellationToken = default) => throw new NotImplementedException(); - - public class Glyph : ITexturedCharacterGlyph - { - public float XOffset => default; - public float YOffset => default; - public float XAdvance => default; - public float Baseline => default; - public char Character => default; - - public float GetKerning(T lastGlyph) where T : ICharacterGlyph => throw new NotImplementedException(); - - public Texture Texture { get; } - public float Width => Texture.Width; - public float Height => Texture.Height; - - public Glyph(Texture texture) - { - Texture = texture; - } - } - } - } -} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b4ad21f045..5b17dc13c2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -478,7 +478,6 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Bold"); AddFont(Resources, @"Fonts/Venera/Venera-Black"); - Fonts.AddStore(new HexaconsIcons.HexaconsStore(Textures)); Fonts.AddStore(new OsuIcon.OsuIconStore(Textures)); } From 655528a5370fe83b05452e220d22103824bfecc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:04:13 +0900 Subject: [PATCH 092/139] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index dd2393ff21..c7e0cc3808 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + From 5f7f1f771d2404e05a7d152d73e893e3d1c54cc0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:09:20 +0900 Subject: [PATCH 093/139] Reword tooltip text for dashboard --- osu.Game/Localisation/NamedOverlayComponentStrings.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Localisation/NamedOverlayComponentStrings.cs b/osu.Game/Localisation/NamedOverlayComponentStrings.cs index 475bea2a4a..72e63d699a 100644 --- a/osu.Game/Localisation/NamedOverlayComponentStrings.cs +++ b/osu.Game/Localisation/NamedOverlayComponentStrings.cs @@ -20,12 +20,12 @@ namespace osu.Game.Localisation public static LocalisableString ChangelogDescription => new TranslatableString(getKey(@"changelog_description"), @"track recent dev updates in the osu! ecosystem"); /// - /// "view your friends and other information" + /// "view your friends and spectate other players" /// - public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and other information"); + public static LocalisableString DashboardDescription => new TranslatableString(getKey(@"dashboard_description"), @"view your friends and spectate other players"); /// - /// "find out who's the best right now" + /// "find out who's the best right now" /// public static LocalisableString RankingsDescription => new TranslatableString(getKey(@"rankings_description"), @"find out who's the best right now"); @@ -39,6 +39,6 @@ namespace osu.Game.Localisation /// public static LocalisableString WikiDescription => new TranslatableString(getKey(@"wiki_description"), @"knowledge base"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } From 1f55ef211eda02b867327746313a32d599645d17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:11:27 +0900 Subject: [PATCH 094/139] Rearrange buttons --- osu.Game/Overlays/Toolbar/Toolbar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 93294a9d30..ec1238ad1f 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -164,11 +164,11 @@ namespace osu.Game.Overlays.Toolbar { new ToolbarNewsButton(), new ToolbarChangelogButton(), + new ToolbarWikiButton(), new ToolbarRankingsButton(), new ToolbarBeatmapListingButton(), new ToolbarChatButton(), new ToolbarSocialButton(), - new ToolbarWikiButton(), new ToolbarMusicButton(), //new ToolbarButton //{ From d4423d493364065baa7984acda63884e26e8a98d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 18:38:29 +0100 Subject: [PATCH 095/139] Store last set score to a `SessionStatic` --- osu.Game/Configuration/SessionStatics.cs | 7 +++++++ osu.Game/Scoring/ScoreInfo.cs | 1 + osu.Game/Screens/Play/SubmittingPlayer.cs | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 8f0a60b23d..1548b781a7 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -9,6 +9,7 @@ using osu.Game.Input; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Scoring; namespace osu.Game.Configuration { @@ -27,6 +28,7 @@ namespace osu.Game.Configuration SetDefault(Static.LastModSelectPanelSamplePlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); SetDefault(Static.TouchInputActive, RuntimeInfo.IsMobile); + SetDefault(Static.LastLocalUserScore, null); } /// @@ -73,5 +75,10 @@ namespace osu.Game.Configuration /// Used in touchscreen detection scenarios (). /// TouchInputActive, + + /// + /// Stores the local user's last score (can be completed or aborted). + /// + LastLocalUserScore, } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7071bd380e..44795c6fa7 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -207,6 +207,7 @@ namespace osu.Game.Scoring clone.Statistics = new Dictionary(clone.Statistics); clone.MaximumStatistics = new Dictionary(clone.MaximumStatistics); + clone.HitEvents = new List(clone.HitEvents); // Ensure we have fresh mods to avoid any references (ie. after gameplay). clone.clearAllMods(); diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index f88526b8f9..ff2c57bf35 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; @@ -37,6 +38,9 @@ namespace osu.Game.Screens.Play [Resolved] private SpectatorClient spectatorClient { get; set; } + [Resolved] + private SessionStatics statics { get; set; } + private TaskCompletionSource scoreSubmissionSource; protected SubmittingPlayer(PlayerConfiguration configuration = null) @@ -176,6 +180,7 @@ namespace osu.Game.Screens.Play { bool exiting = base.OnExiting(e); submitFromFailOrQuit(); + statics.SetValue(Static.LastLocalUserScore, Score.ScoreInfo.DeepClone()); return exiting; } From 1b7af989ec68e06b10c1a014e37c4a56f47cd1c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 19:14:17 +0100 Subject: [PATCH 096/139] Migrate `BeatmapOffsetControl` to use session static directly --- .../Gameplay/TestSceneBeatmapOffsetControl.cs | 28 ++++++++++++++++--- osu.Game/Screens/Play/PlayerLoader.cs | 4 --- .../Play/PlayerSettings/AudioSettings.cs | 7 +++-- .../PlayerSettings/BeatmapOffsetControl.cs | 4 +++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs index f3701b664c..83fc5c2013 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapOffsetControl.cs @@ -12,6 +12,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Play.PlayerSettings; +using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.Ranking; namespace osu.Game.Tests.Visual.Gameplay @@ -44,7 +45,23 @@ namespace osu.Game.Tests.Visual.Gameplay { offsetControl.ReferenceScore.Value = new ScoreInfo { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2) + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(0, 2), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + }; + }); + + AddAssert("No calibration button", () => !offsetControl.ChildrenOfType().Any()); + } + + [Test] + public void TestScoreFromDifferentBeatmap() + { + AddStep("Set short reference score", () => + { + offsetControl.ReferenceScore.Value = new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10), + BeatmapInfo = TestResources.CreateTestBeatmapSetInfo().Beatmaps.First(), }; }); @@ -59,7 +76,8 @@ namespace osu.Game.Tests.Visual.Gameplay offsetControl.ReferenceScore.Value = new ScoreInfo { HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(10), - Mods = new Mod[] { new OsuModRelax() } + Mods = new Mod[] { new OsuModRelax() }, + BeatmapInfo = Beatmap.Value.BeatmapInfo, }; }); @@ -77,7 +95,8 @@ namespace osu.Game.Tests.Visual.Gameplay { offsetControl.ReferenceScore.Value = new ScoreInfo { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error), + BeatmapInfo = Beatmap.Value.BeatmapInfo, }; }); @@ -105,7 +124,8 @@ namespace osu.Game.Tests.Visual.Gameplay { offsetControl.ReferenceScore.Value = new ScoreInfo { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error) + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(average_error), + BeatmapInfo = Beatmap.Value.BeatmapInfo, }; }); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 681189d184..232de53ac3 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -263,10 +263,6 @@ namespace osu.Game.Screens.Play Debug.Assert(CurrentPlayer != null); - var lastScore = CurrentPlayer.Score; - - AudioSettings.ReferenceScore.Value = lastScore?.ScoreInfo; - // prepare for a retry. CurrentPlayer = null; playerConsumed = false; diff --git a/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs index 010d8115fa..3c79721590 100644 --- a/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs +++ b/osu.Game/Screens/Play/PlayerSettings/AudioSettings.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.PlayerSettings { public partial class AudioSettings : PlayerSettingsGroup { - public Bindable ReferenceScore { get; } = new Bindable(); + private Bindable referenceScore { get; } = new Bindable(); private readonly PlayerCheckbox beatmapHitsoundsToggle; @@ -26,15 +26,16 @@ namespace osu.Game.Screens.Play.PlayerSettings beatmapHitsoundsToggle = new PlayerCheckbox { LabelText = SkinSettingsStrings.BeatmapHitsounds }, new BeatmapOffsetControl { - ReferenceScore = { BindTarget = ReferenceScore }, + ReferenceScore = { BindTarget = referenceScore }, }, }; } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, SessionStatics statics) { beatmapHitsoundsToggle.Current = config.GetBindable(OsuSetting.BeatmapHitsounds); + statics.BindWith(Static.LastLocalUserScore, referenceScore); } } } diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index b0e7d08699..3f0f0fd1df 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; @@ -174,6 +175,9 @@ namespace osu.Game.Screens.Play.PlayerSettings if (score.NewValue == null) return; + if (!score.NewValue.BeatmapInfo.AsNonNull().Equals(beatmap.Value.BeatmapInfo)) + return; + if (score.NewValue.Mods.Any(m => !m.UserPlayable || m is IHasNoTimedInputs)) return; From 70aa067eb166477f29b31495da6b1a8121a0bf8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:23:04 +0900 Subject: [PATCH 097/139] Adjust gradient visibility and transition --- osu.Game/Overlays/Toolbar/Toolbar.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index ec1238ad1f..52fad2ba3b 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -224,9 +224,9 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, Alpha = 0, - Height = 100, + Height = 80, Colour = ColourInfo.GradientVertical( - OsuColour.Gray(0).Opacity(0.9f), OsuColour.Gray(0).Opacity(0)), + OsuColour.Gray(0f).Opacity(0.7f), OsuColour.Gray(0).Opacity(0)), }, }; } @@ -241,9 +241,9 @@ namespace osu.Game.Overlays.Toolbar private void updateState() { if (ShowGradient.Value) - gradientBackground.FadeIn(transition_time, Easing.OutQuint); + gradientBackground.FadeIn(2500, Easing.OutQuint); else - gradientBackground.FadeOut(transition_time, Easing.OutQuint); + gradientBackground.FadeOut(200, Easing.OutQuint); } } From 92c4c20a51e544a27b745932334e6442d5d5d994 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 03:43:38 +0900 Subject: [PATCH 098/139] Adjust paddings and fills of toolbar buttons --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 45 ++++++++++--------- osu.Game/Overlays/Toolbar/ToolbarClock.cs | 34 +++++++++----- .../Toolbar/ToolbarOverlayToggleButton.cs | 2 +- .../Toolbar/ToolbarRulesetSelector.cs | 1 + .../Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 5 files changed, 51 insertions(+), 33 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index a547fda814..bd5faf1588 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -7,7 +7,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; @@ -74,6 +73,8 @@ namespace osu.Game.Overlays.Toolbar private readonly SpriteText keyBindingTooltip; protected FillFlowContainer Flow; + protected readonly Container BackgroundContent; + [Resolved] private RealmAccess realm { get; set; } = null!; @@ -82,21 +83,33 @@ namespace osu.Game.Overlays.Toolbar Width = Toolbar.HEIGHT; RelativeSizeAxes = Axes.Y; + Padding = new MarginPadding(3); + Children = new Drawable[] { - HoverBackground = new Box + BackgroundContent = new Container { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + HoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, Flow = new FillFlowContainer { @@ -219,14 +232,6 @@ namespace osu.Game.Overlays.Toolbar public OpaqueBackground() { RelativeSizeAxes = Axes.Both; - Masking = true; - MaskingSmoothness = 0; - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(40), - Radius = 5, - }; Children = new Drawable[] { diff --git a/osu.Game/Overlays/Toolbar/ToolbarClock.cs b/osu.Game/Overlays/Toolbar/ToolbarClock.cs index f1310d8535..67688155ae 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarClock.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarClock.cs @@ -42,21 +42,33 @@ namespace osu.Game.Overlays.Toolbar clockDisplayMode = config.GetBindable(OsuSetting.ToolbarClockDisplayMode); prefer24HourTime = config.GetBindable(OsuSetting.Prefer24HourTime); + Padding = new MarginPadding(3); + Children = new Drawable[] { - hoverBackground = new Box + new Container { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), - Blending = BlendingParameters.Additive, - Alpha = 0, - }, - flashBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Colour = Color4.White.Opacity(100), - Blending = BlendingParameters.Additive, + Masking = true, + CornerRadius = 6, + CornerExponent = 3f, + Children = new Drawable[] + { + hoverBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + flashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Colour = Color4.White.Opacity(100), + Blending = BlendingParameters.Additive, + }, + } }, new FillFlowContainer { diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 78c976111b..37038161e1 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Toolbar public ToolbarOverlayToggleButton() { - Add(stateBackground = new Box + BackgroundContent.Add(stateBackground = new Box { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(150).Opacity(180), diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 715076b368..63d11c1b9e 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -41,6 +41,7 @@ namespace osu.Game.Overlays.Toolbar new OpaqueBackground { Depth = 1, + Masking = true, }, ModeButtonLine = new Container { diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 028decea1e..7d1b6c7404 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Overlays.Toolbar [BackgroundDependencyLoader] private void load(OsuColour colours, IAPIProvider api, LoginOverlay? login) { - Add(new OpaqueBackground { Depth = 1 }); + BackgroundContent.Add(new OpaqueBackground { Depth = 1 }); Flow.Add(new Container { From cf5e3e886386737385fe75cf1642e75279c7ff16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 04:06:29 +0900 Subject: [PATCH 099/139] Breathe some colour and life into the toolbar --- .../Toolbar/ToolbarOverlayToggleButton.cs | 8 ++-- .../Toolbar/ToolbarRulesetSelector.cs | 43 +++++++++---------- .../Toolbar/ToolbarRulesetTabButton.cs | 30 +++++++------ 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 37038161e1..09b8df14a6 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -3,6 +3,7 @@ #nullable disable +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -14,7 +15,7 @@ namespace osu.Game.Overlays.Toolbar { public partial class ToolbarOverlayToggleButton : ToolbarButton { - private readonly Box stateBackground; + private Box stateBackground; private OverlayContainer stateContainer; @@ -44,12 +45,13 @@ namespace osu.Game.Overlays.Toolbar } } - public ToolbarOverlayToggleButton() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { BackgroundContent.Add(stateBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(150).Opacity(180), + Colour = colours.Carmine.Opacity(180), Blending = BlendingParameters.Additive, Depth = 2, Alpha = 0, diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs index 63d11c1b9e..723c24597a 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetSelector.cs @@ -4,20 +4,19 @@ #nullable disable using System.Collections.Generic; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osuTK; -using osuTK.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osuTK.Input; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Overlays.Toolbar { @@ -47,20 +46,18 @@ namespace osu.Game.Overlays.Toolbar { Size = new Vector2(Toolbar.HEIGHT, 3), Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - Masking = true, - EdgeEffect = new EdgeEffectParameters + Origin = Anchor.BottomLeft, + Y = -1, + Children = new Drawable[] { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(18, 3), + } } - } + }, }); foreach (var ruleset in Rulesets.AvailableRulesets) @@ -90,7 +87,7 @@ namespace osu.Game.Overlays.Toolbar { if (SelectedTab != null) { - ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 200, Easing.OutQuint); + ModeButtonLine.MoveToX(SelectedTab.DrawPosition.X, !hasInitialPosition ? 0 : 500, Easing.OutElasticQuarter); if (hasInitialPosition) selectionSamples[SelectedTab.Value.ShortName]?.Play(); diff --git a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs index 74f76c7c89..5500f1c879 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarRulesetTabButton.cs @@ -1,14 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Rulesets; -using osuTK.Graphics; namespace osu.Game.Overlays.Toolbar { @@ -41,27 +43,31 @@ namespace osu.Game.Overlays.Toolbar { protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(); + [Resolved] + private OsuColour colours { get; set; } = null!; + + public RulesetButton() + { + Padding = new MarginPadding(3) + { + Bottom = 5 + }; + } + public bool Active { - set + set => Scheduler.AddOnce(() => { if (value) { - IconContainer.Colour = Color4.White; - IconContainer.EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = new Color4(255, 194, 224, 100), - Radius = 15, - Roundness = 15, - }; + IconContainer.Colour = Color4Extensions.FromHex("#00FFAA"); } else { - IconContainer.Colour = new Color4(255, 194, 224, 255); + IconContainer.Colour = colours.GrayF; IconContainer.EdgeEffect = new EdgeEffectParameters(); } - } + }); } protected override bool OnClick(ClickEvent e) From f51b5f5487e770b783cc12513865eb1d5a3462d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 19:43:14 +0100 Subject: [PATCH 100/139] Add components to track average hit errors across session --- osu.Game/OsuGameBase.cs | 2 + .../Audio/AudioOffsetAdjustControl.cs | 23 ++++++++ .../Audio/SessionAverageHitErrorTracker.cs | 55 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs create mode 100644 osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 48548dc1ef..64f15efb15 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -55,6 +55,7 @@ using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; +using osu.Game.Overlays.Settings.Sections.Audio; using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Resources; using osu.Game.Rulesets; @@ -349,6 +350,7 @@ namespace osu.Game dependencies.CacheAs(powerStatus); dependencies.Cache(SessionStatics = new SessionStatics()); + dependencies.Cache(new SessionAverageHitErrorTracker()); dependencies.Cache(Colours = new OsuColour()); RegisterImportHandler(BeatmapManager); diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs new file mode 100644 index 0000000000..045cd24fc0 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . 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.Containers; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + public partial class AudioOffsetAdjustControl : SettingsItem + { + [BackgroundDependencyLoader] + private void load() + { + } + + protected override Drawable CreateControl() => new AudioOffsetPreview(); + + private partial class AudioOffsetPreview : CompositeDrawable + { + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs b/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs new file mode 100644 index 0000000000..b714d49b89 --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Overlays.Settings.Sections.Audio +{ + /// + /// Tracks the local user's average hit error during the ongoing play session. + /// + [Cached] + public partial class SessionAverageHitErrorTracker : Component + { + public IBindableList AverageHitErrorHistory => averageHitErrorHistory; + private readonly BindableList averageHitErrorHistory = new BindableList(); + + private readonly Bindable latestScore = new Bindable(); + + [BackgroundDependencyLoader] + private void load(SessionStatics statics) + { + statics.BindWith(Static.LastLocalUserScore, latestScore); + latestScore.BindValueChanged(score => calculateAverageHitError(score.NewValue), true); + } + + private void calculateAverageHitError(ScoreInfo? newScore) + { + if (newScore == null) + return; + + if (newScore.Mods.Any(m => !m.UserPlayable || m is IHasNoTimedInputs)) + return; + + if (newScore.HitEvents.Count < 10) + return; + + if (newScore.HitEvents.CalculateAverageHitError() is not double averageError) + return; + + // keep a sane maximum number of entries. + if (averageHitErrorHistory.Count >= 50) + averageHitErrorHistory.RemoveAt(0); + averageHitErrorHistory.Add(averageError); + } + + public void ClearHistory() => averageHitErrorHistory.Clear(); + } +} From 160342ceed4824a3214157c049ff981e2710568f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 21:14:37 +0100 Subject: [PATCH 101/139] Implement automatic suggestion of audio offset based on last plays --- .../TestSceneAudioOffsetAdjustControl.cs | 63 ++++++++ osu.Game/OsuGameBase.cs | 5 +- .../Audio/AudioOffsetAdjustControl.cs | 138 +++++++++++++++++- .../Settings/Sections/Audio/OffsetSettings.cs | 5 +- 4 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs diff --git a/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs new file mode 100644 index 0000000000..efb65bb0a8 --- /dev/null +++ b/osu.Game.Tests/Visual/Settings/TestSceneAudioOffsetAdjustControl.cs @@ -0,0 +1,63 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Overlays.Settings.Sections.Audio; +using osu.Game.Scoring; +using osu.Game.Tests.Visual.Ranking; + +namespace osu.Game.Tests.Visual.Settings +{ + public partial class TestSceneAudioOffsetAdjustControl : OsuTestScene + { + [Resolved] + private SessionStatics statics { get; set; } = null!; + + [Cached] + private SessionAverageHitErrorTracker tracker = new SessionAverageHitErrorTracker(); + + private Container content = null!; + protected override Container Content => content; + + [BackgroundDependencyLoader] + private void load() + { + base.Content.AddRange(new Drawable[] + { + tracker, + content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 400, + AutoSizeAxes = Axes.Y + } + }); + } + + [Test] + public void TestBehaviour() + { + AddStep("create control", () => Child = new AudioOffsetAdjustControl + { + Current = new BindableDouble + { + MinValue = -500, + MaxValue = 500 + } + }); + AddStep("set new score", () => statics.SetValue(Static.LastLocalUserScore, new ScoreInfo + { + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(RNG.NextDouble(-100, 100)), + BeatmapInfo = Beatmap.Value.BeatmapInfo, + })); + AddStep("clear history", () => tracker.ClearHistory()); + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 64f15efb15..0d8a2fbb97 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -201,6 +201,8 @@ namespace osu.Game private RulesetConfigCache rulesetConfigCache; + private SessionAverageHitErrorTracker hitErrorTracker; + protected SpectatorClient SpectatorClient { get; private set; } protected MultiplayerClient MultiplayerClient { get; private set; } @@ -350,7 +352,7 @@ namespace osu.Game dependencies.CacheAs(powerStatus); dependencies.Cache(SessionStatics = new SessionStatics()); - dependencies.Cache(new SessionAverageHitErrorTracker()); + dependencies.Cache(hitErrorTracker = new SessionAverageHitErrorTracker()); dependencies.Cache(Colours = new OsuColour()); RegisterImportHandler(BeatmapManager); @@ -410,6 +412,7 @@ namespace osu.Game }); base.Content.Add(new TouchInputInterceptor()); + base.Content.Add(hitErrorTracker); KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider); KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets); diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 045cd24fc0..0023c60c61 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -1,9 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; +using osuTK; namespace osu.Game.Overlays.Settings.Sections.Audio { @@ -12,12 +26,134 @@ namespace osu.Game.Overlays.Settings.Sections.Audio [BackgroundDependencyLoader] private void load() { + LabelText = AudioSettingsStrings.AudioOffset; } protected override Drawable CreateControl() => new AudioOffsetPreview(); - private partial class AudioOffsetPreview : CompositeDrawable + private partial class AudioOffsetPreview : CompositeDrawable, IHasCurrentValue { + public Bindable Current + { + get => current.Current; + set => current.Current = value; + } + + private readonly BindableNumberWithCurrent current = new BindableNumberWithCurrent(); + + private readonly IBindableList averageHitErrorHistory = new BindableList(); + + private readonly Bindable suggestedOffset = new Bindable(); + + private Container notchContainer = null!; + private TextFlowContainer hintText = null!; + private RoundedButton applySuggestion = null!; + + [BackgroundDependencyLoader] + private void load(SessionAverageHitErrorTracker hitErrorTracker) + { + averageHitErrorHistory.BindTo(hitErrorTracker.AverageHitErrorHistory); + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new TimeSlider + { + RelativeSizeAxes = Axes.X, + Current = { BindTarget = Current }, + KeyboardStep = 1, + }, + notchContainer = new Container + { + RelativeSizeAxes = Axes.X, + Height = 10, + Padding = new MarginPadding { Horizontal = Nub.DEFAULT_EXPANDED_SIZE / 2 }, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + hintText = new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 16)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, + applySuggestion = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = "Apply suggested offset", + Action = () => + { + if (suggestedOffset.Value.HasValue) + current.Value = suggestedOffset.Value.Value; + hitErrorTracker.ClearHistory(); + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + averageHitErrorHistory.BindCollectionChanged(updateDisplay, true); + suggestedOffset.BindValueChanged(_ => updateHintText(), true); + } + + private void updateDisplay(object? _, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (double average in e.NewItems!) + { + notchContainer.ForEach(n => n.Alpha *= 0.95f); + notchContainer.Add(new Box + { + RelativeSizeAxes = Axes.Y, + Width = 2, + RelativePositionAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = getXPositionForAverage(average) + }); + } + + break; + + case NotifyCollectionChangedAction.Remove: + foreach (double average in e.OldItems!) + { + var notch = notchContainer.FirstOrDefault(n => n.X == getXPositionForAverage(average)); + Debug.Assert(notch != null); + notchContainer.Remove(notch, true); + } + + break; + + case NotifyCollectionChangedAction.Reset: + notchContainer.Clear(); + break; + } + + suggestedOffset.Value = averageHitErrorHistory.Count < 3 ? null : -averageHitErrorHistory.Average(); + } + + private float getXPositionForAverage(double average) => (float)(Math.Clamp(-average, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); + + private void updateHintText() + { + hintText.Text = suggestedOffset.Value == null + ? @"Play a few beatmaps to receive a suggested offset!" + : $@"Based on the last {averageHitErrorHistory.Count} plays, the suggested offset is {suggestedOffset.Value:N0} ms."; + applySuggestion.Enabled.Value = suggestedOffset.Value != null; + } } } } diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index 6b5c769853..af15d310fc 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.Audio @@ -23,11 +22,9 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { Children = new Drawable[] { - new SettingsSlider + new AudioOffsetAdjustControl { - LabelText = AudioSettingsStrings.AudioOffset, Current = config.GetBindable(OsuSetting.AudioOffset), - KeyboardStep = 1f }, new SettingsButton { From cf39bb7a18877055a58324d542b9c4feb83d1611 Mon Sep 17 00:00:00 2001 From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:55:20 -0800 Subject: [PATCH 102/139] Fix spinner max bonus not respecting ISamplePlaybackDisabler The spinner max bonus was loaded through SkinnableSound instead of PausableSkinnableSound, leading to it not respecting the case where sample playback is globally disabled through ISamplePlaybackDisabler, and can be easily heard in situations like during the catchup period after seeking using the ArgonSongProgressBar with song volume at 0 --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index f7c1437009..bf4b07eaab 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private const float spinning_sample_initial_frequency = 1.0f; private const float spinning_sample_modulated_base_frequency = 0.5f; - private SkinnableSound maxBonusSample; + private PausableSkinnableSound maxBonusSample; /// /// The amount of bonus score gained from spinning after the required number of spins, for display purposes. @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Looping = true, Frequency = { Value = spinning_sample_initial_frequency } }, - maxBonusSample = new SkinnableSound + maxBonusSample = new PausableSkinnableSound { MinimumSampleVolume = MINIMUM_SAMPLE_VOLUME, } From d9299a8a55bff6757358db2d46b38806817f974a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 23:07:17 +0100 Subject: [PATCH 103/139] Implement visual appearance of "system title" message in main menu --- .../Visual/Menus/TestSceneMainMenu.cs | 30 ++++++ .../API/Requests/Responses/APISystemTitle.cs | 16 +++ osu.Game/OsuGame.cs | 5 +- osu.Game/Screens/Menu/MainMenu.cs | 15 +++ osu.Game/Screens/Menu/SystemTitle.cs | 101 ++++++++++++++++++ 5 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APISystemTitle.cs create mode 100644 osu.Game/Screens/Menu/SystemTitle.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs new file mode 100644 index 0000000000..85aa14f33e --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Testing; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneMainMenu : OsuGameTestScene + { + [Test] + public void TestSystemTitle() + { + AddStep("set system title", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + { + Image = @"https://assets.ppy.sh/main-menu/project-loved-2@2x.png", + Url = @"https://osu.ppy.sh/home/news/2023-12-21-project-loved-december-2023", + }); + AddStep("set another title", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + { + Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", + Url = @"https://osu.ppy.sh/community/contests/189", + }); + AddStep("unset system title", () => Game.ChildrenOfType().Single().Current.Value = null); + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs new file mode 100644 index 0000000000..3a2342cc4c --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -0,0 +1,16 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APISystemTitle + { + [JsonProperty(@"image")] + public string Image { get; set; } = string.Empty; + + [JsonProperty(@"url")] + public string Url { get; set; } = string.Empty; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e7ff99ef01..37996e8832 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -995,7 +995,10 @@ namespace osu.Game }, topMostOverlayContent.Add); if (!args?.Any(a => a == @"--no-version-overlay") ?? true) - loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); + { + dependencies.Cache(versionManager = new VersionManager { Depth = int.MinValue }); + loadComponentSingleFile(versionManager, ScreenContainer.Add); + } loadComponentSingleFile(osuLogo, _ => { diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c25e62d69e..0739689daa 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -76,6 +76,9 @@ namespace osu.Game.Screens.Menu [Resolved(canBeNull: true)] private IDialogOverlay dialogOverlay { get; set; } + [Resolved(canBeNull: true)] + private VersionManager versionManager { get; set; } + protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(); protected override bool PlayExitSound => false; @@ -91,6 +94,7 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; private Container logoTarget; + private SystemTitle systemTitle; private Sample reappearSampleSwoosh; @@ -153,6 +157,7 @@ namespace osu.Game.Screens.Menu Margin = new MarginPadding { Right = 15, Top = 5 } }, new KiaiMenuFountains(), + systemTitle = new SystemTitle(), holdToExitGameOverlay?.CreateProxy() ?? Empty() }); @@ -263,6 +268,16 @@ namespace osu.Game.Screens.Menu } } + protected override void Update() + { + base.Update(); + + systemTitle.Margin = new MarginPadding + { + Bottom = (versionManager?.DrawHeight + 5) ?? 0 + }; + } + protected override void LogoSuspending(OsuLogo logo) { var seq = logo.FadeOut(300, Easing.InSine) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs new file mode 100644 index 0000000000..6ca9aab6c7 --- /dev/null +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Framework.Platform; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Screens.Menu +{ + public partial class SystemTitle : CompositeDrawable + { + internal Bindable Current { get; } = new Bindable(); + + private Container content = null!; + private CancellationTokenSource? cancellationTokenSource; + private SystemTitleImage? currentImage; + + [BackgroundDependencyLoader] + private void load(GameHost? gameHost) + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + AutoSizeAxes = Axes.Both; + + InternalChild = content = new ClickableContainer + { + AutoSizeAxes = Axes.Both, + Action = () => + { + if (!string.IsNullOrEmpty(Current.Value?.Url)) + gameHost?.OpenUrlExternally(Current.Value.Url); + } + }; + } + + protected override bool OnHover(HoverEvent e) + { + content.ScaleTo(1.1f, 500, Easing.OutBounce); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + content.ScaleTo(1f, 500, Easing.OutBounce); + base.OnHoverLost(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(_ => loadNewImage(), true); + } + + private void loadNewImage() + { + cancellationTokenSource?.Cancel(); + cancellationTokenSource = null; + currentImage?.FadeOut(500, Easing.OutQuint).Expire(); + + if (string.IsNullOrEmpty(Current.Value?.Image)) + return; + + LoadComponentAsync(new SystemTitleImage(Current.Value), loaded => + { + if (loaded.SystemTitle != Current.Value) + loaded.Dispose(); + + loaded.FadeInFromZero(500, Easing.OutQuint); + content.Add(currentImage = loaded); + }, (cancellationTokenSource ??= new CancellationTokenSource()).Token); + } + + [LongRunningLoad] + private partial class SystemTitleImage : Sprite + { + public readonly APISystemTitle SystemTitle; + + public SystemTitleImage(APISystemTitle systemTitle) + { + SystemTitle = systemTitle; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textureStore) + { + var texture = textureStore.Get(SystemTitle.Image); + if (SystemTitle.Image.Contains(@"@2x")) + texture.ScaleAdjust *= 2; + Texture = texture; + } + } + } +} From a3f720bc627f27d6bcc9fe36c011190010930b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 23:31:14 +0100 Subject: [PATCH 104/139] Retrieve system title from online source --- .../API/Requests/GetSystemTitleRequest.cs | 15 ++++++++++++ .../API/Requests/Responses/APISystemTitle.cs | 2 +- osu.Game/Screens/Menu/SystemTitle.cs | 23 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/API/Requests/GetSystemTitleRequest.cs diff --git a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs new file mode 100644 index 0000000000..52ca0c11eb --- /dev/null +++ b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Online.API.Requests +{ + public class GetSystemTitleRequest : OsuJsonWebRequest + { + public GetSystemTitleRequest() + : base(@"https://assets.ppy.sh/lazer-status.json") + { + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs index 3a2342cc4c..97bdc8355a 100644 --- a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -5,7 +5,7 @@ using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { - public class APISystemTitle + public record APISystemTitle { [JsonProperty(@"image")] public string Image { get; set; } = string.Empty; diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 6ca9aab6c7..0867fc4748 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,6 +12,7 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Platform; +using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Screens.Menu @@ -57,6 +60,26 @@ namespace osu.Game.Screens.Menu base.LoadComplete(); Current.BindValueChanged(_ => loadNewImage(), true); + + checkForUpdates(); + Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds, true); + } + + private void checkForUpdates() + { + var request = new GetSystemTitleRequest(); + Task.Run(() => request.Perform()) + .ContinueWith(r => + { + if (r.IsCompletedSuccessfully) + Schedule(() => Current.Value = request.ResponseObject); + + // if the request failed, "observe" the exception. + // it isn't very important why this failed, as it's only for display. + // the inner error will be logged by framework mechanisms anyway. + if (r.IsFaulted) + _ = r.Exception; + }); } private void loadNewImage() From ac449131edcee95d9ca61fef538aeea7010c128d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 27 Dec 2023 23:47:37 +0100 Subject: [PATCH 105/139] CodeFileSanity does not like records in standalone files --- .../API/Requests/Responses/APISystemTitle.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs index 97bdc8355a..6d5d91f8f3 100644 --- a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -1,16 +1,29 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using Newtonsoft.Json; namespace osu.Game.Online.API.Requests.Responses { - public record APISystemTitle + public class APISystemTitle : IEquatable { [JsonProperty(@"image")] public string Image { get; set; } = string.Empty; [JsonProperty(@"url")] public string Url { get; set; } = string.Empty; + + public bool Equals(APISystemTitle? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Image == other.Image && Url == other.Url; + } + + public override bool Equals(object? obj) => obj is APISystemTitle other && Equals(other); + + public override int GetHashCode() => HashCode.Combine(Image, Url); } } From ef3975981376e7308f4156024929f404f9240504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 00:09:51 +0100 Subject: [PATCH 106/139] More code quality inspections --- osu.Game/Online/API/Requests/Responses/APISystemTitle.cs | 1 + osu.Game/Screens/Menu/SystemTitle.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs index 6d5d91f8f3..bfa5c1043b 100644 --- a/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs +++ b/osu.Game/Online/API/Requests/Responses/APISystemTitle.cs @@ -24,6 +24,7 @@ namespace osu.Game.Online.API.Requests.Responses public override bool Equals(object? obj) => obj is APISystemTitle other && Equals(other); + // ReSharper disable NonReadonlyMemberInGetHashCode public override int GetHashCode() => HashCode.Combine(Image, Url); } } diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 0867fc4748..6b976a8ed6 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -93,7 +93,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new SystemTitleImage(Current.Value), loaded => { - if (loaded.SystemTitle != Current.Value) + if (!loaded.SystemTitle.Equals(Current.Value)) loaded.Dispose(); loaded.FadeInFromZero(500, Easing.OutQuint); From f8d6b8e347c18e2bf0bec85cdf76aac6f0f5d7a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:10:52 +0900 Subject: [PATCH 107/139] Adjust toolbar animations / layering to feel better --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index bd5faf1588..03a1cfc005 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -183,7 +183,7 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnClick(ClickEvent e) { - flashBackground.FadeOutFromOne(800, Easing.OutQuint); + flashBackground.FadeIn(50).Then().FadeOutFromOne(800, Easing.OutQuint); tooltipContainer.FadeOut(100); return base.OnClick(e); } diff --git a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs index 09b8df14a6..06755a9da9 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarOverlayToggleButton.cs @@ -53,7 +53,7 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.Both, Colour = colours.Carmine.Opacity(180), Blending = BlendingParameters.Additive, - Depth = 2, + Depth = float.MaxValue, Alpha = 0, }); @@ -65,11 +65,11 @@ namespace osu.Game.Overlays.Toolbar switch (state.NewValue) { case Visibility.Hidden: - stateBackground.FadeOut(200); + stateBackground.FadeOut(200, Easing.OutQuint); break; case Visibility.Visible: - stateBackground.FadeIn(200); + stateBackground.FadeIn(200, Easing.OutQuint); break; } } From ffc8778d673dd54a650066b9dc47d6252367da15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:13:35 +0900 Subject: [PATCH 108/139] Fix song select leaderboard tab ordering not matching stable --- .../Select/Leaderboards/BeatmapLeaderboardScope.cs | 6 +++--- osu.Game/Screens/Select/PlayBeatmapDetailArea.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs index 5bcb4c27a7..e2e3404877 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboardScope.cs @@ -12,12 +12,12 @@ namespace osu.Game.Screens.Select.Leaderboards [Description("Local Ranking")] Local, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardCountry))] - Country, - [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardGlobal))] Global, + [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardCountry))] + Country, + [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowScoreboardFriend))] Friend, } diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index 8a1b9ef3e1..deb1100dfc 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -80,8 +80,8 @@ namespace osu.Game.Screens.Select protected override BeatmapDetailAreaTabItem[] CreateTabItems() => base.CreateTabItems().Concat(new BeatmapDetailAreaTabItem[] { new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local), - new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country), new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Global), + new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country), new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Friend), }).ToArray(); @@ -95,12 +95,12 @@ namespace osu.Game.Screens.Select case TabType.Local: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local); - case TabType.Country: - return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country); - case TabType.Global: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Global); + case TabType.Country: + return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Country); + case TabType.Friends: return new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Friend); From 93a8afe96e101a63f65e6bcca92d9707c1fde767 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:40:10 +0900 Subject: [PATCH 109/139] Add very simple cache-busting (30 minutes) --- osu.Game/Online/API/Requests/GetSystemTitleRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs index 52ca0c11eb..659e46bb11 100644 --- a/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs +++ b/osu.Game/Online/API/Requests/GetSystemTitleRequest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -8,7 +9,7 @@ namespace osu.Game.Online.API.Requests public class GetSystemTitleRequest : OsuJsonWebRequest { public GetSystemTitleRequest() - : base(@"https://assets.ppy.sh/lazer-status.json") + : base($@"https://assets.ppy.sh/lazer-status.json?{DateTimeOffset.UtcNow.ToUnixTimeSeconds() / 1800}") { } } From c70e7d340da905d081eb87863ea9588021816453 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:48:17 +0900 Subject: [PATCH 110/139] Adjust animation and add delay to URL open --- osu.Game/Screens/Menu/SystemTitle.cs | 37 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 6b976a8ed6..2c8cb90b96 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -12,6 +12,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Platform; +using osu.Framework.Threading; +using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -25,6 +27,8 @@ namespace osu.Game.Screens.Menu private CancellationTokenSource? cancellationTokenSource; private SystemTitleImage? currentImage; + private ScheduledDelegate? openUrlAction; + [BackgroundDependencyLoader] private void load(GameHost? gameHost) { @@ -32,29 +36,52 @@ namespace osu.Game.Screens.Menu Origin = Anchor.BottomCentre; AutoSizeAxes = Axes.Both; - InternalChild = content = new ClickableContainer + InternalChild = content = new OsuClickableContainer { AutoSizeAxes = Axes.Both, Action = () => { - if (!string.IsNullOrEmpty(Current.Value?.Url)) - gameHost?.OpenUrlExternally(Current.Value.Url); + // Delay slightly to allow animation to play out. + openUrlAction?.Cancel(); + openUrlAction = Scheduler.AddDelayed(() => + { + if (!string.IsNullOrEmpty(Current.Value?.Url)) + gameHost?.OpenUrlExternally(Current.Value.Url); + }, 250); } }; } protected override bool OnHover(HoverEvent e) { - content.ScaleTo(1.1f, 500, Easing.OutBounce); + content.ScaleTo(1.05f, 2000, Easing.OutQuint); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - content.ScaleTo(1f, 500, Easing.OutBounce); + content.ScaleTo(1f, 500, Easing.OutQuint); base.OnHoverLost(e); } + protected override bool OnClick(ClickEvent e) + { + //hover.FlashColour(FlashColour, 800, Easing.OutQuint); + return base.OnClick(e); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + content.ScaleTo(0.95f, 500, Easing.OutQuint); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + content.ScaleTo(1, 500, Easing.OutElastic); + base.OnMouseUp(e); + } + protected override void LoadComplete() { base.LoadComplete(); From 289e0f00f98ea570fd0a3f6b644b4399c0e4b5ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 14:58:05 +0900 Subject: [PATCH 111/139] Add flash on click --- osu.Game/Screens/Menu/SystemTitle.cs | 37 +++++++++++++++++++++------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 2c8cb90b96..667dc6e947 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -41,6 +41,8 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both, Action = () => { + currentImage?.Flash(); + // Delay slightly to allow animation to play out. openUrlAction?.Cancel(); openUrlAction = Scheduler.AddDelayed(() => @@ -64,12 +66,6 @@ namespace osu.Game.Screens.Menu base.OnHoverLost(e); } - protected override bool OnClick(ClickEvent e) - { - //hover.FlashColour(FlashColour, 800, Easing.OutQuint); - return base.OnClick(e); - } - protected override bool OnMouseDown(MouseDownEvent e) { content.ScaleTo(0.95f, 500, Easing.OutQuint); @@ -78,7 +74,9 @@ namespace osu.Game.Screens.Menu protected override void OnMouseUp(MouseUpEvent e) { - content.ScaleTo(1, 500, Easing.OutElastic); + content + .ScaleTo(0.95f) + .ScaleTo(1, 500, Easing.OutElastic); base.OnMouseUp(e); } @@ -129,10 +127,12 @@ namespace osu.Game.Screens.Menu } [LongRunningLoad] - private partial class SystemTitleImage : Sprite + private partial class SystemTitleImage : CompositeDrawable { public readonly APISystemTitle SystemTitle; + private Sprite flash = null!; + public SystemTitleImage(APISystemTitle systemTitle) { SystemTitle = systemTitle; @@ -144,7 +144,26 @@ namespace osu.Game.Screens.Menu var texture = textureStore.Get(SystemTitle.Image); if (SystemTitle.Image.Contains(@"@2x")) texture.ScaleAdjust *= 2; - Texture = texture; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Sprite { Texture = texture }, + flash = new Sprite + { + Texture = texture, + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + }; + } + + public void Flash() + { + flash.FadeInFromZero(50) + .Then() + .FadeOut(500, Easing.OutQuint); } } } From 481a25178658e80891a59bcdd7ad1720576854fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:10:05 +0900 Subject: [PATCH 112/139] Use `HandleLink` to allow potentially opening wiki or otherwise --- osu.Game/Screens/Menu/SystemTitle.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 667dc6e947..967b928b43 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests; @@ -30,7 +29,7 @@ namespace osu.Game.Screens.Menu private ScheduledDelegate? openUrlAction; [BackgroundDependencyLoader] - private void load(GameHost? gameHost) + private void load(OsuGame? game) { Anchor = Anchor.BottomCentre; Origin = Anchor.BottomCentre; @@ -48,7 +47,7 @@ namespace osu.Game.Screens.Menu openUrlAction = Scheduler.AddDelayed(() => { if (!string.IsNullOrEmpty(Current.Value?.Url)) - gameHost?.OpenUrlExternally(Current.Value.Url); + game?.HandleLink(Current.Value.Url); }, 250); } }; From 972234b1e5c1a505f23fe1491d509ab343636b4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:12:44 +0900 Subject: [PATCH 113/139] Move re-schedule inside continuation --- osu.Game/Screens/Menu/SystemTitle.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 967b928b43..b8510dde87 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -86,7 +86,6 @@ namespace osu.Game.Screens.Menu Current.BindValueChanged(_ => loadNewImage(), true); checkForUpdates(); - Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds, true); } private void checkForUpdates() @@ -103,6 +102,8 @@ namespace osu.Game.Screens.Menu // the inner error will be logged by framework mechanisms anyway. if (r.IsFaulted) _ = r.Exception; + + Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds); }); } From 0ea62d0c5d52f98c19062e8baba5e225daea8c5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:16:42 +0900 Subject: [PATCH 114/139] Add initial additive blending on fade in --- osu.Game/Screens/Menu/SystemTitle.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index b8510dde87..8b243e8f7f 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -121,7 +121,6 @@ namespace osu.Game.Screens.Menu if (!loaded.SystemTitle.Equals(Current.Value)) loaded.Dispose(); - loaded.FadeInFromZero(500, Easing.OutQuint); content.Add(currentImage = loaded); }, (cancellationTokenSource ??= new CancellationTokenSource()).Token); } @@ -154,16 +153,25 @@ namespace osu.Game.Screens.Menu { Texture = texture, Blending = BlendingParameters.Additive, - Alpha = 0, }, }; } - public void Flash() + protected override void LoadComplete() + { + base.LoadComplete(); + + this.FadeInFromZero(500, Easing.OutQuint); + flash.FadeOutFromOne(4000, Easing.OutQuint); + } + + public Drawable Flash() { flash.FadeInFromZero(50) .Then() .FadeOut(500, Easing.OutQuint); + + return this; } } } From 6684987289a6dcbd0ab918cd9976909bead57dc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 15:18:21 +0900 Subject: [PATCH 115/139] Reduce refresh interval slightly --- osu.Game/Screens/Menu/SystemTitle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 8b243e8f7f..bb623cacdf 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu if (r.IsFaulted) _ = r.Exception; - Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(15).TotalMilliseconds); + Scheduler.AddDelayed(checkForUpdates, TimeSpan.FromMinutes(5).TotalMilliseconds); }); } From a1867afbb43307ca775e56586bb6c86af038cc81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:05:20 +0900 Subject: [PATCH 116/139] Move menu tips to main menu In preparation for removing the disclaimer screen. --- osu.Game/Configuration/OsuConfigManager.cs | 2 + osu.Game/Localisation/UserInterfaceStrings.cs | 7 +- .../UserInterface/MainMenuSettings.cs | 5 ++ osu.Game/Screens/Menu/Disclaimer.cs | 30 ------- osu.Game/Screens/Menu/MainMenu.cs | 38 +++++++- osu.Game/Screens/Menu/MenuTip.cs | 89 +++++++++++++++++++ osu.Game/Screens/Menu/SystemTitle.cs | 2 - 7 files changed, 138 insertions(+), 35 deletions(-) create mode 100644 osu.Game/Screens/Menu/MenuTip.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 0df870655f..d4162b76d6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -96,6 +96,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.MenuVoice, true); SetDefault(OsuSetting.MenuMusic, true); + SetDefault(OsuSetting.MenuTips, true); SetDefault(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); @@ -350,6 +351,7 @@ namespace osu.Game.Configuration VolumeInactive, MenuMusic, MenuVoice, + MenuTips, CursorRotation, MenuParallax, Prefer24HourTime, diff --git a/osu.Game/Localisation/UserInterfaceStrings.cs b/osu.Game/Localisation/UserInterfaceStrings.cs index 68c5c3ccbc..dceedca05c 100644 --- a/osu.Game/Localisation/UserInterfaceStrings.cs +++ b/osu.Game/Localisation/UserInterfaceStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString MenuCursorSize => new TranslatableString(getKey(@"menu_cursor_size"), @"Menu cursor size"); + /// + /// "Menu tips" + /// + public static LocalisableString ShowMenuTips => new TranslatableString(getKey(@"show_menu_tips"), @"Menu tips"); + /// /// "Parallax" /// @@ -154,6 +159,6 @@ namespace osu.Game.Localisation /// public static LocalisableString TrueRandom => new TranslatableString(getKey(@"true_random"), @"True Random"); - private static string getKey(string key) => $"{prefix}:{key}"; + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 4577fadb01..5e42c3035c 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -29,6 +29,11 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface Children = new Drawable[] { + new SettingsCheckbox + { + LabelText = UserInterfaceStrings.ShowMenuTips, + Current = config.GetBindable(OsuSetting.MenuTips) + }, new SettingsCheckbox { LabelText = UserInterfaceStrings.InterfaceVoices, diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 539d58d2d7..1afef66313 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -122,10 +121,6 @@ namespace osu.Game.Screens.Menu textFlow.NewParagraph(); textFlow.NewParagraph(); - textFlow.AddParagraph("today's tip:", formatSemiBold); - textFlow.AddParagraph(getRandomTip(), formatRegular); - textFlow.NewParagraph(); - textFlow.NewParagraph(); iconColour = colours.Yellow; @@ -228,30 +223,5 @@ namespace osu.Game.Screens.Menu this.Push(nextScreen); }); } - - private string getRandomTip() - { - string[] tips = - { - "You can press Ctrl-T anywhere in the game to toggle the toolbar!", - "You can press Ctrl-O anywhere in the game to access options!", - "All settings are dynamic and take effect in real-time. Try pausing and changing the skin while playing!", - "New features are coming online every update. Make sure to stay up-to-date!", - "If you find the UI too large or small, try adjusting UI scale in settings!", - "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", - "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", - "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", - "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", - "Try scrolling down in the mod select panel to find a bunch of new fun mods!", - "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", - "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!", - "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!", - "Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!", - "Toggle advanced frame / thread statistics with Ctrl-F11!", - "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!", - }; - - return tips[RNG.Next(0, tips.Length)]; - } } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0739689daa..cde7da53ce 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -95,6 +95,8 @@ namespace osu.Game.Screens.Menu private SongTicker songTicker; private Container logoTarget; private SystemTitle systemTitle; + private MenuTip menuTip; + private FillFlowContainer bottomElementsFlow; private Sample reappearSampleSwoosh; @@ -157,7 +159,27 @@ namespace osu.Game.Screens.Menu Margin = new MarginPadding { Right = 15, Top = 5 } }, new KiaiMenuFountains(), - systemTitle = new SystemTitle(), + bottomElementsFlow = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + menuTip = new MenuTip + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }, + systemTitle = new SystemTitle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } + } + }, holdToExitGameOverlay?.CreateProxy() ?? Empty() }); @@ -220,6 +242,8 @@ namespace osu.Game.Screens.Menu if (storage is OsuStorage osuStorage && osuStorage.Error != OsuStorageError.None) dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); + + menuTip.ShowNextTip(); } [CanBeNull] @@ -272,7 +296,7 @@ namespace osu.Game.Screens.Menu { base.Update(); - systemTitle.Margin = new MarginPadding + bottomElementsFlow.Margin = new MarginPadding { Bottom = (versionManager?.DrawHeight + 5) ?? 0 }; @@ -314,6 +338,10 @@ namespace osu.Game.Screens.Menu buttonsContainer.MoveTo(new Vector2(-800, 0), FADE_OUT_DURATION, Easing.InSine); sideFlashes.FadeOut(64, Easing.OutQuint); + + bottomElementsFlow + .ScaleTo(0.9f, 1000, Easing.OutQuint) + .FadeOut(500, Easing.OutQuint); } public override void OnResuming(ScreenTransitionEvent e) @@ -330,6 +358,12 @@ namespace osu.Game.Screens.Menu preloadSongSelect(); musicController.EnsurePlayingSomething(); + + menuTip.ShowNextTip(); + + bottomElementsFlow + .ScaleTo(1, 1000, Easing.OutQuint) + .FadeIn(1000, Easing.OutQuint); } public override bool OnExiting(ScreenExitEvent e) diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs new file mode 100644 index 0000000000..c9c8fea1c1 --- /dev/null +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -0,0 +1,89 @@ +// Copyright (c) ppy Pty Ltd . 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.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Screens.Menu +{ + public partial class MenuTip : CompositeDrawable + { + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + private LinkFlowContainer textFlow = null!; + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + textFlow = new LinkFlowContainer + { + Width = 700, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopCentre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Spacing = new Vector2(0, 2), + }, + }; + } + + public void ShowNextTip() + { + if (!config.Get(OsuSetting.MenuTips)) return; + + static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); + + string tip = getRandomTip(); + + AutoSizeAxes = Axes.Both; + + textFlow.Clear(); + textFlow.AddParagraph("a tip for you:", formatSemiBold); + textFlow.AddParagraph(tip, formatRegular); + + this.FadeInFromZero(200, Easing.OutQuint) + .Delay(1000 + 80 * tip.Length) + .Then() + .FadeOutFromOne(2000, Easing.OutQuint) + .Finally(_ => AutoSizeAxes = Axes.X); + } + + private string getRandomTip() + { + string[] tips = + { + "You can press Ctrl-T anywhere in the game to toggle the toolbar!", + "You can press Ctrl-O anywhere in the game to access options!", + "All settings are dynamic and take effect in real-time. Try changing the skin while watching autoplay!", + "New features are coming online every update. Make sure to stay up-to-date!", + "If you find the UI too large or small, try adjusting UI scale in settings!", + "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", + "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", + "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", + "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", + "Try scrolling down in the mod select panel to find a bunch of new fun mods!", + "Most of the web content (profiles, rankings, etc.) are available natively in-game from the icons on the toolbar!", + "Get more details, hide or delete a beatmap by right-clicking on its panel at song select!", + "All delete operations are temporary until exiting. Restore accidentally deleted content from the maintenance settings!", + "Check out the \"playlists\" system, which lets users create their own custom and permanent leaderboards!", + "Toggle advanced frame / thread statistics with Ctrl-F11!", + "Take a look under the hood at performance counters and enable verbose performance logging with Ctrl-F2!", + }; + + return tips[RNG.Next(0, tips.Length)]; + } + } +} diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index bb623cacdf..060e426f8c 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(OsuGame? game) { - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; AutoSizeAxes = Axes.Both; InternalChild = content = new OsuClickableContainer From 222459d921f886a7dd621057752a94442bc703d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:16:27 +0900 Subject: [PATCH 117/139] Add background and improve layout --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/Menu/MenuTip.cs | 32 ++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index cde7da53ce..e8554d077b 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Menu Direction = FillDirection.Vertical, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Spacing = new Vector2(15), + Spacing = new Vector2(5), Children = new Drawable[] { menuTip = new MenuTip diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index c9c8fea1c1..ac23162bd0 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -4,12 +4,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Menu { @@ -27,14 +29,29 @@ namespace osu.Game.Screens.Menu InternalChildren = new Drawable[] { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 15, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + Alpha = 0.4f, + }, + } + }, textFlow = new LinkFlowContainer { - Width = 700, + Width = 600, AutoSizeAxes = Axes.Y, TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, Spacing = new Vector2(0, 2), + Margin = new MarginPadding(10) }, }; } @@ -43,13 +60,11 @@ namespace osu.Game.Screens.Menu { if (!config.Get(OsuSetting.MenuTips)) return; - static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); - static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); + static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular); + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold); string tip = getRandomTip(); - AutoSizeAxes = Axes.Both; - textFlow.Clear(); textFlow.AddParagraph("a tip for you:", formatSemiBold); textFlow.AddParagraph(tip, formatRegular); @@ -57,8 +72,7 @@ namespace osu.Game.Screens.Menu this.FadeInFromZero(200, Easing.OutQuint) .Delay(1000 + 80 * tip.Length) .Then() - .FadeOutFromOne(2000, Easing.OutQuint) - .Finally(_ => AutoSizeAxes = Axes.X); + .FadeOutFromOne(2000, Easing.OutQuint); } private string getRandomTip() From 932d03a4f8aebf8b76d43fad4c7b8ffa17e0ae95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:19:12 +0900 Subject: [PATCH 118/139] Make toggle more immediately hide/show tips --- osu.Game/Screens/Menu/MainMenu.cs | 3 +-- osu.Game/Screens/Menu/MenuTip.cs | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index e8554d077b..e3fc69dc2f 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -242,8 +242,6 @@ namespace osu.Game.Screens.Menu if (storage is OsuStorage osuStorage && osuStorage.Error != OsuStorageError.None) dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); - - menuTip.ShowNextTip(); } [CanBeNull] @@ -359,6 +357,7 @@ namespace osu.Game.Screens.Menu musicController.EnsurePlayingSomething(); + // Cycle tip on resuming menuTip.ShowNextTip(); bottomElementsFlow diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index ac23162bd0..325fb07767 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -22,6 +23,8 @@ namespace osu.Game.Screens.Menu private LinkFlowContainer textFlow = null!; + private Bindable showMenuTips = null!; + [BackgroundDependencyLoader] private void load() { @@ -56,9 +59,21 @@ namespace osu.Game.Screens.Menu }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + showMenuTips = config.GetBindable(OsuSetting.MenuTips); + showMenuTips.BindValueChanged(_ => ShowNextTip(), true); + } + public void ShowNextTip() { - if (!config.Get(OsuSetting.MenuTips)) return; + if (!showMenuTips.Value) + { + this.FadeOut(100, Easing.OutQuint); + return; + } static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.Regular); static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold); From 0ad6ac8b2a2866d5b26cb044110122cd4702cd8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 16:48:17 +0900 Subject: [PATCH 119/139] Remove unused variable --- osu.Game/Screens/Menu/MainMenu.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index e3fc69dc2f..a90d9151b5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -94,7 +94,6 @@ namespace osu.Game.Screens.Menu private ParallaxContainer buttonsContainer; private SongTicker songTicker; private Container logoTarget; - private SystemTitle systemTitle; private MenuTip menuTip; private FillFlowContainer bottomElementsFlow; @@ -173,7 +172,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, }, - systemTitle = new SystemTitle + new SystemTitle { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, From b19f72481b5d3cd1ad81458601aca0404a09d8fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:19:41 +0900 Subject: [PATCH 120/139] Fade out quickly on game exit sequence --- osu.Game/Screens/Menu/MainMenu.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a90d9151b5..bc19e9cb63 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -399,6 +399,10 @@ namespace osu.Game.Screens.Menu songTicker.Hide(); this.FadeOut(3000); + + bottomElementsFlow + .FadeOut(500, Easing.OutQuint); + return base.OnExiting(e); } From 1f2339244ef739341e3429d88678e512430a47d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:14:16 +0900 Subject: [PATCH 121/139] Add supporter display to main menu --- .../Visual/Menus/TestSceneSupporterDisplay.cs | 37 +++++ osu.Game/Screens/Menu/MainMenu.cs | 13 ++ osu.Game/Screens/Menu/SupporterDisplay.cs | 126 ++++++++++++++++++ 3 files changed, 176 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs create mode 100644 osu.Game/Screens/Menu/SupporterDisplay.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs b/osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs new file mode 100644 index 0000000000..8b18adbe0d --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneSupporterDisplay.cs @@ -0,0 +1,37 @@ +// Copyright (c) ppy Pty Ltd . 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.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneSupporterDisplay : OsuTestScene + { + [Test] + public void TestBasic() + { + AddStep("create display", () => + { + Child = new SupporterDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddStep("toggle support", () => + { + ((DummyAPIAccess)API).LocalUser.Value = new APIUser + { + Username = API.LocalUser.Value.Username, + Id = API.LocalUser.Value.Id + 1, + IsSupporter = !API.LocalUser.Value.IsSupporter, + }; + }); + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index bc19e9cb63..401460a498 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -96,6 +96,7 @@ namespace osu.Game.Screens.Menu private Container logoTarget; private MenuTip menuTip; private FillFlowContainer bottomElementsFlow; + private SupporterDisplay supporterDisplay; private Sample reappearSampleSwoosh; @@ -179,6 +180,12 @@ namespace osu.Game.Screens.Menu } } }, + supporterDisplay = new SupporterDisplay + { + Margin = new MarginPadding(5), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, holdToExitGameOverlay?.CreateProxy() ?? Empty() }); @@ -339,6 +346,9 @@ namespace osu.Game.Screens.Menu bottomElementsFlow .ScaleTo(0.9f, 1000, Easing.OutQuint) .FadeOut(500, Easing.OutQuint); + + supporterDisplay + .FadeOut(500, Easing.OutQuint); } public override void OnResuming(ScreenTransitionEvent e) @@ -403,6 +413,9 @@ namespace osu.Game.Screens.Menu bottomElementsFlow .FadeOut(500, Easing.OutQuint); + supporterDisplay + .FadeOut(500, Easing.OutQuint); + return base.OnExiting(e); } diff --git a/osu.Game/Screens/Menu/SupporterDisplay.cs b/osu.Game/Screens/Menu/SupporterDisplay.cs new file mode 100644 index 0000000000..d8ba21cd88 --- /dev/null +++ b/osu.Game/Screens/Menu/SupporterDisplay.cs @@ -0,0 +1,126 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public partial class SupporterDisplay : CompositeDrawable + { + private LinkFlowContainer supportFlow = null!; + + private Drawable heart = null!; + + private readonly IBindable currentUser = new Bindable(); + + private Box backgroundBox = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + Height = 40; + + AutoSizeAxes = Axes.X; + AutoSizeDuration = 1000; + AutoSizeEasing = Easing.OutQuint; + + Masking = true; + CornerExponent = 2.5f; + CornerRadius = 15; + + InternalChildren = new Drawable[] + { + backgroundBox = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.4f, + }, + supportFlow = new LinkFlowContainer + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Spacing = new Vector2(0, 2), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + const float font_size = 14; + + static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: font_size, weight: FontWeight.SemiBold); + + currentUser.BindTo(api.LocalUser); + currentUser.BindValueChanged(e => + { + supportFlow.Children.ForEach(d => d.FadeOut().Expire()); + + if (e.NewValue.IsSupporter) + { + supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold); + + backgroundBox.FadeColour(colours.Pink, 250); + } + else + { + supportFlow.AddText("Consider becoming an ", formatSemiBold); + supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold); + supportFlow.AddText(" to help support osu!'s development", formatSemiBold); + + backgroundBox.FadeColour(colours.Pink4, 250); + } + + supportFlow.AddIcon(FontAwesome.Solid.Heart, t => + { + heart = t; + + t.Padding = new MarginPadding { Left = 5, Top = 1 }; + t.Font = t.Font.With(size: font_size); + t.Origin = Anchor.Centre; + t.Colour = colours.Pink; + + Schedule(() => + { + heart?.FlashColour(Color4.White, 750, Easing.OutQuint).Loop(); + }); + }); + }, true); + + this + .FadeOut() + .Delay(1000) + .FadeInFromZero(800, Easing.OutQuint); + + Scheduler.AddDelayed(() => + { + AutoSizeEasing = Easing.In; + supportFlow.BypassAutoSizeAxes = Axes.X; + this + .Delay(200) + .FadeOut(750, Easing.Out); + }, 6000); + } + } +} From 7dc50b9baf5e6268c84b4177d3f6b14bbaa99514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:18:47 +0900 Subject: [PATCH 122/139] Don't dismiss on hover, and allow dismissing via click --- osu.Game/Screens/Menu/SupporterDisplay.cs | 43 ++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/SupporterDisplay.cs b/osu.Game/Screens/Menu/SupporterDisplay.cs index d8ba21cd88..6639300f4a 100644 --- a/osu.Game/Screens/Menu/SupporterDisplay.cs +++ b/osu.Game/Screens/Menu/SupporterDisplay.cs @@ -8,6 +8,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; @@ -113,8 +115,47 @@ namespace osu.Game.Screens.Menu .Delay(1000) .FadeInFromZero(800, Easing.OutQuint); - Scheduler.AddDelayed(() => + scheduleDismissal(); + } + + protected override bool OnClick(ClickEvent e) + { + dismissalDelegate?.Cancel(); + + supportFlow.BypassAutoSizeAxes = Axes.X; + this.FadeOut(500, Easing.OutQuint); + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + backgroundBox.FadeTo(0.6f, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + backgroundBox.FadeTo(0.4f, 500, Easing.OutQuint); + base.OnHoverLost(e); + } + + private ScheduledDelegate? dismissalDelegate; + + private void scheduleDismissal() + { + dismissalDelegate?.Cancel(); + dismissalDelegate = Scheduler.AddDelayed(() => { + // If the user is hovering they may want to interact with the link. + // Give them more time. + if (IsHovered) + { + scheduleDismissal(); + return; + } + + dismissalDelegate?.Cancel(); + AutoSizeEasing = Easing.In; supportFlow.BypassAutoSizeAxes = Axes.X; this From bd0e2b4dde99cd20d990959ba52965cc73099977 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:21:29 +0900 Subject: [PATCH 123/139] Remove disclaimer screen completely --- .../Visual/Menus/TestSceneDisclaimer.cs | 29 --- osu.Game/Screens/Loader.cs | 13 +- osu.Game/Screens/Menu/Disclaimer.cs | 227 ------------------ 3 files changed, 2 insertions(+), 267 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs delete mode 100644 osu.Game/Screens/Menu/Disclaimer.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs b/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs deleted file mode 100644 index fb82b0df80..0000000000 --- a/osu.Game.Tests/Visual/Menus/TestSceneDisclaimer.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Allocation; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Screens.Menu; - -namespace osu.Game.Tests.Visual.Menus -{ - public partial class TestSceneDisclaimer : ScreenTestScene - { - [BackgroundDependencyLoader] - private void load() - { - AddStep("load disclaimer", () => LoadScreen(new Disclaimer())); - - AddStep("toggle support", () => - { - ((DummyAPIAccess)API).LocalUser.Value = new APIUser - { - Username = API.LocalUser.Value.Username, - Id = API.LocalUser.Value.Id + 1, - IsSupporter = !API.LocalUser.Value.IsSupporter, - }; - }); - } - } -} diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 372cfe748e..4dba512cbd 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -21,8 +21,6 @@ namespace osu.Game.Screens { public partial class Loader : StartupScreen { - private bool showDisclaimer; - public Loader() { ValidForResume = false; @@ -35,13 +33,7 @@ namespace osu.Game.Screens private LoadingSpinner spinner; private ScheduledDelegate spinnerShow; - protected virtual OsuScreen CreateLoadableScreen() - { - if (showDisclaimer) - return new Disclaimer(getIntroSequence()); - - return getIntroSequence(); - } + protected virtual OsuScreen CreateLoadableScreen() => getIntroSequence(); private IntroScreen getIntroSequence() { @@ -107,9 +99,8 @@ namespace osu.Game.Screens } [BackgroundDependencyLoader] - private void load(OsuGameBase game, OsuConfigManager config) + private void load(OsuConfigManager config) { - showDisclaimer = game.IsDeployedBuild; introSequence = config.Get(OsuSetting.IntroSequence); } diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs deleted file mode 100644 index 1afef66313..0000000000 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Screens; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Screens.Menu -{ - public partial class Disclaimer : StartupScreen - { - private SpriteIcon icon; - private Color4 iconColour; - private LinkFlowContainer textFlow; - private LinkFlowContainer supportFlow; - - private Drawable heart; - - private const float icon_y = -85; - private const float icon_size = 30; - - private readonly OsuScreen nextScreen; - - private readonly Bindable currentUser = new Bindable(); - private FillFlowContainer fill; - - private readonly List expendableText = new List(); - - public Disclaimer(OsuScreen nextScreen = null) - { - this.nextScreen = nextScreen; - ValidForResume = false; - } - - [Resolved] - private IAPIProvider api { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - InternalChildren = new Drawable[] - { - icon = new SpriteIcon - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Icon = OsuIcon.Logo, - Size = new Vector2(icon_size), - Y = icon_y, - }, - fill = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Y = icon_y, - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - Children = new Drawable[] - { - textFlow = new LinkFlowContainer - { - Width = 680, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.TopCentre, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Spacing = new Vector2(0, 2), - }, - } - }, - supportFlow = new LinkFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - TextAnchor = Anchor.BottomCentre, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Padding = new MarginPadding(20), - Alpha = 0, - Spacing = new Vector2(0, 2), - }, - }; - - textFlow.AddText("this is osu!", t => t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular)); - - expendableText.Add(textFlow.AddText("lazer", t => - { - t.Font = t.Font.With(Typeface.Torus, 30, FontWeight.Regular); - t.Colour = colours.PinkLight; - })); - - static void formatRegular(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular); - static void formatSemiBold(SpriteText t) => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold); - - textFlow.NewParagraph(); - - textFlow.AddText("the next ", formatRegular); - textFlow.AddText("major update", t => - { - t.Font = t.Font.With(Typeface.Torus, 20, FontWeight.SemiBold); - t.Colour = colours.Pink; - }); - expendableText.Add(textFlow.AddText(" coming to osu!", formatRegular)); - textFlow.AddText(".", formatRegular); - - textFlow.NewParagraph(); - textFlow.NewParagraph(); - - textFlow.NewParagraph(); - - iconColour = colours.Yellow; - - // manually transfer the user once, but only do the final bind in LoadComplete to avoid thread woes (API scheduler could run while this screen is still loading). - // the manual transfer is here to ensure all text content is loaded ahead of time as this is very early in the game load process and we want to avoid stutters. - currentUser.Value = api.LocalUser.Value; - currentUser.BindValueChanged(e => - { - supportFlow.Children.ForEach(d => d.FadeOut().Expire()); - - if (e.NewValue.IsSupporter) - { - supportFlow.AddText("Eternal thanks to you for supporting osu!", formatSemiBold); - } - else - { - supportFlow.AddText("Consider becoming an ", formatSemiBold); - supportFlow.AddLink("osu!supporter", "https://osu.ppy.sh/home/support", formatSemiBold); - supportFlow.AddText(" to help support osu!'s development", formatSemiBold); - } - - supportFlow.AddIcon(FontAwesome.Solid.Heart, t => - { - heart = t; - - t.Padding = new MarginPadding { Left = 5, Top = 3 }; - t.Font = t.Font.With(size: 20); - t.Origin = Anchor.Centre; - t.Colour = colours.Pink; - - Schedule(() => heart?.FlashColour(Color4.White, 750, Easing.OutQuint).Loop()); - }); - - if (supportFlow.IsPresent) - supportFlow.FadeInFromZero(500); - }, true); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - if (nextScreen != null) - LoadComponentAsync(nextScreen); - - ((IBindable)currentUser).BindTo(api.LocalUser); - } - - public override void OnSuspending(ScreenTransitionEvent e) - { - base.OnSuspending(e); - - // Once this screen has finished being displayed, we don't want to unnecessarily handle user change events. - currentUser.UnbindAll(); - } - - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); - - icon.RotateTo(10); - icon.FadeOut(); - icon.ScaleTo(0.5f); - - icon.Delay(500).FadeIn(500).ScaleTo(1, 500, Easing.OutQuint); - - using (BeginDelayedSequence(3000)) - { - icon.FadeColour(iconColour, 200, Easing.OutQuint); - icon.MoveToY(icon_y * 1.3f, 500, Easing.OutCirc) - .RotateTo(-360, 520, Easing.OutQuint) - .Then() - .MoveToY(icon_y, 160, Easing.InQuart) - .FadeColour(Color4.White, 160); - - using (BeginDelayedSequence(520 + 160)) - { - fill.MoveToOffset(new Vector2(0, 15), 160, Easing.OutQuart); - Schedule(() => expendableText.SelectMany(t => t.Drawables).ForEach(t => - { - t.FadeOut(100); - t.ScaleTo(new Vector2(0, 1), 100, Easing.OutQuart); - })); - } - } - - supportFlow.FadeOut().Delay(2000).FadeIn(500); - double delay = 500; - foreach (var c in textFlow.Children) - c.FadeTo(0.001f).Delay(delay += 20).FadeIn(500); - - this - .FadeInFromZero(500) - .Then(5500) - .FadeOut(250) - .ScaleTo(0.9f, 250, Easing.InQuint) - .Finally(_ => - { - if (nextScreen != null) - this.Push(nextScreen); - }); - } - } -} From 2ec9343868693f45f7650e2209271b157222d973 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:35:10 +0900 Subject: [PATCH 124/139] Add the ability to spectate a user by right clicking their user panel --- osu.Game/Localisation/ContextMenuStrings.cs | 9 +++-- osu.Game/Users/UserPanel.cs | 39 +++++++++++++-------- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/osu.Game/Localisation/ContextMenuStrings.cs b/osu.Game/Localisation/ContextMenuStrings.cs index 029fba67d8..cb18a2159c 100644 --- a/osu.Game/Localisation/ContextMenuStrings.cs +++ b/osu.Game/Localisation/ContextMenuStrings.cs @@ -20,9 +20,14 @@ namespace osu.Game.Localisation public static LocalisableString ViewBeatmap => new TranslatableString(getKey(@"view_beatmap"), @"View beatmap"); /// - /// "Invite player" + /// "Invite to room" /// - public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite player"); + public static LocalisableString InvitePlayer => new TranslatableString(getKey(@"invite_player"), @"Invite to room"); + + /// + /// "Spectate" + /// + public static LocalisableString SpectatePlayer => new TranslatableString(getKey(@"spectate_player"), @"Spectate"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 273faf9bd1..b6a77e754d 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -13,6 +13,7 @@ using osu.Game.Overlays; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; +using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -20,6 +21,8 @@ using osu.Game.Online.Chat; using osu.Game.Resources.Localisation.Web; using osu.Game.Localisation; using osu.Game.Online.Multiplayer; +using osu.Game.Screens; +using osu.Game.Screens.Play; namespace osu.Game.Users { @@ -60,6 +63,9 @@ namespace osu.Game.Users [Resolved] protected OverlayColourProvider? ColourProvider { get; private set; } + [Resolved] + private IPerformFromScreenRunner? performer { get; set; } + [Resolved] protected OsuColour Colours { get; private set; } = null!; @@ -113,23 +119,26 @@ namespace osu.Game.Users new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, ViewProfile) }; - if (!User.Equals(api.LocalUser.Value)) - { - items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () => - { - channelManager?.OpenPrivateChannel(User); - chatOverlay?.Show(); - })); - } + if (User.Equals(api.LocalUser.Value)) + return items.ToArray(); - if ( - // TODO: uncomment this once lazer / osu-web is updating online states - // User.IsOnline && - multiplayerClient?.Room != null && - multiplayerClient.Room.Users.All(u => u.UserID != User.Id) - ) + items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () => { - items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id))); + channelManager?.OpenPrivateChannel(User); + chatOverlay?.Show(); + })); + + if (User.IsOnline) + { + items.Add(new OsuMenuItem(ContextMenuStrings.SpectatePlayer, MenuItemType.Standard, () => + { + performer?.PerformFromScreen(s => s.Push(new SoloSpectatorScreen(User))); + })); + + if (multiplayerClient?.Room?.Users.All(u => u.UserID != User.Id) == true) + { + items.Add(new OsuMenuItem(ContextMenuStrings.InvitePlayer, MenuItemType.Standard, () => multiplayerClient.InvitePlayer(User.Id))); + } } return items.ToArray(); From 22eced33006f959af2bc17198eafd46779a4923b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 17:40:07 +0900 Subject: [PATCH 125/139] Show local user in online users --- osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs index 37ea3f9c38..ee277ff538 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyOnlineDisplay.cs @@ -131,9 +131,6 @@ namespace osu.Game.Overlays.Dashboard { int userId = kvp.Key; - if (userId == api.LocalUser.Value.Id) - continue; - users.GetUserAsync(userId).ContinueWith(task => { APIUser user = task.GetResultSafely(); From 28e220ca501592f854aab01b294981ca4d83154e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 19:04:19 +0900 Subject: [PATCH 126/139] Update popup dialog design Had to be done. I hated the old ones so much. As usual, disclaimer that this is an iterative design and will probably be replaced in the future. --- .../UserInterface/TestSceneDialogOverlay.cs | 19 ++--- .../Graphics/UserInterface/DialogButton.cs | 14 ++-- osu.Game/Overlays/Dialog/PopupDialog.cs | 77 +++++++++++-------- osu.Game/Overlays/DialogOverlay.cs | 12 +-- 4 files changed, 68 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index 81b692004b..3b38343f04 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -9,6 +9,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -19,11 +20,15 @@ namespace osu.Game.Tests.Visual.UserInterface { private DialogOverlay overlay; + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); + } + [Test] public void TestBasic() { - AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); - TestPopupDialog firstDialog = null; TestPopupDialog secondDialog = null; @@ -84,7 +89,7 @@ namespace osu.Game.Tests.Visual.UserInterface })); AddAssert("second dialog displayed", () => overlay.CurrentDialog == secondDialog); - AddAssert("first dialog is not part of hierarchy", () => firstDialog.Parent == null); + AddUntilStep("first dialog is not part of hierarchy", () => firstDialog.Parent == null); } [Test] @@ -92,7 +97,7 @@ namespace osu.Game.Tests.Visual.UserInterface { PopupDialog dialog = null; - AddStep("create dialog overlay", () => overlay = new SlowLoadingDialogOverlay()); + AddStep("create slow loading dialog overlay", () => overlay = new SlowLoadingDialogOverlay()); AddStep("start loading overlay", () => LoadComponentAsync(overlay, Add)); @@ -128,8 +133,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDismissBeforePush() { - AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); - TestPopupDialog testDialog = null; AddStep("dismissed dialog push", () => { @@ -146,8 +149,6 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDismissBeforePushViaButtonPress() { - AddStep("create dialog overlay", () => Child = overlay = new DialogOverlay()); - TestPopupDialog testDialog = null; AddStep("dismissed dialog push", () => { @@ -163,7 +164,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddAssert("no dialog pushed", () => overlay.CurrentDialog == null); - AddAssert("dialog is not part of hierarchy", () => testDialog.Parent == null); + AddUntilStep("dialog is not part of hierarchy", () => testDialog.Parent == null); } private partial class TestPopupDialog : PopupDialog diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index db81bc991d..c920597a95 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -25,7 +25,7 @@ namespace osu.Game.Graphics.UserInterface private const float idle_width = 0.8f; private const float hover_width = 0.9f; - private const float hover_duration = 500; + private const float hover_duration = 300; private const float click_duration = 200; public event Action? StateChanged; @@ -54,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box rightGlow; private readonly Box background; private readonly SpriteText spriteText; - private Vector2 hoverSpacing => new Vector2(3f, 0f); + private Vector2 hoverSpacing => new Vector2(1.4f, 0f); public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button) : base(sampleSet) @@ -279,15 +279,15 @@ namespace osu.Game.Graphics.UserInterface if (newState == SelectionState.Selected) { - spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - ColourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); + spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutQuint); + ColourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutQuint); glowContainer.FadeIn(hover_duration, Easing.OutQuint); } else { - ColourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic); - spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); - glowContainer.FadeOut(hover_duration, Easing.OutQuint); + ColourContainer.ResizeWidthTo(idle_width, hover_duration / 2, Easing.OutQuint); + spriteText.TransformSpacingTo(Vector2.Zero, hover_duration / 2, Easing.OutQuint); + glowContainer.FadeOut(hover_duration / 2, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 36a9baac67..663c2e78ce 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osuTK; @@ -25,11 +26,10 @@ namespace osu.Game.Overlays.Dialog public abstract partial class PopupDialog : VisibilityContainer { public const float ENTER_DURATION = 500; - public const float EXIT_DURATION = 200; + public const float EXIT_DURATION = 500; private readonly Vector2 ringSize = new Vector2(100f); private readonly Vector2 ringMinifiedSize = new Vector2(20f); - private readonly Vector2 buttonsEnterSpacing = new Vector2(0f, 50f); private readonly Box flashLayer; private Sample flashSample = null!; @@ -108,13 +108,20 @@ namespace osu.Game.Overlays.Dialog protected PopupDialog() { - RelativeSizeAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; Children = new Drawable[] { content = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Alpha = 0f, Children = new Drawable[] { @@ -122,11 +129,13 @@ namespace osu.Game.Overlays.Dialog { RelativeSizeAxes = Axes.Both, Masking = true, + CornerRadius = 20, + CornerExponent = 2.5f, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, - Colour = Color4.Black.Opacity(0.5f), - Radius = 8, + Colour = Color4.Black.Opacity(0.2f), + Radius = 14, }, Children = new Drawable[] { @@ -142,23 +151,29 @@ namespace osu.Game.Overlays.Dialog ColourDark = Color4Extensions.FromHex(@"1e171e"), TriangleScale = 4, }, + flashLayer = new Box + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = Color4Extensions.FromHex(@"221a21"), + }, }, }, new FillFlowContainer { - Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(0f, 10f), - Padding = new MarginPadding { Bottom = 10 }, + Padding = new MarginPadding { Vertical = 60 }, Children = new Drawable[] { new Container { Origin = Anchor.TopCentre, Anchor = Anchor.TopCentre, + Padding = new MarginPadding { Bottom = 30 }, Size = ringSize, Children = new Drawable[] { @@ -181,6 +196,7 @@ namespace osu.Game.Overlays.Dialog Origin = Anchor.Centre, Anchor = Anchor.Centre, Icon = FontAwesome.Solid.TimesCircle, + Y = -2, Size = new Vector2(50), }, }, @@ -202,25 +218,18 @@ namespace osu.Game.Overlays.Dialog TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(5), + }, + buttonsContainer = new FillFlowContainer + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding { Top = 30 }, }, }, }, - buttonsContainer = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - flashLayer = new Box - { - Alpha = 0, - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - Colour = Color4Extensions.FromHex(@"221a21"), - }, }, }, }; @@ -231,7 +240,7 @@ namespace osu.Game.Overlays.Dialog } [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, OsuColour colours) { flashSample = audio.Samples.Get(@"UI/default-select-disabled"); } @@ -288,15 +297,15 @@ namespace osu.Game.Overlays.Dialog // Reset various animations but only if the dialog animation fully completed if (content.Alpha == 0) { - buttonsContainer.TransformSpacingTo(buttonsEnterSpacing); - buttonsContainer.MoveToY(buttonsEnterSpacing.Y); + content.ScaleTo(0.7f); ring.ResizeTo(ringMinifiedSize); } - content.FadeIn(ENTER_DURATION, Easing.OutQuint); - ring.ResizeTo(ringSize, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.TransformSpacingTo(Vector2.Zero, ENTER_DURATION, Easing.OutQuint); - buttonsContainer.MoveToY(0, ENTER_DURATION, Easing.OutQuint); + content + .ScaleTo(1, 750, Easing.OutElasticHalf) + .FadeIn(ENTER_DURATION, Easing.OutQuint); + + ring.ResizeTo(ringSize, ENTER_DURATION * 1.5f, Easing.OutQuint); } protected override void PopOut() @@ -306,7 +315,9 @@ namespace osu.Game.Overlays.Dialog // This is presumed to always be a sane default "cancel" action. buttonsContainer.Last().TriggerClick(); - content.FadeOut(EXIT_DURATION, Easing.InSine); + content + .ScaleTo(0.7f, EXIT_DURATION, Easing.Out) + .FadeOut(EXIT_DURATION, Easing.OutQuint); } private void pressButtonAtIndex(int index) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 005162bbcc..a85f1ecbcd 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -29,16 +29,18 @@ namespace osu.Game.Overlays public DialogOverlay() { - RelativeSizeAxes = Axes.Both; + AutoSizeAxes = Axes.Y; Child = dialogContainer = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, }; - Width = 0.4f; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; + Width = 500; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; } [BackgroundDependencyLoader] From b7f3c83514deec96959b51bcbb227ec0ee4f8801 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:01:29 +0900 Subject: [PATCH 127/139] Expose the ability to update global offset from the player loader screen --- osu.Game/Overlays/SettingsOverlay.cs | 29 +++++++++++++++---- .../PlayerSettings/BeatmapOffsetControl.cs | 19 ++++++++++++ 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 5735b1515a..cf76ed8781 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -3,19 +3,21 @@ #nullable disable +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Framework.Testing; +using osu.Game.Graphics; +using osu.Game.Localisation; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; using osu.Game.Overlays.Settings.Sections.Input; using osuTK.Graphics; -using System.Collections.Generic; -using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Localisation; namespace osu.Game.Overlays { @@ -55,6 +57,21 @@ namespace osu.Game.Overlays public override bool AcceptsFocus => lastOpenedSubPanel == null || lastOpenedSubPanel.State.Value == Visibility.Hidden; + public void ShowAtControl() + where T : Drawable + { + Show(); + + // wait for load of sections + if (!SectionsContainer.Any()) + { + Scheduler.Add(ShowAtControl); + return; + } + + SectionsContainer.ScrollTo(SectionsContainer.ChildrenOfType().Single()); + } + private T createSubPanel(T subPanel) where T : SettingsSubPanel { diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 3f0f0fd1df..8c6f1a8f7f 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -21,7 +21,9 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Input.Bindings; using osu.Game.Localisation; +using osu.Game.Overlays; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Audio; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -213,6 +215,8 @@ namespace osu.Game.Screens.Play.PlayerSettings lastPlayAverage = average; lastPlayBeatmapOffset = Current.Value; + LinkFlowContainer globalOffsetText; + referenceScoreContainer.AddRange(new Drawable[] { lastPlayGraph = new HitEventTimingDistributionGraph(hitEvents) @@ -226,9 +230,24 @@ namespace osu.Game.Screens.Play.PlayerSettings Text = BeatmapOffsetControlStrings.CalibrateUsingLastPlay, Action = () => Current.Value = lastPlayBeatmapOffset - lastPlayAverage }, + globalOffsetText = new LinkFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + } }); + + if (settings != null) + { + globalOffsetText.AddText("You can also "); + globalOffsetText.AddLink("adjust the global offset", () => settings.ShowAtControl()); + globalOffsetText.AddText(" based off this play."); + } } + [Resolved] + private SettingsOverlay? settings { get; set; } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 91af94086c6ba2905779c07ebb35dc011979993f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:02:06 +0900 Subject: [PATCH 128/139] Remove offset wizard button for now --- osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs index af15d310fc..e05d20a5db 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/OffsetSettings.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { protected override LocalisableString Header => AudioSettingsStrings.OffsetHeader; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing", "delay", "latency" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { "universal", "uo", "timing", "delay", "latency", "wizard" }); [BackgroundDependencyLoader] private void load(OsuConfigManager config) @@ -26,10 +26,6 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { Current = config.GetBindable(OsuSetting.AudioOffset), }, - new SettingsButton - { - Text = AudioSettingsStrings.OffsetWizard - } }; } } From e1a376c0a742d958399734683c8eb79631f37add Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:13:19 +0900 Subject: [PATCH 129/139] Fix using "Back" binding at spectator fail screen not working --- osu.Game/Screens/Play/GameplayMenuOverlay.cs | 10 +++++++++- osu.Game/Screens/Play/PauseOverlay.cs | 8 +++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 0680842891..440b8d37b9 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -44,7 +44,15 @@ namespace osu.Game.Screens.Play /// /// Action that is invoked when is triggered. /// - protected virtual Action BackAction => () => InternalButtons.LastOrDefault()?.TriggerClick(); + protected virtual Action BackAction => () => + { + // We prefer triggering the button click as it will animate... + // but sometimes buttons aren't present (see FailOverlay's constructor as an example). + if (Buttons.Any()) + Buttons.Last().TriggerClick(); + else + OnQuit?.Invoke(); + }; /// /// Action that is invoked when is triggered. diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 88561ada71..2aa2793fd4 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -29,7 +29,13 @@ namespace osu.Game.Screens.Play private SkinnableSound pauseLoop; - protected override Action BackAction => () => InternalButtons.First().TriggerClick(); + protected override Action BackAction => () => + { + if (Buttons.Any()) + Buttons.First().TriggerClick(); + else + OnResume?.Invoke(); + }; [BackgroundDependencyLoader] private void load(OsuColour colours) From f8347288c1a5cdc5ab6587a49e3980075eec4872 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:29:56 +0900 Subject: [PATCH 130/139] Add padding around text in dialogs --- .../UserInterface/TestSceneDialogOverlay.cs | 24 +++++++++++++++++++ osu.Game/Overlays/Dialog/PopupDialog.cs | 2 ++ 2 files changed, 26 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs index 3b38343f04..f2313022ec 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDialogOverlay.cs @@ -92,6 +92,30 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("first dialog is not part of hierarchy", () => firstDialog.Parent == null); } + [Test] + public void TestTooMuchText() + { + AddStep("dialog #1", () => overlay.Push(new TestPopupDialog + { + Icon = FontAwesome.Regular.TrashAlt, + HeaderText = @"Confirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion ofConfirm deletion of", + BodyText = @"Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver.Ayase Rie - Yuima-ru*World TVver. ", + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"I never want to see this again.", + Action = () => Console.WriteLine(@"OK"), + }, + new PopupDialogCancelButton + { + Text = @"Firetruck, I still want quick ranks!", + Action = () => Console.WriteLine(@"Cancel"), + }, + }, + })); + } + [Test] public void TestPushBeforeLoad() { diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 663c2e78ce..4048b35e78 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -210,6 +210,7 @@ namespace osu.Game.Overlays.Dialog RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, TextAnchor = Anchor.TopCentre, + Padding = new MarginPadding { Horizontal = 5 }, }, body = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 18)) { @@ -218,6 +219,7 @@ namespace osu.Game.Overlays.Dialog TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 5 }, }, buttonsContainer = new FillFlowContainer { From 6bc0f02e2d46296fe2838cf97d57a3905d739928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Dec 2023 20:40:51 +0900 Subject: [PATCH 131/139] Fix test initialising dialogs without cleaning up --- .../Visual/UserInterface/TestScenePopupDialog.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 9537ab63be..37ff66aa35 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Overlays.Dialog; @@ -15,18 +12,17 @@ namespace osu.Game.Tests.Visual.UserInterface { public partial class TestScenePopupDialog : OsuManualInputManagerTestScene { - private TestPopupDialog dialog; + private TestPopupDialog dialog = null!; [SetUpSteps] public void SetUpSteps() { AddStep("new popup", () => { - Add(dialog = new TestPopupDialog + Child = dialog = new TestPopupDialog { - RelativeSizeAxes = Axes.Both, State = { Value = Framework.Graphics.Containers.Visibility.Visible }, - }); + }; }); } From bb0737837b537fd7d97f276e48aaae871f0abc5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 13:58:00 +0100 Subject: [PATCH 132/139] Fix test failing due to querying button position during transform --- osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 37ff66aa35..96d19911bd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -29,6 +29,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestDangerousButton([Values(false, true)] bool atEdge) { + AddStep("finish transforms", () => dialog.FinishTransforms(true)); + if (atEdge) { AddStep("move mouse to button edge", () => From e6f1d7db4446e9e0c2308b5d17c937c65a4f7a27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:10:03 +0100 Subject: [PATCH 133/139] Move `SessionAverageHitErrorTracker` to more general namespace --- .../Audio => Configuration}/SessionAverageHitErrorTracker.cs | 3 +-- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game/{Overlays/Settings/Sections/Audio => Configuration}/SessionAverageHitErrorTracker.cs (95%) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs b/osu.Game/Configuration/SessionAverageHitErrorTracker.cs similarity index 95% rename from osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs rename to osu.Game/Configuration/SessionAverageHitErrorTracker.cs index b714d49b89..13b1ea7d37 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/SessionAverageHitErrorTracker.cs +++ b/osu.Game/Configuration/SessionAverageHitErrorTracker.cs @@ -5,12 +5,11 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; -namespace osu.Game.Overlays.Settings.Sections.Audio +namespace osu.Game.Configuration { /// /// Tracks the local user's average hit error during the ongoing play session. diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index 0023c60c61..e10de58a6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; From 6718de01ecc7397f3804954f0c01aaa81d1fffdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:11:08 +0100 Subject: [PATCH 134/139] Suggest audio adjust after one play --- .../Settings/Sections/Audio/AudioOffsetAdjustControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs index e10de58a6c..98acea1f2e 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioOffsetAdjustControl.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio break; } - suggestedOffset.Value = averageHitErrorHistory.Count < 3 ? null : -averageHitErrorHistory.Average(); + suggestedOffset.Value = averageHitErrorHistory.Any() ? -averageHitErrorHistory.Average() : null; } private float getXPositionForAverage(double average) => (float)(Math.Clamp(-average, current.MinValue, current.MaxValue) / (2 * current.MaxValue)); @@ -152,7 +152,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio { hintText.Text = suggestedOffset.Value == null ? @"Play a few beatmaps to receive a suggested offset!" - : $@"Based on the last {averageHitErrorHistory.Count} plays, the suggested offset is {suggestedOffset.Value:N0} ms."; + : $@"Based on the last {averageHitErrorHistory.Count} play(s), the suggested offset is {suggestedOffset.Value:N0} ms."; applySuggestion.Enabled.Value = suggestedOffset.Value != null; } } From 619b0cc69b1998fdc8b1b1fcb69bcddaefc08004 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:12:28 +0100 Subject: [PATCH 135/139] Fix code quality inspection --- .../Screens/Play/PlayerSettings/BeatmapOffsetControl.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs index 8c6f1a8f7f..8efb80e771 100644 --- a/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs +++ b/osu.Game/Screens/Play/PlayerSettings/BeatmapOffsetControl.cs @@ -160,11 +160,11 @@ namespace osu.Game.Screens.Play.PlayerSettings // Apply to all difficulties in a beatmap set for now (they generally always share timing). foreach (var b in setInfo.Beatmaps) { - BeatmapUserSettings settings = b.UserSettings; + BeatmapUserSettings userSettings = b.UserSettings; double val = Current.Value; - if (settings.Offset != val) - settings.Offset = val; + if (userSettings.Offset != val) + userSettings.Offset = val; } }); } From 6d124513e7d88aba2849b5494783820eeb476777 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:15:15 +0100 Subject: [PATCH 136/139] Fix test failures due to player bailing early after failing to load beatmap --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ff2c57bf35..83adf1f960 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.Play { bool exiting = base.OnExiting(e); submitFromFailOrQuit(); - statics.SetValue(Static.LastLocalUserScore, Score.ScoreInfo.DeepClone()); + statics.SetValue(Static.LastLocalUserScore, Score?.ScoreInfo.DeepClone()); return exiting; } From 93c7ebdae3f61e95e36cc61c014d71fb67e8a3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 14:30:11 +0100 Subject: [PATCH 137/139] Remove unused using --- osu.Game/OsuGameBase.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 90c88b2b33..4e465f59df 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -55,7 +55,6 @@ using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; -using osu.Game.Overlays.Settings.Sections.Audio; using osu.Game.Overlays.Settings.Sections.Input; using osu.Game.Resources; using osu.Game.Rulesets; From 68402bfd113b3c4051aead283fe3cf57d5b9ca78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 20:32:33 +0100 Subject: [PATCH 138/139] Add test coverage of failure scenario --- osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs index 85aa14f33e..46570e7cdf 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenu.cs @@ -24,6 +24,11 @@ namespace osu.Game.Tests.Visual.Menus Image = @"https://assets.ppy.sh/main-menu/wf2023-vote@2x.png", Url = @"https://osu.ppy.sh/community/contests/189", }); + AddStep("set title with nonexistent image", () => Game.ChildrenOfType().Single().Current.Value = new APISystemTitle + { + Image = @"https://test.invalid/@2x", // .invalid TLD reserved by https://datatracker.ietf.org/doc/html/rfc2606#section-2 + Url = @"https://osu.ppy.sh/community/contests/189", + }); AddStep("unset system title", () => Game.ChildrenOfType().Single().Current.Value = null); } } From 7a10e132eaea162febb65d03baa16067a1244e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Dec 2023 20:33:56 +0100 Subject: [PATCH 139/139] Fix crash when retrieval of system title image fails Closes https://github.com/ppy/osu/issues/26194. --- osu.Game/Screens/Menu/SystemTitle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/SystemTitle.cs b/osu.Game/Screens/Menu/SystemTitle.cs index 060e426f8c..7d49a1dd5f 100644 --- a/osu.Game/Screens/Menu/SystemTitle.cs +++ b/osu.Game/Screens/Menu/SystemTitle.cs @@ -138,8 +138,8 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(LargeTextureStore textureStore) { - var texture = textureStore.Get(SystemTitle.Image); - if (SystemTitle.Image.Contains(@"@2x")) + Texture? texture = textureStore.Get(SystemTitle.Image); + if (texture != null && SystemTitle.Image.Contains(@"@2x")) texture.ScaleAdjust *= 2; AutoSizeAxes = Axes.Both;