diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt
index 022da0a2ea..03fd21829d 100644
--- a/CodeAnalysis/BannedSymbols.txt
+++ b/CodeAnalysis/BannedSymbols.txt
@@ -15,6 +15,8 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen
M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks.
P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks.
M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever.
+M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
+M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture.
M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString.
M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead.
diff --git a/osu.Android.props b/osu.Android.props
index 3f4c8e2d24..f251e8ee71 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -51,8 +51,8 @@
-
-
+
+
diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
index 0169627867..728aa27da2 100644
--- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
+++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderApplication.cs
@@ -14,6 +14,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
+using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Skinning;
using osu.Game.Tests.Visual;
using osuTK;
@@ -68,10 +69,8 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("create slider", () =>
{
- var tintingSkin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
- tintingSkin.Configuration.ConfigDictionary["AllowSliderBallTint"] = "1";
-
- var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(tintingSkin, Beatmap.Value.Beatmap);
+ var skin = skinManager.GetSkin(DefaultLegacySkin.CreateInfo());
+ var provider = Ruleset.Value.CreateInstance().CreateSkinTransformer(skin, Beatmap.Value.Beatmap);
Child = new SkinProvidingContainer(provider)
{
@@ -92,10 +91,10 @@ namespace osu.Game.Rulesets.Osu.Tests
});
AddStep("set accent white", () => dho.AccentColour.Value = Color4.White);
- AddAssert("ball is white", () => dho.ChildrenOfType().Single().AccentColour == Color4.White);
+ AddAssert("ball is white", () => dho.ChildrenOfType().Single().BallColour == Color4.White);
AddStep("set accent red", () => dho.AccentColour.Value = Color4.Red);
- AddAssert("ball is red", () => dho.ChildrenOfType().Single().AccentColour == Color4.Red);
+ AddAssert("ball is red", () => dho.ChildrenOfType().Single().BallColour == Color4.Red);
}
private Slider prepareObject(Slider slider)
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index d58a435728..785d15c15b 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -14,12 +14,10 @@ using osu.Game.Audio;
using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.Osu.Skinning;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -106,7 +104,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
foreach (var drawableHitObject in NestedHitObjects)
drawableHitObject.AccentColour.Value = colour.NewValue;
- updateBallTint();
}, true);
Tracking.BindValueChanged(updateSlidingSample);
@@ -257,22 +254,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
SliderBody?.RecyclePath();
}
- protected override void ApplySkin(ISkinSource skin, bool allowFallback)
- {
- base.ApplySkin(skin, allowFallback);
-
- updateBallTint();
- }
-
- private void updateBallTint()
- {
- if (CurrentSkin == null)
- return;
-
- bool allowBallTint = CurrentSkin.GetConfig(OsuSkinConfiguration.AllowSliderBallTint)?.Value ?? false;
- Ball.AccentColour = allowBallTint ? AccentColour.Value : Color4.White;
- }
-
protected override void CheckForResult(bool userTriggered, double timeOffset)
{
if (userTriggered || Time.Current < HitObject.EndTime)
@@ -331,7 +312,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
base.UpdateHitStateTransforms(state);
- const float fade_out_time = 450;
+ const float fade_out_time = 240;
switch (state)
{
@@ -341,7 +322,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
break;
}
- this.FadeOut(fade_out_time, Easing.OutQuint).Expire();
+ this.FadeOut(fade_out_time).Expire();
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => SliderBody?.ReceivePositionalInputAt(screenSpacePos) ?? base.ReceivePositionalInputAt(screenSpacePos);
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
index a2fe623897..de6ca7dd38 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs
@@ -11,28 +11,20 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Events;
-using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Skinning.Default;
using osu.Game.Skinning;
using osuTK;
-using osuTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
- public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour
+ public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition
{
public const float FOLLOW_AREA = 2.4f;
public Func GetInitialHitAction;
- public Color4 AccentColour
- {
- get => ball.Colour;
- set => ball.Colour = value;
- }
-
private Drawable followCircleReceptor;
private DrawableSlider drawableSlider;
private Drawable ball;
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
index 53f4d21975..6ae9d5bc34 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs
@@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
///
public readonly IBindable SpinsPerMinute = new BindableDouble();
- private const double fade_out_duration = 160;
+ private const double fade_out_duration = 240;
public DrawableSpinner()
: this(null)
diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
index ffdcba3cdb..36dc8c801d 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonMainCirclePiece.cs
@@ -108,18 +108,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
{
base.LoadComplete();
- accentColour.BindValueChanged(colour =>
- {
- outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
- outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
- innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
- flash.Colour = colour.NewValue;
- }, true);
-
indexInCurrentCombo.BindValueChanged(index => number.Text = (index.NewValue + 1).ToString(), true);
+ accentColour.BindValueChanged(colour =>
+ {
+ // A colour transform is applied.
+ // Without removing transforms first, when it is rewound it may apply an old colour.
+ outerGradient.ClearTransforms(targetMember: nameof(Colour));
+ outerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.1f));
+
+ outerFill.Colour = innerFill.Colour = colour.NewValue.Darken(4);
+ innerGradient.Colour = ColourInfo.GradientVertical(colour.NewValue.Darken(0.5f), colour.NewValue.Darken(0.6f));
+ flash.Colour = colour.NewValue;
+
+ updateStateTransforms(drawableObject, drawableObject.State.Value);
+ }, true);
+
drawableObject.ApplyCustomUpdateState += updateStateTransforms;
- updateStateTransforms(drawableObject, drawableObject.State.Value);
}
private void updateStateTransforms(DrawableHitObject drawableHitObject, ArmedState state)
@@ -173,11 +178,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon
.FadeOut(flash_in_duration);
}
- // The flash layer starts white to give the wanted brightness, but is almost immediately
- // recoloured to the accent colour. This would more correctly be done with two layers (one for the initial flash)
- // but works well enough with the colour fade.
flash.FadeTo(1, flash_in_duration, Easing.OutQuint);
- flash.FlashColour(accentColour.Value, fade_out_time, Easing.OutQuint);
this.FadeOut(fade_out_time, Easing.OutQuad);
break;
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
index 1b2ab82044..f9f9751b6c 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs
@@ -134,10 +134,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
switch (state)
{
case ArmedState.Hit:
- CircleSprite.FadeOut(legacy_fade_duration, Easing.Out);
+ CircleSprite.FadeOut(legacy_fade_duration);
CircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
- OverlaySprite.FadeOut(legacy_fade_duration, Easing.Out);
+ OverlaySprite.FadeOut(legacy_fade_duration);
OverlaySprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
if (hasNumber)
@@ -146,11 +146,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
if (legacyVersion >= 2.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, Easing.Out);
+ hitCircleText.FadeOut(legacy_fade_duration / 4);
else
{
// old skins scale and fade it normally along other pieces.
- hitCircleText.FadeOut(legacy_fade_duration, Easing.Out);
+ hitCircleText.FadeOut(legacy_fade_duration);
hitCircleText.ScaleTo(1.4f, legacy_fade_duration, Easing.Out);
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
index 22944becf3..71c3e4c9f0 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyNewStyleSpinner.cs
@@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
this.FadeOut();
- using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn / 2))
- this.FadeInFromZero(spinner.TimeFadeIn / 2);
+ using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimeFadeIn))
+ this.FadeInFromZero(spinner.TimeFadeIn);
using (BeginAbsoluteSequence(spinner.StartTime - spinner.TimePreempt))
{
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
index 414879f42d..60d71ae843 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderBall.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.Sprites;
@@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
[Resolved(canBeNull: true)]
private DrawableHitObject? parentObject { get; set; }
+ public Color4 BallColour => animationContent.Colour;
+
private Sprite layerNd = null!;
private Sprite layerSpec = null!;
@@ -61,6 +64,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
};
}
+ private readonly IBindable accentColour = new Bindable();
+
protected override void LoadComplete()
{
base.LoadComplete();
@@ -69,6 +74,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
parentObject.ApplyCustomUpdateState += updateStateTransforms;
updateStateTransforms(parentObject, parentObject.State.Value);
+
+ if (skin.GetConfig(SkinConfiguration.LegacySetting.AllowSliderBallTint)?.Value == true)
+ {
+ accentColour.BindTo(parentObject.AccentColour);
+ accentColour.BindValueChanged(a => animationContent.Colour = a.NewValue, true);
+ }
}
}
diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs
index 004222ad7a..a817e5f2b7 100644
--- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySpinner.cs
@@ -65,6 +65,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{
spin = new Sprite
{
+ Alpha = 0,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-spin"),
@@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
},
bonusCounter = new LegacySpriteText(LegacyFont.Score)
{
- Alpha = 0f,
+ Alpha = 0,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
Scale = new Vector2(SPRITE_SCALE),
@@ -179,6 +180,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
spmCounter.MoveToOffset(new Vector2(0, -spm_hide_offset), d.HitObject.TimeFadeIn, Easing.Out);
}
+ using (BeginAbsoluteSequence(d.HitObject.StartTime - d.HitObject.TimeFadeIn / 2))
+ spin.FadeInFromZero(d.HitObject.TimeFadeIn / 2);
+
using (BeginAbsoluteSequence(d.HitObject.StartTime))
ApproachCircle?.ScaleTo(SPRITE_SCALE * 0.1f, d.HitObject.Duration);
diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
index 306a1e38b9..1c0a62454b 100644
--- a/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/OsuSkinConfiguration.cs
@@ -9,7 +9,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
SliderBorderSize,
SliderPathRadius,
- AllowSliderBallTint,
CursorCentre,
CursorExpand,
CursorRotate,
diff --git a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
index aba4d0ff63..46c8e7c02a 100644
--- a/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
+++ b/osu.Game.Rulesets.Osu/Skinning/SmokeSegment.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@@ -22,8 +23,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
public abstract class SmokeSegment : Drawable, ITexturedShaderDrawable
{
- private const int max_point_count = 18_000;
-
// fade anim values
private const double initial_fade_out_duration = 4000;
@@ -85,12 +84,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
totalDistance = pointInterval;
}
- private Vector2 nextPointDirection()
- {
- float angle = RNG.NextSingle(0, 2 * MathF.PI);
- return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
- }
-
public void AddPosition(Vector2 position, double time)
{
lastPosition ??= position;
@@ -107,33 +100,27 @@ namespace osu.Game.Rulesets.Osu.Skinning
Vector2 pointPos = (pointInterval - (totalDistance - delta)) * increment + (Vector2)lastPosition;
increment *= pointInterval;
- if (SmokePoints.Count > 0 && SmokePoints[^1].Time > time)
- {
- int index = ~SmokePoints.BinarySearch(new SmokePoint { Time = time }, new SmokePoint.UpperBoundComparer());
- SmokePoints.RemoveRange(index, SmokePoints.Count - index);
- }
-
totalDistance %= pointInterval;
- for (int i = 0; i < count; i++)
+ if (SmokePoints.Count == 0 || SmokePoints[^1].Time <= time)
{
- SmokePoints.Add(new SmokePoint
+ for (int i = 0; i < count; i++)
{
- Position = pointPos,
- Time = time,
- Direction = nextPointDirection(),
- });
+ SmokePoints.Add(new SmokePoint
+ {
+ Position = pointPos,
+ Time = time,
+ Angle = RNG.NextSingle(0, 2 * MathF.PI),
+ });
- pointPos += increment;
+ pointPos += increment;
+ }
}
Invalidate(Invalidation.DrawNode);
}
lastPosition = position;
-
- if (SmokePoints.Count >= max_point_count)
- FinishDrawing(time);
}
public void FinishDrawing(double time)
@@ -157,7 +144,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
public Vector2 Position;
public double Time;
- public Vector2 Direction;
+ public float Angle;
public struct UpperBoundComparer : IComparer
{
@@ -171,6 +158,17 @@ namespace osu.Game.Rulesets.Osu.Skinning
return x.Time > target.Time ? 1 : -1;
}
}
+
+ public struct LowerBoundComparer : IComparer
+ {
+ public int Compare(SmokePoint x, SmokePoint target)
+ {
+ // Similar logic as UpperBoundComparer, except returned index will always be
+ // the first element larger or equal
+
+ return x.Time < target.Time ? -1 : 1;
+ }
+ }
}
protected class SmokeDrawNode : TexturedShaderDrawNode
@@ -187,11 +185,11 @@ namespace osu.Game.Rulesets.Osu.Skinning
private Vector2 drawSize;
private Texture? texture;
private int rotationSeed;
- private int rotationIndex;
+ private int firstVisiblePointIndex;
// anim calculation vars (color, scale, direction)
private double initialFadeOutDurationTrunc;
- private double firstVisiblePointTime;
+ private double firstVisiblePointTimeAfterSmokeEnded;
private double initialFadeOutTime;
private double reFadeInTime;
@@ -206,9 +204,6 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
base.ApplyState();
- points.Clear();
- points.AddRange(Source.SmokePoints);
-
radius = Source.radius;
drawSize = Source.DrawSize;
texture = Source.Texture;
@@ -220,11 +215,18 @@ namespace osu.Game.Rulesets.Osu.Skinning
rotationSeed = Source.rotationSeed;
initialFadeOutDurationTrunc = Math.Min(initial_fade_out_duration, SmokeEndTime - SmokeStartTime);
- firstVisiblePointTime = SmokeEndTime - initialFadeOutDurationTrunc;
+ firstVisiblePointTimeAfterSmokeEnded = SmokeEndTime - initialFadeOutDurationTrunc;
- initialFadeOutTime = CurrentTime;
- reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / re_fade_in_speed);
- finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTime * (1 - 1 / final_fade_out_speed);
+ initialFadeOutTime = Math.Min(CurrentTime, SmokeEndTime);
+ reFadeInTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / re_fade_in_speed);
+ finalFadeOutTime = CurrentTime - initialFadeOutDurationTrunc - firstVisiblePointTimeAfterSmokeEnded * (1 - 1 / final_fade_out_speed);
+
+ double firstVisiblePointTime = Math.Min(SmokeEndTime, CurrentTime) - initialFadeOutDurationTrunc;
+ firstVisiblePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = firstVisiblePointTime }, new SmokePoint.LowerBoundComparer());
+ int futurePointIndex = ~Source.SmokePoints.BinarySearch(new SmokePoint { Time = CurrentTime }, new SmokePoint.UpperBoundComparer());
+
+ points.Clear();
+ points.AddRange(Source.SmokePoints.Skip(firstVisiblePointIndex).Take(futurePointIndex - firstVisiblePointIndex));
}
public sealed override void Draw(IRenderer renderer)
@@ -234,9 +236,14 @@ namespace osu.Game.Rulesets.Osu.Skinning
if (points.Count == 0)
return;
- rotationIndex = 0;
+ quadBatch ??= renderer.CreateQuadBatch(200, 4);
+
+ if (points.Count > quadBatch.Size && quadBatch.Size != IRenderer.MAX_QUADS)
+ {
+ int batchSize = Math.Min(quadBatch.Size * 2, IRenderer.MAX_QUADS);
+ quadBatch = renderer.CreateQuadBatch(batchSize, 4);
+ }
- quadBatch ??= renderer.CreateQuadBatch(max_point_count / 10, 10);
texture ??= renderer.WhitePixel;
RectangleF textureRect = texture.GetTextureRect();
@@ -248,8 +255,8 @@ namespace osu.Game.Rulesets.Osu.Skinning
shader.Bind();
texture.Bind();
- foreach (var point in points)
- drawPointQuad(point, textureRect);
+ for (int i = 0; i < points.Count; i++)
+ drawPointQuad(points[i], textureRect, i + firstVisiblePointIndex);
shader.Unbind();
renderer.PopLocalMatrix();
@@ -263,30 +270,34 @@ namespace osu.Game.Rulesets.Osu.Skinning
{
var color = Color4.White;
- double timeDoingInitialFadeOut = Math.Min(initialFadeOutTime, SmokeEndTime) - point.Time;
+ double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
- if (timeDoingInitialFadeOut > 0)
+ if (timeDoingFinalFadeOut > 0 && point.Time >= firstVisiblePointTimeAfterSmokeEnded)
{
- float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
- color.A = (1 - fraction) * initial_alpha;
+ float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
+ fraction = MathF.Pow(fraction, 5);
+ color.A = (1 - fraction) * re_fade_in_alpha;
}
-
- if (color.A > 0)
+ else
{
- double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
- double timeDoingFinalFadeOut = finalFadeOutTime - point.Time / final_fade_out_speed;
+ double timeDoingInitialFadeOut = initialFadeOutTime - point.Time;
- if (timeDoingFinalFadeOut > 0)
+ if (timeDoingInitialFadeOut > 0)
{
- float fraction = Math.Clamp((float)(timeDoingFinalFadeOut / final_fade_out_duration), 0, 1);
- fraction = MathF.Pow(fraction, 5);
- color.A = (1 - fraction) * re_fade_in_alpha;
+ float fraction = Math.Clamp((float)(timeDoingInitialFadeOut / initial_fade_out_duration), 0, 1);
+ color.A = (1 - fraction) * initial_alpha;
}
- else if (timeDoingReFadeIn > 0)
+
+ if (point.Time > firstVisiblePointTimeAfterSmokeEnded)
{
- float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
- fraction = 1 - MathF.Pow(1 - fraction, 5);
- color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
+ double timeDoingReFadeIn = reFadeInTime - point.Time / re_fade_in_speed;
+
+ if (timeDoingReFadeIn > 0)
+ {
+ float fraction = Math.Clamp((float)(timeDoingReFadeIn / re_fade_in_duration), 0, 1);
+ fraction = 1 - MathF.Pow(1 - fraction, 5);
+ color.A = fraction * (re_fade_in_alpha - color.A) + color.A;
+ }
}
}
@@ -301,33 +312,33 @@ namespace osu.Game.Rulesets.Osu.Skinning
return fraction * (final_scale - initial_scale) + initial_scale;
}
- protected virtual Vector2 PointDirection(SmokePoint point)
+ protected virtual Vector2 PointDirection(SmokePoint point, int index)
{
- float initialAngle = MathF.Atan2(point.Direction.Y, point.Direction.X);
- float finalAngle = initialAngle + nextRotation();
-
double timeDoingRotation = CurrentTime - point.Time;
float fraction = Math.Clamp((float)(timeDoingRotation / rotation_duration), 0, 1);
fraction = 1 - MathF.Pow(1 - fraction, 5);
- float angle = fraction * (finalAngle - initialAngle) + initialAngle;
+ float angle = fraction * getRotation(index) + point.Angle;
return new Vector2(MathF.Sin(angle), -MathF.Cos(angle));
}
- private float nextRotation() => max_rotation * (StatelessRNG.NextSingle(rotationSeed, rotationIndex++) * 2 - 1);
+ private float getRotation(int index) => max_rotation * (StatelessRNG.NextSingle(rotationSeed, index) * 2 - 1);
- private void drawPointQuad(SmokePoint point, RectangleF textureRect)
+ private void drawPointQuad(SmokePoint point, RectangleF textureRect, int index)
{
Debug.Assert(quadBatch != null);
var colour = PointColour(point);
- float scale = PointScale(point);
- var dir = PointDirection(point);
- var ortho = dir.PerpendicularLeft;
-
- if (colour.A == 0 || scale == 0)
+ if (colour.A == 0)
return;
+ float scale = PointScale(point);
+ if (scale == 0)
+ return;
+
+ var dir = PointDirection(point, index);
+ var ortho = dir.PerpendicularLeft;
+
var localTopLeft = point.Position + (radius * scale * (-ortho - dir));
var localTopRight = point.Position + (radius * scale * (-ortho + dir));
var localBotLeft = point.Position + (radius * scale * (ortho - dir));
diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
index 1e87ed27df..a98f931e7a 100644
--- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
+++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs
@@ -71,9 +71,9 @@ namespace osu.Game.Tests.Editing
[TestCase(1)]
[TestCase(2)]
- public void TestSpeedMultiplier(float multiplier)
+ public void TestSpeedMultiplierDoesNotChangeDistanceSnap(float multiplier)
{
- assertSnapDistance(100 * multiplier, new HitObject
+ assertSnapDistance(100, new HitObject
{
DifficultyControlPoint = new DifficultyControlPoint
{
diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
index 5aadd6f56a..917434ae22 100644
--- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
+++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs
@@ -244,7 +244,10 @@ namespace osu.Game.Tests.Visual.Background
public void TestResumeFromPlayer()
{
performFullSetup();
- AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos));
+ AddStep("Move mouse to Visual Settings location", () => InputManager.MoveMouseTo(playerLoader.ScreenSpaceDrawQuad.TopRight
+ + new Vector2(-playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Width,
+ playerLoader.VisualSettingsPos.ScreenSpaceDrawQuad.Height / 2
+ )));
AddStep("Resume PlayerLoader", () => player.Restart());
AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos));
diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
index 156a1ee34a..6d036f8e9b 100644
--- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
+++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs
@@ -214,7 +214,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void addControlPoints(IList controlPoints, double sequenceStartTime)
{
- controlPoints.ForEach(point => point.StartTime += sequenceStartTime);
+ controlPoints.ForEach(point => point.Time += sequenceStartTime);
scrollContainers.ForEach(container =>
{
@@ -224,7 +224,7 @@ namespace osu.Game.Tests.Visual.Gameplay
foreach (var playfield in playfields)
{
foreach (var controlPoint in controlPoints)
- playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime));
+ playfield.Add(createDrawablePoint(playfield, controlPoint.Time));
}
}
diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
index f31261dc1f..63677ce378 100644
--- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
+++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs
@@ -97,14 +97,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
[Test]
- public void TestCurrentItemDoesNotHaveDeleteButton()
+ public void TestSingleItemDoesNotHaveDeleteButton()
+ {
+ AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
+ AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
+
+ assertDeleteButtonVisibility(0, false);
+ }
+
+ [Test]
+ public void TestCurrentItemHasDeleteButtonIfNotSingle()
{
AddStep("set all players queue mode", () => MultiplayerClient.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers }).WaitSafely());
AddUntilStep("wait for queue mode change", () => MultiplayerClient.ClientAPIRoom?.QueueMode.Value == QueueMode.AllPlayers);
addPlaylistItem(() => API.LocalUser.Value.OnlineID);
- assertDeleteButtonVisibility(0, false);
+ assertDeleteButtonVisibility(0, true);
assertDeleteButtonVisibility(1, true);
AddStep("finish current item", () => MultiplayerClient.FinishCurrentItem().WaitSafely());
diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
index 6c0191ae27..75c47f0b1b 100644
--- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
+++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs
@@ -178,6 +178,7 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestKeyboardLocalCursor([Values] bool clickToShow)
{
+ AddStep("Enable cursor hiding", () => globalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = true);
AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3].ScreenSpaceDrawQuad.Centre + new Vector2(10, 0)));
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
AddAssert("Check global cursor alpha is 1", () => globalCursorDisplay.MenuCursor.Alpha == 1);
@@ -201,6 +202,7 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestKeyboardUserCursor([Values] bool clickToShow)
{
+ AddStep("Enable cursor hiding", () => globalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = true);
AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor));
AddAssert("Check global cursor alpha is 0", () => !checkVisible(globalCursorDisplay.MenuCursor) && globalCursorDisplay.MenuCursor.ActiveCursor.Alpha == 0);
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
index 56a432aec4..0a09e6e7e6 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs
@@ -9,11 +9,8 @@ using osuTK.Graphics;
namespace osu.Game.Beatmaps.ControlPoints
{
- public abstract class ControlPoint : IComparable, IDeepCloneable, IEquatable
+ public abstract class ControlPoint : IComparable, IDeepCloneable, IEquatable, IControlPoint
{
- ///
- /// The time at which the control point takes effect.
- ///
[JsonIgnore]
public double Time { get; set; }
diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
index 4be6b5eede..422e306450 100644
--- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
+++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs
@@ -196,8 +196,8 @@ namespace osu.Game.Beatmaps.ControlPoints
/// The time to find the control point at.
/// The control point to use when is before any control points.
/// The active control point at , or a fallback if none found.
- protected T BinarySearchWithFallback(IReadOnlyList list, double time, T fallback)
- where T : ControlPoint
+ public static T BinarySearchWithFallback(IReadOnlyList list, double time, T fallback)
+ where T : class, IControlPoint
{
return BinarySearch(list, time) ?? fallback;
}
@@ -207,9 +207,9 @@ namespace osu.Game.Beatmaps.ControlPoints
///
/// The list to search.
/// The time to find the control point at.
- /// The active control point at .
- protected virtual T BinarySearch(IReadOnlyList list, double time)
- where T : ControlPoint
+ /// The active control point at . Will return null if there are no control points, or if the time is before the first control point.
+ public static T BinarySearch(IReadOnlyList list, double time)
+ where T : class, IControlPoint
{
if (list == null)
throw new ArgumentNullException(nameof(list));
diff --git a/osu.Game/Beatmaps/ControlPoints/IControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/IControlPoint.cs
new file mode 100644
index 0000000000..091e99e029
--- /dev/null
+++ b/osu.Game/Beatmaps/ControlPoints/IControlPoint.cs
@@ -0,0 +1,13 @@
+// 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.Beatmaps.ControlPoints
+{
+ public interface IControlPoint
+ {
+ ///
+ /// The time at which the control point takes effect.
+ ///
+ double Time { get; }
+ }
+}
diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
index 75500fbc4e..5f5749dc73 100644
--- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
+++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
@@ -355,6 +355,14 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
+ case LegacyEventType.Sprite:
+ // Generally, the background is the first thing defined in a beatmap file.
+ // In some older beatmaps, it is not present and replaced by a storyboard-level background instead.
+ // Allow the first sprite (by file order) to act as the background in such cases.
+ if (string.IsNullOrEmpty(beatmap.BeatmapInfo.Metadata.BackgroundFile))
+ beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[3]);
+ break;
+
case LegacyEventType.Background:
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
break;
diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
index 33b0c308cc..af542989ff 100644
--- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
+++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs
@@ -23,6 +23,21 @@ namespace osu.Game.Graphics.Cursor
private readonly IBindable screenshotCursorVisibility = new Bindable(true);
public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent;
+ private bool hideCursorOnNonMouseInput;
+
+ public bool HideCursorOnNonMouseInput
+ {
+ get => hideCursorOnNonMouseInput;
+ set
+ {
+ if (hideCursorOnNonMouseInput == value)
+ return;
+
+ hideCursorOnNonMouseInput = value;
+ updateState();
+ }
+ }
+
protected override Drawable CreateCursor() => activeCursor = new Cursor();
private Cursor activeCursor = null!;
@@ -75,7 +90,7 @@ namespace osu.Game.Graphics.Cursor
private void updateState()
{
- bool combinedVisibility = State.Value == Visibility.Visible && lastInputWasMouse.Value && !isIdle.Value;
+ bool combinedVisibility = State.Value == Visibility.Visible && (lastInputWasMouse.Value || !hideCursorOnNonMouseInput) && !isIdle.Value;
if (visible == combinedVisibility)
return;
@@ -262,14 +277,19 @@ namespace osu.Game.Graphics.Cursor
{
switch (e)
{
- case MouseEvent:
+ case MouseDownEvent:
+ case MouseMoveEvent:
lastInputWasMouseSource.Value = true;
return false;
- default:
+ case KeyDownEvent keyDown when !keyDown.Repeat:
+ case JoystickPressEvent:
+ case MidiDownEvent:
lastInputWasMouseSource.Value = false;
return false;
}
+
+ return false;
}
}
diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs
index 6358317e9d..f0ff76b35d 100644
--- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs
+++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs
@@ -15,6 +15,9 @@ namespace osu.Game.Graphics.UserInterface
[Description("button")]
Button,
+ [Description("button-sidebar")]
+ ButtonSidebar,
+
[Description("toolbar")]
Toolbar,
diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
index 2a8b41fd20..9acb0c7f94 100644
--- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
+++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs
@@ -44,6 +44,8 @@ namespace osu.Game.Graphics.UserInterface
public virtual LocalisableString TooltipText { get; private set; }
+ public bool PlaySamplesOnAdjust { get; set; } = true;
+
///
/// Whether to format the tooltip as a percentage or the actual value.
///
@@ -187,6 +189,9 @@ namespace osu.Game.Graphics.UserInterface
private void playSample(T value)
{
+ if (!PlaySamplesOnAdjust)
+ return;
+
if (Clock == null || Clock.CurrentTime - lastSampleTime <= 30)
return;
diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
index 2c9f250028..4469d50acb 100644
--- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
+++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs
@@ -114,6 +114,7 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty("has_replay")]
public bool HasReplay { get; set; }
+ // These properties are calculated or not relevant to any external usage.
public bool ShouldSerializeID() => false;
public bool ShouldSerializeUser() => false;
public bool ShouldSerializeBeatmap() => false;
@@ -122,6 +123,18 @@ namespace osu.Game.Online.API.Requests.Responses
public bool ShouldSerializeOnlineID() => false;
public bool ShouldSerializeHasReplay() => false;
+ // These fields only need to be serialised if they hold values.
+ // Generally this is required because this model may be used by server-side components, but
+ // we don't want to bother sending these fields in score submission requests, for instance.
+ public bool ShouldSerializeEndedAt() => EndedAt != default;
+ public bool ShouldSerializeStartedAt() => StartedAt != default;
+ public bool ShouldSerializeLegacyScoreId() => LegacyScoreId != null;
+ public bool ShouldSerializeLegacyTotalScore() => LegacyTotalScore != null;
+ public bool ShouldSerializeMods() => Mods.Length > 0;
+ public bool ShouldSerializeUserID() => UserID > 0;
+ public bool ShouldSerializeBeatmapID() => BeatmapID > 0;
+ public bool ShouldSerializeBuildID() => BuildID != null;
+
#endregion
public override string ToString() => $"score_id: {ID} user_id: {UserID}";
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index b3eaf5cd01..2bdcb57f2a 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -1333,6 +1333,8 @@ namespace osu.Game
OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode);
API.Activity.BindTo(newOsuScreen.Activity);
+ GlobalCursorDisplay.MenuCursor.HideCursorOnNonMouseInput = newOsuScreen.HideMenuCursorOnNonMouseInput;
+
if (newOsuScreen.HideOverlaysOnEnter)
CloseAllOverlays();
else
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
index cea8fdd733..8f188f04d9 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs
@@ -8,6 +8,7 @@ using osu.Framework.Audio;
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
@@ -21,7 +22,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
{
Children = new Drawable[]
{
- new SettingsSlider
+ new VolumeAdjustSlider
{
LabelText = AudioSettingsStrings.MasterVolume,
Current = audio.Volume,
@@ -35,14 +36,15 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
- new SettingsSlider
+ new VolumeAdjustSlider
{
LabelText = AudioSettingsStrings.EffectVolume,
Current = audio.VolumeSample,
KeyboardStep = 0.01f,
DisplayAsPercentage = true
},
- new SettingsSlider
+
+ new VolumeAdjustSlider
{
LabelText = AudioSettingsStrings.MusicVolume,
Current = audio.VolumeTrack,
@@ -51,5 +53,15 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
},
};
}
+
+ private class VolumeAdjustSlider : SettingsSlider
+ {
+ protected override Drawable CreateControl()
+ {
+ var sliderBar = (OsuSliderBar)base.CreateControl();
+ sliderBar.PlaySamplesOnAdjust = false;
+ return sliderBar;
+ }
+ }
}
}
diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs
index c6a4cbbcaa..2c4832c68a 100644
--- a/osu.Game/Overlays/Settings/SidebarButton.cs
+++ b/osu.Game/Overlays/Settings/SidebarButton.cs
@@ -16,6 +16,11 @@ namespace osu.Game.Overlays.Settings
[Resolved]
protected OverlayColourProvider ColourProvider { get; private set; }
+ protected SidebarButton()
+ : base(HoverSampleSet.ButtonSidebar)
+ {
+ }
+
[BackgroundDependencyLoader]
private void load()
{
diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs
index 04c9a626b5..6dd9e2a56d 100644
--- a/osu.Game/Overlays/SettingsToolboxGroup.cs
+++ b/osu.Game/Overlays/SettingsToolboxGroup.cs
@@ -40,8 +40,6 @@ namespace osu.Game.Overlays
Anchor = Anchor.TopCentre,
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
- AutoSizeDuration = transition_duration,
- AutoSizeEasing = Easing.OutQuint,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 10, Top = 5, Bottom = 10 },
Spacing = new Vector2(0, 15),
diff --git a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
index 46a827e03a..d23d8ad438 100644
--- a/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/DistancedHitObjectComposer.cs
@@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit
public virtual float GetBeatSnapDistanceAt(HitObject referenceObject)
{
- return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * referenceObject.DifficultyControlPoint.SliderVelocity / BeatSnapProvider.BeatDivisor);
+ return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * 1 / BeatSnapProvider.BeatDivisor);
}
public virtual float DurationToDistance(HitObject referenceObject, double duration)
diff --git a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
index 1e80bd165b..279de2f940 100644
--- a/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
+++ b/osu.Game/Rulesets/Timing/MultiplierControlPoint.cs
@@ -11,12 +11,12 @@ namespace osu.Game.Rulesets.Timing
///
/// A control point which adds an aggregated multiplier based on the provided 's BeatLength and 's SpeedMultiplier.
///
- public class MultiplierControlPoint : IComparable
+ public class MultiplierControlPoint : IComparable, IControlPoint
{
///
/// The time in milliseconds at which this starts.
///
- public double StartTime;
+ public double Time { get; set; }
///
/// The aggregate multiplier which this provides.
@@ -54,13 +54,13 @@ namespace osu.Game.Rulesets.Timing
///
/// Creates a .
///
- /// The start time of this .
- public MultiplierControlPoint(double startTime)
+ /// The start time of this .
+ public MultiplierControlPoint(double time)
{
- StartTime = startTime;
+ Time = time;
}
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
- public int CompareTo(MultiplierControlPoint other) => StartTime.CompareTo(other?.StartTime);
+ public int CompareTo(MultiplierControlPoint other) => Time.CompareTo(other?.Time);
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
index 0bd8aa64c9..c957a84eb1 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs
@@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
return -PositionAt(startTime, endTime, timeRange, scrollLength);
}
- public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
+ public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
=> (float)((time - currentTime) / timeRange * scrollLength);
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
index d2fb9e3531..f78509f919 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs
@@ -53,8 +53,9 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
/// The current time.
/// The amount of visible time.
/// The absolute spatial length through .
+ /// The time to be used for control point lookups (ie. the parent's start time for nested hit objects).
/// The absolute spatial position.
- float PositionAt(double time, double currentTime, double timeRange, float scrollLength);
+ float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null);
///
/// Computes the time which brings a point to a provided spatial position given the current time.
@@ -63,7 +64,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
/// The current time.
/// The amount of visible time.
/// The absolute spatial length through .
- /// The time at which == .
+ /// The time at which == .
double TimeAt(float position, double currentTime, double timeRange, float scrollLength);
///
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
index d41117bce8..54079c7895 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs
@@ -4,22 +4,20 @@
#nullable disable
using System;
+using System.Linq;
using osu.Framework.Lists;
+using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Timing;
namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
{
public class OverlappingScrollAlgorithm : IScrollAlgorithm
{
- private readonly MultiplierControlPoint searchPoint;
-
private readonly SortedList controlPoints;
public OverlappingScrollAlgorithm(SortedList controlPoints)
{
this.controlPoints = controlPoints;
-
- searchPoint = new MultiplierControlPoint();
}
public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength)
@@ -37,8 +35,8 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
return -PositionAt(startTime, endTime, timeRange, scrollLength);
}
- public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
- => (float)((time - currentTime) / timeRange * controlPointAt(time).Multiplier * scrollLength);
+ public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
+ => (float)((time - currentTime) / timeRange * controlPointAt(originTime ?? time).Multiplier * scrollLength);
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
{
@@ -52,7 +50,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
for (; i < controlPoints.Count; i++)
{
float lastPos = pos;
- pos = PositionAt(controlPoints[i].StartTime, currentTime, timeRange, scrollLength);
+ pos = PositionAt(controlPoints[i].Time, currentTime, timeRange, scrollLength);
if (pos > position)
{
@@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
i = Math.Clamp(i, 0, controlPoints.Count - 1);
- return controlPoints[i].StartTime + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength;
+ return controlPoints[i].Time + (position - pos) * timeRange / controlPoints[i].Multiplier / scrollLength;
}
public void Reset()
@@ -78,19 +76,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
/// The .
private MultiplierControlPoint controlPointAt(double time)
{
- if (controlPoints.Count == 0)
- return new MultiplierControlPoint(double.NegativeInfinity);
-
- if (time < controlPoints[0].StartTime)
- return controlPoints[0];
-
- searchPoint.StartTime = time;
- int index = controlPoints.BinarySearch(searchPoint);
-
- if (index < 0)
- index = ~index - 1;
-
- return controlPoints[index];
+ return ControlPointInfo.BinarySearch(controlPoints, time)
+ // The standard binary search will fail if there's no control points, or if the time is before the first.
+ // For this method, we want to use the first control point in the latter case.
+ ?? controlPoints.FirstOrDefault()
+ ?? new MultiplierControlPoint(double.NegativeInfinity);
}
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
index bfddc22573..774beb20c7 100644
--- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs
@@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
return (float)(objectLength * scrollLength);
}
- public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
+ public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
{
double timelineLength = relativePositionAt(time, timeRange) - relativePositionAt(currentTime, timeRange);
return (float)(timelineLength * scrollLength);
@@ -121,7 +121,7 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
if (controlPoints.Count == 0)
return;
- positionMappings.Add(new PositionMapping(controlPoints[0].StartTime, controlPoints[0]));
+ positionMappings.Add(new PositionMapping(controlPoints[0].Time, controlPoints[0]));
for (int i = 0; i < controlPoints.Count - 1; i++)
{
@@ -129,9 +129,9 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms
var next = controlPoints[i + 1];
// Figure out how much of the time range the duration represents, and adjust it by the speed multiplier
- float length = (float)((next.StartTime - current.StartTime) / timeRange * current.Multiplier);
+ float length = (float)((next.Time - current.Time) / timeRange * current.Multiplier);
- positionMappings.Add(new PositionMapping(next.StartTime, next, positionMappings[^1].Position + length));
+ positionMappings.Add(new PositionMapping(next.Time, next, positionMappings[^1].Position + length));
}
}
diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
index 825aba5bc2..68469d083c 100644
--- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs
@@ -158,9 +158,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
// Trim unwanted sequences of timing changes
timingChanges = timingChanges
// Collapse sections after the last hit object
- .Where(s => s.StartTime <= lastObjectTime)
+ .Where(s => s.Time <= lastObjectTime)
// Collapse sections with the same start time
- .GroupBy(s => s.StartTime).Select(g => g.Last()).OrderBy(s => s.StartTime);
+ .GroupBy(s => s.Time).Select(g => g.Last()).OrderBy(s => s.Time);
ControlPoints.AddRange(timingChanges);
diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
index 37da157cc1..424fc7c44c 100644
--- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
+++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs
@@ -93,9 +93,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
///
/// Given a time, return the position along the scrolling axis within this at time .
///
- public float PositionAtTime(double time, double currentTime)
+ public float PositionAtTime(double time, double currentTime, double? originTime = null)
{
- float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength);
+ float scrollPosition = scrollingInfo.Algorithm.PositionAt(time, currentTime, timeRange.Value, scrollLength, originTime);
return axisInverted ? -scrollPosition : scrollPosition;
}
@@ -236,8 +236,10 @@ namespace osu.Game.Rulesets.UI.Scrolling
entry.LifetimeStart = Math.Min(entry.HitObject.StartTime - judgementOffset, computedStartTime);
}
- private void updateLayoutRecursive(DrawableHitObject hitObject)
+ private void updateLayoutRecursive(DrawableHitObject hitObject, double? parentHitObjectStartTime = null)
{
+ parentHitObjectStartTime ??= hitObject.HitObject.StartTime;
+
if (hitObject.HitObject is IHasDuration e)
{
float length = LengthAtTime(hitObject.HitObject.StartTime, e.EndTime);
@@ -249,17 +251,17 @@ namespace osu.Game.Rulesets.UI.Scrolling
foreach (var obj in hitObject.NestedHitObjects)
{
- updateLayoutRecursive(obj);
+ updateLayoutRecursive(obj, parentHitObjectStartTime);
// Nested hitobjects don't need to scroll, but they do need accurate positions and start lifetime
- updatePosition(obj, hitObject.HitObject.StartTime);
+ updatePosition(obj, hitObject.HitObject.StartTime, parentHitObjectStartTime);
setComputedLifetimeStart(obj.Entry);
}
}
- private void updatePosition(DrawableHitObject hitObject, double currentTime)
+ private void updatePosition(DrawableHitObject hitObject, double currentTime, double? parentHitObjectStartTime = null)
{
- float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime);
+ float position = PositionAtTime(hitObject.HitObject.StartTime, currentTime, parentHitObjectStartTime);
if (scrollingAxis == Direction.Horizontal)
hitObject.X = position;
diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs
index 7d8657a3df..a5739a41b1 100644
--- a/osu.Game/Screens/IOsuScreen.cs
+++ b/osu.Game/Screens/IOsuScreen.cs
@@ -41,6 +41,11 @@ namespace osu.Game.Screens
///
bool HideOverlaysOnEnter { get; }
+ ///
+ /// Whether the menu cursor should be hidden when non-mouse input is received.
+ ///
+ bool HideMenuCursorOnNonMouseInput { get; }
+
///
/// Whether overlays should be able to be opened when this screen is current.
///
diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
index 39740e650f..ba6b482729 100644
--- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
+++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs
@@ -78,9 +78,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist
return;
bool isItemOwner = Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost;
+ bool isValidItem = isItemOwner && !Item.Expired;
- AllowDeletion = isItemOwner && !Item.Expired && Item.ID != multiplayerClient.Room.Settings.PlaylistItemId;
- AllowEditing = isItemOwner && !Item.Expired;
+ AllowDeletion = isValidItem
+ && (Item.ID != multiplayerClient.Room.Settings.PlaylistItemId // This is an optimisation for the following check.
+ || multiplayerClient.Room.Playlist.Count(i => !i.Expired) > 1);
+
+ AllowEditing = isValidItem;
}
protected override void Dispose(bool isDisposing)
diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs
index 0678a90f71..6be13bbda3 100644
--- a/osu.Game/Screens/OsuScreen.cs
+++ b/osu.Game/Screens/OsuScreen.cs
@@ -40,11 +40,10 @@ namespace osu.Game.Screens
public virtual bool AllowExternalScreenChange => false;
- ///
- /// Whether all overlays should be hidden when this screen is entered or resumed.
- ///
public virtual bool HideOverlaysOnEnter => false;
+ public virtual bool HideMenuCursorOnNonMouseInput => false;
+
///
/// The initial overlay activation mode to use when this screen is entered for the first time.
///
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index 68b623b781..7048f83c09 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -66,6 +66,8 @@ namespace osu.Game.Screens.Play
public override bool HideOverlaysOnEnter => true;
+ public override bool HideMenuCursorOnNonMouseInput => true;
+
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
// We are managing our own adjustments (see OnEntering/OnExiting).
diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs
index 2d096f1c38..4ff5083107 100644
--- a/osu.Game/Screens/Play/PlayerLoader.cs
+++ b/osu.Game/Screens/Play/PlayerLoader.cs
@@ -64,6 +64,8 @@ namespace osu.Game.Screens.Play
protected Task? DisposalTask { get; private set; }
+ private OsuScrollContainer settingsScroll = null!;
+
private bool backgroundBrightnessReduction;
private readonly BindableDouble volumeAdjustment = new BindableDouble(1);
@@ -168,30 +170,30 @@ namespace osu.Game.Screens.Play
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
- new OsuScrollContainer
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Y,
- Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2,
- Padding = new MarginPadding { Vertical = padding },
- Masking = false,
- Child = PlayerSettings = new FillFlowContainer
- {
- AutoSizeAxes = Axes.Both,
- Direction = FillDirection.Vertical,
- Spacing = new Vector2(0, 20),
- Padding = new MarginPadding { Horizontal = padding },
- Children = new PlayerSettingsGroup[]
- {
- VisualSettings = new VisualSettings(),
- AudioSettings = new AudioSettings(),
- new InputSettings()
- }
- },
- },
- idleTracker = new IdleTracker(750),
}),
+ settingsScroll = new OsuScrollContainer
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Y,
+ Width = SettingsToolboxGroup.CONTAINER_WIDTH + padding * 2,
+ Padding = new MarginPadding { Vertical = padding },
+ Masking = false,
+ Child = PlayerSettings = new FillFlowContainer
+ {
+ AutoSizeAxes = Axes.Both,
+ Direction = FillDirection.Vertical,
+ Spacing = new Vector2(0, 20),
+ Padding = new MarginPadding { Horizontal = padding },
+ Children = new PlayerSettingsGroup[]
+ {
+ VisualSettings = new VisualSettings(),
+ AudioSettings = new AudioSettings(),
+ new InputSettings()
+ }
+ },
+ },
+ idleTracker = new IdleTracker(750),
lowPassFilter = new AudioFilter(audio.TrackMixer),
highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass)
};
@@ -227,6 +229,9 @@ namespace osu.Game.Screens.Play
Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment);
+ // Start off-screen.
+ settingsScroll.MoveToX(settingsScroll.DrawWidth);
+
content.ScaleTo(0.7f);
contentIn();
@@ -316,6 +321,16 @@ namespace osu.Game.Screens.Play
content.StopTracking();
}
+ protected override void LogoSuspending(OsuLogo logo)
+ {
+ base.LogoSuspending(logo);
+ content.StopTracking();
+
+ logo
+ .FadeOut(CONTENT_OUT_DURATION / 2, Easing.OutQuint)
+ .ScaleTo(logo.Scale * 0.8f, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
+ }
+
#endregion
protected override void Update()
@@ -394,6 +409,10 @@ namespace osu.Game.Screens.Play
content.FadeInFromZero(400);
content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer);
+
+ settingsScroll.FadeInFromZero(500, Easing.Out)
+ .MoveToX(0, 500, Easing.OutQuint);
+
lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint);
highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in)
@@ -407,6 +426,10 @@ namespace osu.Game.Screens.Play
content.ScaleTo(0.7f, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
content.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint);
+
+ settingsScroll.FadeOut(CONTENT_OUT_DURATION, Easing.OutQuint)
+ .MoveToX(settingsScroll.DrawWidth, CONTENT_OUT_DURATION * 2, Easing.OutQuint);
+
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, CONTENT_OUT_DURATION);
highPassFilter.CutoffTo(0, CONTENT_OUT_DURATION);
}
@@ -435,7 +458,7 @@ namespace osu.Game.Screens.Play
ContentOut();
- TransformSequence pushSequence = this.Delay(CONTENT_OUT_DURATION);
+ TransformSequence pushSequence = this.Delay(0);
// only show if the warning was created (i.e. the beatmap needs it)
// and this is not a restart of the map (the warning expires after first load).
@@ -444,6 +467,7 @@ namespace osu.Game.Screens.Play
const double epilepsy_display_length = 3000;
pushSequence
+ .Delay(CONTENT_OUT_DURATION)
.Schedule(() => epilepsyWarning.State.Value = Visibility.Visible)
.TransformBindableTo(volumeAdjustment, 0.25, EpilepsyWarning.FADE_DURATION, Easing.OutQuint)
.Delay(epilepsy_display_length)
diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs
index d56b9c23c8..345bd5a134 100644
--- a/osu.Game/Screens/Play/SubmittingPlayer.cs
+++ b/osu.Game/Screens/Play/SubmittingPlayer.cs
@@ -86,16 +86,13 @@ namespace osu.Game.Screens.Play
// Generally a timeout would not happen here as APIAccess will timeout first.
if (!tcs.Task.Wait(60000))
- handleTokenFailure(new InvalidOperationException("Token retrieval timed out (request never run)"));
+ req.TriggerFailure(new InvalidOperationException("Token retrieval timed out (request never run)"));
return true;
void handleTokenFailure(Exception exception)
{
- // This method may be invoked multiple times due to the Task.Wait call above.
- // We only really care about the first error.
- if (!tcs.TrySetResult(false))
- return;
+ tcs.SetResult(false);
if (HandleTokenRetrievalFailure(exception))
{
diff --git a/osu.Game/Skinning/DefaultLegacySkin.cs b/osu.Game/Skinning/DefaultLegacySkin.cs
index 04f1286dc7..b80275a1e8 100644
--- a/osu.Game/Skinning/DefaultLegacySkin.cs
+++ b/osu.Game/Skinning/DefaultLegacySkin.cs
@@ -46,6 +46,8 @@ namespace osu.Game.Skinning
new Color4(242, 24, 57, 255)
};
+ Configuration.ConfigDictionary[nameof(SkinConfiguration.LegacySetting.AllowSliderBallTint)] = @"true";
+
Configuration.LegacyVersion = 2.7m;
}
}
diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs
index 980dee8601..469657c03c 100644
--- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs
+++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs
@@ -85,10 +85,6 @@ namespace osu.Game.Skinning.Editor
{
public Action? RequestPlacement;
- protected override bool ShouldBeConsideredForInput(Drawable child) => false;
-
- public override bool PropagateNonPositionalInputSubTree => false;
-
private readonly Drawable component;
private readonly CompositeDrawable? dependencySource;
@@ -177,6 +173,10 @@ namespace osu.Game.Skinning.Editor
public class DependencyBorrowingContainer : Container
{
+ protected override bool ShouldBeConsideredForInput(Drawable child) => false;
+
+ public override bool PropagateNonPositionalInputSubTree => false;
+
private readonly CompositeDrawable? donor;
public DependencyBorrowingContainer(CompositeDrawable? donor)
diff --git a/osu.Game/Skinning/SkinConfiguration.cs b/osu.Game/Skinning/SkinConfiguration.cs
index 0b1159f8fd..4e5d96ccb8 100644
--- a/osu.Game/Skinning/SkinConfiguration.cs
+++ b/osu.Game/Skinning/SkinConfiguration.cs
@@ -38,7 +38,8 @@ namespace osu.Game.Skinning
HitCirclePrefix,
HitCircleOverlap,
AnimationFramerate,
- LayeredHitSounds
+ LayeredHitSounds,
+ AllowSliderBallTint,
}
public static List DefaultComboColours { get; } = new List
diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
index cf7fe6e45d..1817a704b9 100644
--- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs
+++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs
@@ -99,8 +99,8 @@ namespace osu.Game.Tests.Visual
public float GetLength(double startTime, double endTime, double timeRange, float scrollLength)
=> implementation.GetLength(startTime, endTime, timeRange, scrollLength);
- public float PositionAt(double time, double currentTime, double timeRange, float scrollLength)
- => implementation.PositionAt(time, currentTime, timeRange, scrollLength);
+ public float PositionAt(double time, double currentTime, double timeRange, float scrollLength, double? originTime = null)
+ => implementation.PositionAt(time, currentTime, timeRange, scrollLength, originTime);
public double TimeAt(float position, double currentTime, double timeRange, float scrollLength)
=> implementation.TimeAt(position, currentTime, timeRange, scrollLength);
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index f1fed6913b..22474c0592 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -18,7 +18,7 @@
-
+
@@ -35,8 +35,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
diff --git a/osu.iOS.props b/osu.iOS.props
index c79d0e4864..cf70b65578 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -61,8 +61,8 @@
-
-
+
+
@@ -82,7 +82,7 @@
-
+